View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.rule;
5   
6   import java.util.ArrayList;
7   import java.util.HashMap;
8   import java.util.HashSet;
9   import java.util.Iterator;
10  import java.util.LinkedHashMap;
11  import java.util.List;
12  import java.util.Map;
13  import java.util.Set;
14  
15  import net.sourceforge.pmd.Rule;
16  import net.sourceforge.pmd.RuleContext;
17  import net.sourceforge.pmd.RuleSet;
18  import net.sourceforge.pmd.benchmark.Benchmark;
19  import net.sourceforge.pmd.benchmark.Benchmarker;
20  import net.sourceforge.pmd.lang.ast.Node;
21  
22  /**
23   * This is a base class for RuleChainVisitor implementations which
24   * extracts interesting nodes from an AST, and lets each Rule visit
25   * the nodes it has expressed interest in.
26   */
27  public abstract class AbstractRuleChainVisitor implements RuleChainVisitor {
28      /**
29       * These are all the rules participating in the RuleChain, grouped by RuleSet.
30       */
31      protected Map<RuleSet, List<Rule>> ruleSetRules = new LinkedHashMap<>();
32  
33      /**
34       * This is a mapping from node names to nodes instances for the current AST.
35       */
36      protected Map<String, List<Node>> nodeNameToNodes;
37  
38      /**
39       * @see RuleChainVisitor#add(RuleSet, Rule)
40       */
41      public void add(RuleSet ruleSet, Rule rule) {
42      	
43  		if (!ruleSetRules.containsKey(ruleSet)) {
44  		    ruleSetRules.put(ruleSet, new ArrayList<Rule>());
45  		}
46  		ruleSetRules.get(ruleSet).add(rule);
47      }
48  
49      /**
50       * @see RuleChainVisitor#visitAll(List, RuleContext)
51       */
52      public void visitAll(List<Node> nodes, RuleContext ctx) {
53          initialize();
54          clear();
55  
56          // Perform a visitation of the AST to index nodes which need visiting by
57          // type
58          long start = System.nanoTime();
59          indexNodes(nodes, ctx);
60          long end = System.nanoTime();
61          Benchmarker.mark(Benchmark.RuleChainVisit, end - start, 1);
62  
63          // For each RuleSet, only if this source file applies
64          for (Map.Entry<RuleSet, List<Rule>> entry : ruleSetRules.entrySet()) {
65          	RuleSet ruleSet = entry.getKey();
66              if (!ruleSet.applies(ctx.getSourceCodeFile())) {
67  	        	continue;
68  	            }
69  
70              // For each rule, allow it to visit the nodes it desires
71              start = System.nanoTime();
72              for (Rule rule: entry.getValue()) {
73                  int visits = 0;
74  	        	if (!RuleSet.applies(rule, ctx.getLanguageVersion())) {
75  	        	    continue;
76  	        	}
77                  final List<String> nodeNames = rule.getRuleChainVisits();
78                  for (int j = 0; j < nodeNames.size(); j++) {
79                      List<Node> ns = nodeNameToNodes.get(nodeNames.get(j));
80                      for (Node node: ns) {
81                          // Visit with underlying Rule, not the RuleReference
82                          while (rule instanceof RuleReference) {
83                              rule = ((RuleReference)rule).getRule();
84                          }
85                          visit(rule, node, ctx);
86                      }
87                      visits += ns.size();
88                  }
89                  end = System.nanoTime();
90                  Benchmarker.mark(Benchmark.RuleChainRule, rule.getName(), end - start, visits);
91                  start = end;
92              }
93          }
94      }
95  
96      /**
97       * Visit the given rule to the given node.
98       */
99      protected abstract void visit(Rule rule, Node node, RuleContext ctx);
100 
101     /**
102      * Index all nodes for visitation by rules.
103      */
104     protected abstract void indexNodes(List<Node> nodes, RuleContext ctx);
105 
106     /**
107      * Index a single node for visitation by rules.
108      */
109     protected void indexNode(Node node) {
110         List<Node> nodes = nodeNameToNodes.get(node.toString());
111         if (nodes != null) {
112             nodes.add(node);
113         }
114     }
115 
116     /**
117      * Initialize the RuleChainVisitor to be ready to perform visitations. This
118      * method should not be called until it is known that all Rules participating
119      * in the RuleChain are ready to be initialized themselves.  Some rules
120      * may require full initialization to determine if they will participate in
121      * the RuleChain, so this has been delayed as long as possible to ensure
122      * that manipulation of the Rules is no longer occurring.
123      */
124     protected void initialize() {
125         if (nodeNameToNodes != null) {
126             return;
127         }
128 
129         // Determine all node types that need visiting
130         Set<String> visitedNodes = new HashSet<>();
131         for (Iterator<Map.Entry<RuleSet, List<Rule>>> entryIterator = ruleSetRules.entrySet().iterator(); entryIterator.hasNext();) {
132             Map.Entry<RuleSet, List<Rule>> entry = entryIterator.next();
133             for (Iterator<Rule> ruleIterator = entry.getValue().iterator(); ruleIterator.hasNext();) {
134                 Rule rule = ruleIterator.next();
135                 if (rule.usesRuleChain()) {
136                     visitedNodes.addAll(rule.getRuleChainVisits());
137                 }
138                 else {
139                     // Drop rules which do not participate in the rule chain.
140                     ruleIterator.remove();
141                 }
142             }
143             // Drop RuleSets in which all Rules have been dropped.
144             if (entry.getValue().isEmpty()) {
145         	entryIterator.remove();
146             }
147         }
148 
149         // Setup the data structure to manage mapping node names to node
150         // instances.  We intend to reuse this data structure between
151         // visits to different ASTs.
152         nodeNameToNodes = new HashMap<>();
153         for (String s: visitedNodes) {
154             List<Node> nodes = new ArrayList<>(100);
155             nodeNameToNodes.put(s, nodes);
156         }
157     }
158 
159     /**
160      * Clears the internal data structure used to manage the nodes visited
161      * between visiting different ASTs.
162      */
163     protected void clear() {
164         for (List<Node> l: nodeNameToNodes.values()) {
165             l.clear();
166         }
167     }
168 }