1
2
3
4 package net.sourceforge.pmd.lang.rule.xpath;
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.Map.Entry;
12 import java.util.Stack;
13 import java.util.logging.Level;
14 import java.util.logging.Logger;
15
16 import net.sourceforge.pmd.PropertyDescriptor;
17 import net.sourceforge.pmd.RuleContext;
18 import net.sourceforge.pmd.lang.ast.Node;
19
20 import org.jaxen.BaseXPath;
21 import org.jaxen.JaxenException;
22 import org.jaxen.Navigator;
23 import org.jaxen.SimpleVariableContext;
24 import org.jaxen.XPath;
25 import org.jaxen.expr.AllNodeStep;
26 import org.jaxen.expr.DefaultXPathFactory;
27 import org.jaxen.expr.Expr;
28 import org.jaxen.expr.LocationPath;
29 import org.jaxen.expr.NameStep;
30 import org.jaxen.expr.Predicate;
31 import org.jaxen.expr.Step;
32 import org.jaxen.expr.UnionExpr;
33 import org.jaxen.expr.XPathFactory;
34 import org.jaxen.saxpath.Axis;
35
36
37
38
39 public class JaxenXPathRuleQuery extends AbstractXPathRuleQuery {
40
41 private static final Logger LOG = Logger.getLogger(JaxenXPathRuleQuery.class.getName());
42
43 private static enum InitializationStatus {
44 NONE, PARTIAL, FULL
45 };
46
47
48 private InitializationStatus initializationStatus = InitializationStatus.NONE;
49 private Map<String, List<XPath>> nodeNameToXPaths;
50
51 private static final String AST_ROOT = "_AST_ROOT_";
52
53
54
55
56 @Override
57 public boolean isSupportedVersion(String version) {
58 return XPATH_1_0.equals(version);
59 }
60
61
62
63
64 @Override
65 @SuppressWarnings("unchecked")
66 public List<Node> evaluate(Node node, RuleContext data) {
67 List<Node> results = new ArrayList<Node>();
68 try {
69 initializeXPathExpression(data.getLanguageVersion().getLanguageVersionHandler().getXPathHandler()
70 .getNavigator());
71 List<XPath> xpaths = nodeNameToXPaths.get(node.toString());
72 if (xpaths == null) {
73 xpaths = nodeNameToXPaths.get(AST_ROOT);
74 }
75 for (XPath xpath : xpaths) {
76 List<Node> nodes = xpath.selectNodes(node);
77 results.addAll(nodes);
78 }
79 } catch (JaxenException ex) {
80 throw new RuntimeException(ex);
81 }
82 return results;
83 }
84
85
86
87
88 @Override
89 public List<String> getRuleChainVisits() {
90 try {
91
92 initializeXPathExpression(null);
93 return super.getRuleChainVisits();
94 } catch (JaxenException ex) {
95 throw new RuntimeException(ex);
96 }
97 }
98
99 @SuppressWarnings("unchecked")
100 private void initializeXPathExpression(Navigator navigator) throws JaxenException {
101 if (initializationStatus == InitializationStatus.FULL) {
102 return;
103 } else if (initializationStatus == InitializationStatus.PARTIAL && navigator == null) {
104 if (LOG.isLoggable(Level.SEVERE)) {
105 LOG.severe("XPathRule is not initialized because no navigator was provided. "
106 + "Please make sure to implement getXPathHandler in the handler of the language. "
107 + "See also AbstractLanguageVersionHandler.");
108 }
109 return;
110 }
111
112
113
114
115
116
117
118 nodeNameToXPaths = new HashMap<String, List<XPath>>();
119
120 BaseXPath originalXPath = createXPath(xpath, navigator);
121 indexXPath(originalXPath, AST_ROOT);
122
123 boolean useRuleChain = true;
124 Stack<Expr> pending = new Stack<Expr>();
125 pending.push(originalXPath.getRootExpr());
126 while (!pending.isEmpty()) {
127 Expr node = pending.pop();
128
129
130 boolean valid = false;
131
132
133 if (node instanceof LocationPath) {
134 LocationPath locationPath = (LocationPath) node;
135 if (locationPath.isAbsolute()) {
136
137 List<Step> steps = locationPath.getSteps();
138 if (steps.size() >= 2) {
139 Step step1 = steps.get(0);
140 Step step2 = steps.get(1);
141
142 if (step1 instanceof AllNodeStep && ((AllNodeStep) step1).getAxis() == Axis.DESCENDANT_OR_SELF) {
143
144 if (step2 instanceof NameStep && ((NameStep) step2).getAxis() == Axis.CHILD) {
145
146 XPathFactory xpathFactory = new DefaultXPathFactory();
147
148
149 LocationPath relativeLocationPath = xpathFactory.createRelativeLocationPath();
150
151 Step allNodeStep = xpathFactory.createAllNodeStep(Axis.SELF);
152
153 for (Iterator<Predicate> i = step2.getPredicates().iterator(); i.hasNext();) {
154 allNodeStep.addPredicate(i.next());
155 }
156 relativeLocationPath.addStep(allNodeStep);
157
158
159 for (int i = 2; i < steps.size(); i++) {
160 relativeLocationPath.addStep(steps.get(i));
161 }
162
163 BaseXPath xpath = createXPath(relativeLocationPath.getText(), navigator);
164 indexXPath(xpath, ((NameStep) step2).getLocalName());
165 valid = true;
166 }
167 }
168 }
169 }
170 } else if (node instanceof UnionExpr) {
171 UnionExpr unionExpr = (UnionExpr) node;
172 pending.push(unionExpr.getLHS());
173 pending.push(unionExpr.getRHS());
174 valid = true;
175 }
176 if (!valid) {
177 useRuleChain = false;
178 break;
179 }
180 }
181
182 if (useRuleChain) {
183
184 super.ruleChainVisits.addAll(nodeNameToXPaths.keySet());
185 } else {
186
187 nodeNameToXPaths.clear();
188 indexXPath(originalXPath, AST_ROOT);
189 if (LOG.isLoggable(Level.FINE)) {
190 LOG.log(Level.FINE, "Unable to use RuleChain for for XPath: " + xpath);
191 }
192 }
193
194 if (navigator == null) {
195 this.initializationStatus = InitializationStatus.PARTIAL;
196
197 nodeNameToXPaths = null;
198 } else {
199 this.initializationStatus = InitializationStatus.FULL;
200 }
201
202 }
203
204 private void indexXPath(XPath xpath, String nodeName) {
205 List<XPath> xpaths = nodeNameToXPaths.get(nodeName);
206 if (xpaths == null) {
207 xpaths = new ArrayList<XPath>();
208 nodeNameToXPaths.put(nodeName, xpaths);
209 }
210 xpaths.add(xpath);
211 }
212
213 private BaseXPath createXPath(String xpathQueryString, Navigator navigator) throws JaxenException {
214
215 BaseXPath xpath = new BaseXPath(xpathQueryString, navigator);
216 if (properties.size() > 1) {
217 SimpleVariableContext vc = new SimpleVariableContext();
218 for (Entry<PropertyDescriptor<?>, Object> e : properties.entrySet()) {
219 String propName = e.getKey().name();
220 if (!"xpath".equals(propName)) {
221 Object value = e.getValue();
222 vc.setVariableValue(propName, value != null ? value.toString() : null);
223 }
224 }
225 xpath.setVariableContext(vc);
226 }
227 return xpath;
228 }
229 }