/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.rule; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.benchmark.Benchmark; import net.sourceforge.pmd.benchmark.Benchmarker; import net.sourceforge.pmd.lang.ast.Node; /** * This is a base class for RuleChainVisitor implementations which extracts * interesting nodes from an AST, and lets each Rule visit the nodes it has * expressed interest in. */ public abstract class AbstractRuleChainVisitor implements RuleChainVisitor { /** * These are all the rules participating in the RuleChain, grouped by * RuleSet. */ protected Map<RuleSet, List<Rule>> ruleSetRules = new LinkedHashMap<>(); /** * This is a mapping from node names to nodes instances for the current AST. */ protected Map<String, List<Node>> nodeNameToNodes; /** * @see RuleChainVisitor#add(RuleSet, Rule) */ @Override public void add(RuleSet ruleSet, Rule rule) { if (!ruleSetRules.containsKey(ruleSet)) { ruleSetRules.put(ruleSet, new ArrayList<Rule>()); } ruleSetRules.get(ruleSet).add(rule); } /** * @see RuleChainVisitor#visitAll(List, RuleContext) */ @Override public void visitAll(List<Node> nodes, RuleContext ctx) { initialize(); clear(); // Perform a visitation of the AST to index nodes which need visiting by // type long start = System.nanoTime(); indexNodes(nodes, ctx); long end = System.nanoTime(); Benchmarker.mark(Benchmark.RuleChainVisit, end - start, 1); // For each RuleSet, only if this source file applies for (Map.Entry<RuleSet, List<Rule>> entry : ruleSetRules.entrySet()) { RuleSet ruleSet = entry.getKey(); if (!ruleSet.applies(ctx.getSourceCodeFile())) { continue; } // For each rule, allow it to visit the nodes it desires start = System.nanoTime(); for (Rule rule : entry.getValue()) { int visits = 0; if (!RuleSet.applies(rule, ctx.getLanguageVersion())) { continue; } final List<String> nodeNames = rule.getRuleChainVisits(); for (int j = 0; j < nodeNames.size(); j++) { List<Node> ns = nodeNameToNodes.get(nodeNames.get(j)); for (Node node : ns) { // Visit with underlying Rule, not the RuleReference Rule actualRule = rule; while (actualRule instanceof RuleReference) { actualRule = ((RuleReference) actualRule).getRule(); } visit(actualRule, node, ctx); } visits += ns.size(); } end = System.nanoTime(); Benchmarker.mark(Benchmark.RuleChainRule, rule.getName(), end - start, visits); start = end; } } } /** * Visit the given rule to the given node. */ protected abstract void visit(Rule rule, Node node, RuleContext ctx); /** * Index all nodes for visitation by rules. */ protected abstract void indexNodes(List<Node> nodes, RuleContext ctx); /** * Index a single node for visitation by rules. */ protected void indexNode(Node node) { List<Node> nodes = nodeNameToNodes.get(node.toString()); if (nodes != null) { nodes.add(node); } } /** * Initialize the RuleChainVisitor to be ready to perform visitations. This * method should not be called until it is known that all Rules * participating in the RuleChain are ready to be initialized themselves. * Some rules may require full initialization to determine if they will * participate in the RuleChain, so this has been delayed as long as * possible to ensure that manipulation of the Rules is no longer occurring. */ protected void initialize() { if (nodeNameToNodes != null) { return; } // Determine all node types that need visiting Set<String> visitedNodes = new HashSet<>(); for (Iterator<Map.Entry<RuleSet, List<Rule>>> entryIterator = ruleSetRules.entrySet().iterator(); entryIterator .hasNext();) { Map.Entry<RuleSet, List<Rule>> entry = entryIterator.next(); for (Iterator<Rule> ruleIterator = entry.getValue().iterator(); ruleIterator.hasNext();) { Rule rule = ruleIterator.next(); if (rule.usesRuleChain()) { visitedNodes.addAll(rule.getRuleChainVisits()); } else { // Drop rules which do not participate in the rule chain. ruleIterator.remove(); } } // Drop RuleSets in which all Rules have been dropped. if (entry.getValue().isEmpty()) { entryIterator.remove(); } } // Setup the data structure to manage mapping node names to node // instances. We intend to reuse this data structure between // visits to different ASTs. nodeNameToNodes = new HashMap<>(); for (String s : visitedNodes) { List<Node> nodes = new ArrayList<>(100); nodeNameToNodes.put(s, nodes); } } /** * Clears the internal data structure used to manage the nodes visited * between visiting different ASTs. */ protected void clear() { for (List<Node> l : nodeNameToNodes.values()) { l.clear(); } } }