/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.RuleChainVisitor;

/**
 * The RuleChain is a means by which Rules can participate in a uniform
 * visitation of the AST, and not need perform their own independent visitation.
 * The RuleChain exists as a means to improve the speed of PMD when there are
 * many Rules.
 */
public class RuleChain {
    // Mapping from Language to RuleChainVisitor
    private final Map<Language, RuleChainVisitor> languageToRuleChainVisitor = new HashMap<>();

    /**
     * Add all Rules from the given RuleSet which want to participate in the
     * RuleChain.
     *
     * @param ruleSet
     *            The RuleSet to add Rules from.
     */
    public void add(RuleSet ruleSet) {
        for (Rule r : ruleSet.getRules()) {
            add(ruleSet, r);
        }
    }

    /**
     * Add the given Rule if it wants to participate in the RuleChain.
     *
     * @param ruleSet
     *            The RuleSet to which the rule belongs.
     * @param rule
     *            The Rule to add.
     */
    private void add(RuleSet ruleSet, Rule rule) {
        RuleChainVisitor visitor = getRuleChainVisitor(rule.getLanguage());
        if (visitor != null) {
            visitor.add(ruleSet, rule);
        }
    }

    /**
     * Apply the RuleChain to the given Nodes using the given RuleContext, for
     * those rules using the given Language.
     *
     * @param nodes
     *            The Nodes.
     * @param ctx
     *            The RuleContext.
     * @param language
     *            The Language.
     */
    public void apply(List<Node> nodes, RuleContext ctx, Language language) {
        RuleChainVisitor visitor = getRuleChainVisitor(language);
        if (visitor != null) {
            visitor.visitAll(nodes, ctx);
        }
    }

    // Get the RuleChainVisitor for the appropriate Language.
    private RuleChainVisitor getRuleChainVisitor(Language language) {
        RuleChainVisitor visitor = languageToRuleChainVisitor.get(language);
        if (visitor == null) {
            if (language.getRuleChainVisitorClass() != null) {
                try {
                    visitor = (RuleChainVisitor) language.getRuleChainVisitorClass().newInstance();
                } catch (InstantiationException e) {
                    throw new IllegalStateException(
                            "Failure to created RuleChainVisitor: " + language.getRuleChainVisitorClass(), e);
                } catch (IllegalAccessException e) {
                    throw new IllegalStateException(
                            "Failure to created RuleChainVisitor: " + language.getRuleChainVisitorClass(), e);
                }
                languageToRuleChainVisitor.put(language, visitor);
            } else {
                throw new IllegalArgumentException("Language does not have a RuleChainVisitor: " + language);
            }
        }
        return visitor;
    }
}
