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 }
|