AbstractRuleChainVisitor.java
001 package net.sourceforge.pmd.lang.rule;
002 
003 import java.util.ArrayList;
004 import java.util.HashMap;
005 import java.util.HashSet;
006 import java.util.Iterator;
007 import java.util.LinkedHashMap;
008 import java.util.List;
009 import java.util.Map;
010 import java.util.Set;
011 
012 import net.sourceforge.pmd.Rule;
013 import net.sourceforge.pmd.RuleContext;
014 import net.sourceforge.pmd.RuleSet;
015 import net.sourceforge.pmd.lang.ast.Node;
016 import net.sourceforge.pmd.util.Benchmark;
017 
018 /**
019  * This is a base class for RuleChainVisitor implementations which
020  * extracts interesting nodes from an AST, and lets each Rule visit
021  * the nodes it has expressed interest in.
022  */
023 public abstract class AbstractRuleChainVisitor implements RuleChainVisitor {
024     /**
025      * These are all the rules participating in the RuleChain, grouped by RuleSet.
026      */
027     protected Map<RuleSet, List<Rule>> ruleSetRules = new LinkedHashMap<RuleSet, List<Rule>>();
028 
029     /**
030      * This is a mapping from node names to nodes instances for the current AST.
031      */
032     protected Map<String, List<Node>> nodeNameToNodes;
033 
034     /**
035      @see RuleChainVisitor#add(RuleSet, Rule)
036      */
037     public void add(RuleSet ruleSet, Rule rule) {
038   if (!ruleSetRules.containsKey(ruleSet)) {
039       ruleSetRules.put(ruleSet, new ArrayList<Rule>());
040   }
041   ruleSetRules.get(ruleSet).add(rule);
042     }
043 
044     /**
045      @see RuleChainVisitor#visitAll(List, RuleContext)
046      */
047     public void visitAll(List<Node> nodes, RuleContext ctx) {
048         initialize();
049         clear();
050 
051         // Perform a visitation of the AST to index nodes which need visiting by
052         // type
053         long start = System.nanoTime();
054         indexNodes(nodes, ctx);
055         long end = System.nanoTime();
056         Benchmark.mark(Benchmark.TYPE_RULE_CHAIN_VISIT, end - start, 1);
057 
058         // For each RuleSet, only if this source file applies
059         for (RuleSet ruleSet : ruleSetRules.keySet()) {
060             if (!ruleSet.applies(ctx.getSourceCodeFile())) {
061           continue;
062             }
063             // For each rule, allow it to visit the nodes it desires
064             start = System.nanoTime();
065             for (Rule rule: ruleSetRules.get(ruleSet)) {
066                 int visits = 0;
067           if (!RuleSet.applies(rule, ctx.getLanguageVersion())) {
068               continue;
069           }
070                 final List<String> nodeNames = rule.getRuleChainVisits();
071                 for (int j = 0; j < nodeNames.size(); j++) {
072                     List<Node> ns = nodeNameToNodes.get(nodeNames.get(j));
073                     for (Node node: ns) {
074                         // Visit with underlying Rule, not the RuleReference
075                         while (rule instanceof RuleReference) {
076                             rule = ((RuleReference)rule).getRule();
077                         }
078                         visit(rule, node, ctx);
079                     }
080                     visits += ns.size();
081                 }
082                 end = System.nanoTime();
083                 Benchmark.mark(Benchmark.TYPE_RULE_CHAIN_RULE, rule.getName(), end - start, visits);
084                 start = end;
085             }
086         }
087     }
088 
089     /**
090      * Visit the given rule to the given node.
091      */
092     protected abstract void visit(Rule rule, Node node, RuleContext ctx);
093 
094     /**
095      * Index all nodes for visitation by rules.
096      */
097     protected abstract void indexNodes(List<Node> nodes, RuleContext ctx);
098 
099     /**
100      * Index a single node for visitation by rules.
101      */
102     protected void indexNode(Node node) {
103         List<Node> nodes = nodeNameToNodes.get(node.toString());
104         if (nodes != null) {
105             nodes.add(node);
106         }
107     }
108 
109     /**
110      * Initialize the RuleChainVisitor to be ready to perform visitations. This
111      * method should not be called until it is known that all Rules participating
112      * in the RuleChain are ready to be initialized themselves.  Some rules
113      * may require full initialization to determine if they will participate in
114      * the RuleChain, so this has been delayed as long as possible to ensure
115      * that manipulation of the Rules is no longer occurring.
116      */
117     protected void initialize() {
118         if (nodeNameToNodes != null) {
119             return;
120         }
121 
122         // Determine all node types that need visiting
123         Set<String> visitedNodes = new HashSet<String>();
124         for (Iterator<Map.Entry<RuleSet, List<Rule>>> entryIterator = ruleSetRules.entrySet().iterator(); entryIterator.hasNext();) {
125             Map.Entry<RuleSet, List<Rule>> entry = entryIterator.next();
126             for (Iterator<Rule> ruleIterator = entry.getValue().iterator(); ruleIterator.hasNext();) {
127                 Rule rule = ruleIterator.next();
128                 if (rule.usesRuleChain()) {
129                     visitedNodes.addAll(rule.getRuleChainVisits());
130                 }
131                 else {
132                     // Drop rules which do not participate in the rule chain.
133                     ruleIterator.remove();
134                 }
135             }
136             // Drop RuleSets in which all Rules have been dropped.
137             if (entry.getValue().isEmpty()) {
138           entryIterator.remove();
139             }
140         }
141 
142         // Setup the data structure to manage mapping node names to node
143         // instances.  We intend to reuse this data structure between
144         // visits to different ASTs.
145         nodeNameToNodes = new HashMap<String, List<Node>>();
146         for (String s: visitedNodes) {
147             List<Node> nodes = new ArrayList<Node>(100);
148             nodeNameToNodes.put(s, nodes);
149         }
150     }
151 
152     /**
153      * Clears the internal data structure used to manage the nodes visited
154      * between visiting different ASTs.
155      */
156     protected void clear() {
157         for (List<Node> l: nodeNameToNodes.values()) {
158             l.clear();
159         }
160     }
161 }