1 /**
2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3 */
4 package net.sourceforge.pmd.rules;
5
6 import java.util.ArrayList;
7 import java.util.HashMap;
8 import java.util.Iterator;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Stack;
12 import java.util.Map.Entry;
13
14 import net.sourceforge.pmd.AbstractJavaRule;
15 import net.sourceforge.pmd.RuleContext;
16 import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
17 import net.sourceforge.pmd.ast.Node;
18 import net.sourceforge.pmd.ast.SimpleNode;
19 import net.sourceforge.pmd.jaxen.DocumentNavigator;
20 import net.sourceforge.pmd.jaxen.MatchesFunction;
21 import net.sourceforge.pmd.jaxen.TypeOfFunction;
22
23 import org.jaxen.BaseXPath;
24 import org.jaxen.JaxenException;
25 import org.jaxen.SimpleVariableContext;
26 import org.jaxen.XPath;
27 import org.jaxen.expr.AllNodeStep;
28 import org.jaxen.expr.DefaultXPathFactory;
29 import org.jaxen.expr.Expr;
30 import org.jaxen.expr.LocationPath;
31 import org.jaxen.expr.NameStep;
32 import org.jaxen.expr.Predicate;
33 import org.jaxen.expr.Step;
34 import org.jaxen.expr.UnionExpr;
35 import org.jaxen.expr.XPathFactory;
36 import org.jaxen.saxpath.Axis;
37
38 /**
39 * Rule that tries to match an XPath expression against a DOM
40 * view of the AST of a "compilation unit".
41 * <p/>
42 * This rule needs a property "xpath".
43 */
44 public class XPathRule extends AbstractJavaRule {
45
46
47 private Map<String, List<XPath>> nodeNameToXPaths;
48 private boolean regexpFunctionRegistered;
49 private boolean typeofFunctionRegistered;
50
51 private static final String AST_ROOT = "_AST_ROOT_";
52
53 /**
54 * Evaluate the AST with compilationUnit as root-node, against
55 * the XPath expression found as property with name "xpath".
56 * All matches are reported as violations.
57 *
58 * @param compilationUnit the Node that is the root of the AST to be checked
59 * @param data
60 */
61 public void evaluate(Node compilationUnit, RuleContext data) {
62 try {
63 initializeXPathExpression();
64 List<XPath> xpaths = nodeNameToXPaths.get(compilationUnit.toString());
65 if (xpaths == null) {
66 xpaths = nodeNameToXPaths.get(AST_ROOT);
67 }
68 for (XPath xpath: xpaths) {
69 List results = xpath.selectNodes(compilationUnit);
70 for (Iterator j = results.iterator(); j.hasNext();) {
71 SimpleNode n = (SimpleNode) j.next();
72
73 if (n instanceof ASTVariableDeclaratorId && getBooleanProperty("pluginname")) {
74 addViolation(data, n, n.getImage());
75 } else {
76 addViolation(data, n, getMessage());
77 }
78 }
79 }
80 } catch (JaxenException ex) {
81 throw new RuntimeException(ex);
82 }
83 }
84
85 public List<String> getRuleChainVisits() {
86 try {
87 initializeXPathExpression();
88 return super.getRuleChainVisits();
89 } catch (JaxenException ex) {
90 throw new RuntimeException(ex);
91 }
92 }
93
94 private void initializeXPathExpression() throws JaxenException {
95 if (nodeNameToXPaths != null) {
96 return;
97 }
98
99 if (!regexpFunctionRegistered) {
100 MatchesFunction.registerSelfInSimpleContext();
101 regexpFunctionRegistered = true;
102 }
103
104 if (!typeofFunctionRegistered) {
105 TypeOfFunction.registerSelfInSimpleContext();
106 typeofFunctionRegistered = true;
107 }
108
109
110
111
112
113
114
115 nodeNameToXPaths = new HashMap<String, List<XPath>>();
116
117 BaseXPath originalXPath = createXPath(getStringProperty("xpath"));
118 indexXPath(originalXPath, AST_ROOT);
119
120 boolean useRuleChain = true;
121 Stack<Expr> pending = new Stack<Expr>();
122 pending.push(originalXPath.getRootExpr());
123 while (!pending.isEmpty()) {
124 Expr node = pending.pop();
125
126
127 boolean valid = false;
128
129
130 if (node instanceof LocationPath) {
131 LocationPath locationPath = (LocationPath)node;
132 if (locationPath.isAbsolute()) {
133
134 List steps = locationPath.getSteps();
135 if (steps.size() >= 2) {
136 Step step1 = (Step)steps.get(0);
137 Step step2 = (Step)steps.get(1);
138
139 if (step1 instanceof AllNodeStep && ((AllNodeStep)step1).getAxis() == Axis.DESCENDANT_OR_SELF) {
140
141 if (step2 instanceof NameStep && ((NameStep)step2).getAxis() == Axis.CHILD) {
142
143 XPathFactory xpathFactory = new DefaultXPathFactory();
144
145
146 LocationPath relativeLocationPath = xpathFactory.createRelativeLocationPath();
147
148 Step allNodeStep = xpathFactory.createAllNodeStep(Axis.SELF);
149
150 for (Iterator i = step2.getPredicates().iterator(); i.hasNext();) {
151 allNodeStep.addPredicate((Predicate)i.next());
152 }
153 relativeLocationPath.addStep(allNodeStep);
154
155
156 for (int i = 2; i < steps.size(); i++) {
157 relativeLocationPath.addStep((Step)steps.get(i));
158 }
159
160 BaseXPath xpath = createXPath(relativeLocationPath.getText());
161 indexXPath(xpath, ((NameStep)step2).getLocalName());
162 valid = true;
163 }
164 }
165 }
166 }
167 } else if (node instanceof UnionExpr) {
168 UnionExpr unionExpr = (UnionExpr)node;
169 pending.push(unionExpr.getLHS());
170 pending.push(unionExpr.getRHS());
171 valid = true;
172 }
173 if (!valid) {
174 useRuleChain = false;
175 break;
176 }
177 }
178
179 if (useRuleChain) {
180
181 for (String s: nodeNameToXPaths.keySet()) {
182 addRuleChainVisit(s);
183 }
184 } else {
185 nodeNameToXPaths.clear();
186 indexXPath(originalXPath, AST_ROOT);
187
188 }
189 }
190
191 private void indexXPath(XPath xpath, String nodeName) {
192 List<XPath> xpaths = nodeNameToXPaths.get(nodeName);
193 if (xpaths == null) {
194 xpaths = new ArrayList<XPath>();
195 nodeNameToXPaths.put(nodeName, xpaths);
196 }
197 xpaths.add(xpath);
198 }
199
200 private BaseXPath createXPath(String xpathQueryString) throws JaxenException {
201
202
203
204
205
206
207 xpathQueryString = xpathQueryString.replaceAll("\"\"\"", "'\"'");
208
209 BaseXPath xpath = new BaseXPath(xpathQueryString, new DocumentNavigator());
210 if (getProperties().size() > 1) {
211 SimpleVariableContext vc = new SimpleVariableContext();
212 for (Entry e: getProperties().entrySet()) {
213 if (!"xpath".equals(e.getKey())) {
214 vc.setVariableValue((String) e.getKey(), e.getValue());
215 }
216 }
217 xpath.setVariableContext(vc);
218 }
219 return xpath;
220 }
221
222 /**
223 * Apply the rule to all compilation units.
224 */
225 public void apply(List astCompilationUnits, RuleContext ctx) {
226 for (Iterator i = astCompilationUnits.iterator(); i.hasNext();) {
227 evaluate((Node) i.next(), ctx);
228 }
229 }
230 }