Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
XPathWrapper |
|
| 1.68;1.68 | ||||
XPathWrapper$1 |
|
| 1.68;1.68 | ||||
XPathWrapper$MyVariableResolver |
|
| 1.68;1.68 |
1 | // Copyright 2008-2014 severally by the contributors | |
2 | // | |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | // you may not use this file except in compliance with the License. | |
5 | // You may obtain a copy of the License at | |
6 | // | |
7 | // http://www.apache.org/licenses/LICENSE-2.0 | |
8 | // | |
9 | // Unless required by applicable law or agreed to in writing, software | |
10 | // distributed under the License is distributed on an "AS IS" BASIS, | |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | // See the License for the specific language governing permissions and | |
13 | // limitations under the License. | |
14 | ||
15 | package net.sf.practicalxml.xpath; | |
16 | ||
17 | import java.util.ArrayList; | |
18 | import java.util.HashMap; | |
19 | import java.util.List; | |
20 | import java.util.Map; | |
21 | import javax.xml.namespace.QName; | |
22 | import javax.xml.xpath.XPath; | |
23 | import javax.xml.xpath.XPathConstants; | |
24 | import javax.xml.xpath.XPathExpression; | |
25 | import javax.xml.xpath.XPathExpressionException; | |
26 | import javax.xml.xpath.XPathFactory; | |
27 | import javax.xml.xpath.XPathFunction; | |
28 | import javax.xml.xpath.XPathVariableResolver; | |
29 | ||
30 | import org.w3c.dom.Element; | |
31 | import org.w3c.dom.Node; | |
32 | import org.w3c.dom.NodeList; | |
33 | ||
34 | import net.sf.practicalxml.DomUtil; | |
35 | import net.sf.practicalxml.XmlException; | |
36 | ||
37 | ||
38 | ||
39 | /** | |
40 | * This class simplifies the use of XPath expressions, hiding the factory and | |
41 | * return types, and providing a simple builder-style interface for adding | |
42 | * resolvers. It also maintains the expression in a compiled form, improving | |
43 | * reuse performance. | |
44 | * <p> | |
45 | * <strong>Warning:</strong> | |
46 | * <code>XPathWrapper</code>, like the underlying JDK <code>XPath</code> object, | |
47 | * is not thread-safe. However, separate instances may be created and evaluated | |
48 | * on separate threads without explicit synchronization. To make this work, we | |
49 | * create a new <code>XPathFactory</code> instance for each wrapper (with the | |
50 | * Sun JDK, this adds little overhead), synchronizing <code>newInstance()</code> | |
51 | * on <code>XPathWrapper.class</code>. | |
52 | */ | |
53 | 37 | public class XPathWrapper |
54 | implements Cloneable | |
55 | { | |
56 | private final String _expr; | |
57 | 201 | private final NamespaceResolver _nsResolver = new NamespaceResolver(); |
58 | 201 | private Map<QName,Object> _variables = new HashMap<QName,Object>(); |
59 | 201 | private FunctionResolver _functions = new FunctionResolver(); |
60 | ||
61 | private XPathExpression _compiled; | |
62 | ||
63 | ||
64 | /** | |
65 | * Creates a new instance, which may then be customized with various | |
66 | * resolvers, and used to evaluate expressions. | |
67 | */ | |
68 | public XPathWrapper(String expr) | |
69 | 201 | { |
70 | 201 | _expr = expr; |
71 | 201 | } |
72 | ||
73 | ||
74 | //---------------------------------------------------------------------------- | |
75 | // Public methods | |
76 | //---------------------------------------------------------------------------- | |
77 | ||
78 | /** | |
79 | * Adds a namespace binding to this expression. All bindings must be | |
80 | * added prior to the first call to <code>evaluate()</code>. If you add | |
81 | * multiple bindings with the same namespace, only the last is retained. | |
82 | * | |
83 | * @param prefix The prefix used to reference this namespace in the | |
84 | * XPath expression. Note that this does <em>not</em> | |
85 | * need to be the same prefix used by the document. | |
86 | * @param nsURI The namespace URI to associate with this prefix. | |
87 | * | |
88 | * @return The wrapper, so that calls may be chained. | |
89 | */ | |
90 | public XPathWrapper bindNamespace(String prefix, String nsURI) | |
91 | { | |
92 | 54 | _nsResolver.addNamespace(prefix, nsURI); |
93 | 54 | return this; |
94 | } | |
95 | ||
96 | ||
97 | /** | |
98 | * Sets the default namespace binding: this will be applied to all | |
99 | * expressions that do not explicitly specify a prefix (although | |
100 | * they must use the colon separating the non-existent prefix from | |
101 | * the element name). | |
102 | * | |
103 | * @param nsURI The default namespace for this document. | |
104 | * | |
105 | * @return The wrapper, so that calls may be chained. | |
106 | * | |
107 | * @deprecated In practice, this method isn't particularly useful: | |
108 | * if you're going to specify ":" on a term, you might | |
109 | * as well specify a single-character prefix. It won't | |
110 | * be removed, but a future implementation might parse | |
111 | * an expression sans colons, and insert references. | |
112 | ||
113 | */ | |
114 | @Deprecated | |
115 | public XPathWrapper bindDefaultNamespace(String nsURI) | |
116 | { | |
117 | 3 | _nsResolver.setDefaultNamespace(nsURI); |
118 | 3 | return this; |
119 | } | |
120 | ||
121 | ||
122 | /** | |
123 | * Binds a value to a variable, replacing any previous value for that | |
124 | * variable. Unlike other configuration methods, this may be called | |
125 | * after calling <code>evaluate()</code>; the new values will be used | |
126 | * for subsequent evaluations. | |
127 | * | |
128 | * @param name The name of the variable; this is turned into a | |
129 | * <code>QName</code> without namespace. | |
130 | * @param value The value of the variable; the XPath evaluator must | |
131 | * be able to convert this value into a type usable in | |
132 | * an XPath expression. | |
133 | * | |
134 | * @return The wrapper, so that calls may be chained. | |
135 | */ | |
136 | public XPathWrapper bindVariable(String name, Object value) | |
137 | { | |
138 | 3 | return bindVariable(new QName(name), value); |
139 | } | |
140 | ||
141 | ||
142 | /** | |
143 | * Binds a value to a variable, replacing any previous value for that | |
144 | * variable. Unlike other configuration methods, this may be called | |
145 | * after calling <code>evaluate()</code>; the new values will be used | |
146 | * for subsequent evaluations. | |
147 | * | |
148 | * @param name The fully-qualified name of the variable. | |
149 | * @param value The value of the variable; the XPath evaluator must | |
150 | * be able to convert this value into a type usable in | |
151 | * an XPath expression. | |
152 | * | |
153 | * @return The wrapper, so that calls may be chained. | |
154 | */ | |
155 | public XPathWrapper bindVariable(QName name, Object value) | |
156 | { | |
157 | 18 | _variables.put(name, value); |
158 | 18 | return this; |
159 | } | |
160 | ||
161 | ||
162 | /** | |
163 | * Binds a self-describing function to this expression. Subsequent calls | |
164 | * with the same name/arity will silently replace the binding. | |
165 | * <p> | |
166 | * Per the JDK documentation, user-defined functions must occupy their | |
167 | * own namespace. You must bind a namespace for this function, and use | |
168 | * the bound prefix to reference it in your expression. Alternatively, | |
169 | * you can call the variant of <code>bindFunction()</code> that binds | |
170 | * a prefix to the function's namespace. | |
171 | * | |
172 | * @param func The function. | |
173 | * | |
174 | * @return The wrapper, so that calls may be chained. | |
175 | */ | |
176 | public XPathWrapper bindFunction(FunctionResolver.SelfDescribingFunction func) | |
177 | { | |
178 | 1 | _functions.addFunction(func); |
179 | 1 | return this; |
180 | } | |
181 | ||
182 | ||
183 | /** | |
184 | * Binds a self-describing function to this expression, along with the | |
185 | * prefix used to access that function. This also establishes a namespace | |
186 | * binding for that prefix. | |
187 | * <p> | |
188 | * Per the JDK documentation, user-defined functions must occupy their | |
189 | * own namespace. This method will retrieve the namespace from the | |
190 | * function, and bind it to the passed prefix. | |
191 | * | |
192 | * @param func The function. | |
193 | * @param prefix The prefix to bind to this function's namespace. | |
194 | * | |
195 | * @return The wrapper, so that calls may be chained. | |
196 | */ | |
197 | public XPathWrapper bindFunction(FunctionResolver.SelfDescribingFunction func, String prefix) | |
198 | { | |
199 | 19 | _functions.addFunction(func); |
200 | 19 | return bindNamespace(prefix, func.getNamespaceUri()); |
201 | } | |
202 | ||
203 | ||
204 | /** | |
205 | * Binds a standard <code>XPathFunction</code> to this expression, | |
206 | * handling any number of arguments. Subsequent calls to this method | |
207 | * with the same name will silently replace the binding. | |
208 | * <p> | |
209 | * Per the JDK documentation, user-defined functions must occupy their | |
210 | * own namespace. If the qualified name that you pass to this method | |
211 | * includes a prefix, the associated namespace will be bound to that | |
212 | * prefix. If not, you must bind the namespace explicitly. In either | |
213 | * case, you must refer to the function in your expression using a | |
214 | * bound prefix. | |
215 | * | |
216 | * @param name The qualified name for this function. Must contain | |
217 | * a name and namespace, may contain a prefix. | |
218 | * @param func The function to bind to this name. | |
219 | * | |
220 | * @return The wrapper, so that calls may be chained. | |
221 | */ | |
222 | public XPathWrapper bindFunction(QName name, XPathFunction func) | |
223 | { | |
224 | 3 | return bindFunction(name, func, 0, Integer.MAX_VALUE); |
225 | } | |
226 | ||
227 | ||
228 | /** | |
229 | * Binds a standard <code>XPathFunction</code> to this expression, | |
230 | * handling a specific number of arguments. Subsequent calls to this | |
231 | * method with the same name and arity will silently replace the binding. | |
232 | * <p> | |
233 | * Per the JDK documentation, user-defined functions must occupy their | |
234 | * own namespace. If the qualified name that you pass to this method | |
235 | * includes a prefix, the associated namespace will be bound to that | |
236 | * prefix. If not, you must bind the namespace explicitly. In either | |
237 | * case, you must refer to the function in your expression using a | |
238 | * bound prefix. | |
239 | * | |
240 | * @param name The qualified name for this function. Must contain | |
241 | * a name and namespace, may contain a prefix. | |
242 | * @param func The function to bind to this name. | |
243 | * @param arity The number of arguments accepted by this function. | |
244 | * | |
245 | * @return The wrapper, so that calls may be chained. | |
246 | */ | |
247 | public XPathWrapper bindFunction(QName name, XPathFunction func, int arity) | |
248 | { | |
249 | 2 | return bindFunction(name, func, arity, arity); |
250 | } | |
251 | ||
252 | ||
253 | /** | |
254 | * Binds a standard <code>XPathFunction</code> to this expression, | |
255 | * handling a specific range of arguments. Subsequent calls to this | |
256 | * method with the same name and range of arguments will silently | |
257 | * replace the binding. | |
258 | * <p> | |
259 | * Per the JDK documentation, user-defined functions must occupy their | |
260 | * own namespace. If the qualified name that you pass to this method | |
261 | * includes a prefix, the associated namespace will be bound to that | |
262 | * prefix. If not, you must bind the namespace explicitly. In either | |
263 | * case, you must refer to the function in your expression using a | |
264 | * bound prefix. | |
265 | * | |
266 | * @param name The qualified name for this function. Must contain | |
267 | * a name and namespace, may contain a prefix. | |
268 | * @param func The function to bind to this name. | |
269 | * @param minArity The minimum number of arguments accepted by this | |
270 | * function. | |
271 | * @param maxArity The maximum number of arguments accepted by this | |
272 | * function. | |
273 | * | |
274 | * @return The wrapper, so that calls may be chained. | |
275 | */ | |
276 | public XPathWrapper bindFunction(QName name, XPathFunction func, | |
277 | int minArity, int maxArity) | |
278 | { | |
279 | 5 | _functions.addFunction(func, name, minArity, maxArity); |
280 | 5 | if (!"".equals(name.getPrefix())) |
281 | 3 | bindNamespace(name.getPrefix(), name.getNamespaceURI()); |
282 | 5 | return this; |
283 | } | |
284 | ||
285 | ||
286 | /** | |
287 | * Attaches a pre-build {@link FunctionResolver} to this wrapper, | |
288 | * replacing the existing resolver (and removing all bound functions). | |
289 | * This method is intended for use by {@link XPathWrapperFactory}, but | |
290 | * is exposed for applications that wish to manage their own XPaths. | |
291 | * | |
292 | * @since 1.1.12 | |
293 | */ | |
294 | public XPathWrapper setFunctionResolver(FunctionResolver resolver) | |
295 | { | |
296 | 20 | _functions = resolver; |
297 | 20 | return this; |
298 | } | |
299 | ||
300 | ||
301 | /** | |
302 | * Applies this expression to the the specified node, converting the | |
303 | * resulting <code>NodeList</code> into a <code>java.util.List</code>. | |
304 | */ | |
305 | public List<Node> evaluate(Node context) | |
306 | { | |
307 | 79 | return DomUtil.asList( |
308 | evaluate(context, XPathConstants.NODESET, NodeList.class), | |
309 | Node.class); | |
310 | } | |
311 | ||
312 | ||
313 | /** | |
314 | * Applies this expression to the the specified node, converting the | |
315 | * resulting <code>NodeList</code> into a <code>java.util.List</code> | |
316 | * containing only nodes of the specified type. | |
317 | */ | |
318 | public <T> List<T> evaluate(Node context, Class<T> klass) | |
319 | { | |
320 | 3 | return DomUtil.filter( |
321 | evaluate(context, XPathConstants.NODESET, NodeList.class), | |
322 | klass); | |
323 | } | |
324 | ||
325 | ||
326 | /** | |
327 | * Applies this expression to the the specified node, and returning the | |
328 | * first <code>Element</code> from the resulting nodelist. Returns | |
329 | * <code>null</code> if there is no element in the list. | |
330 | * | |
331 | * @since 1.1.11 | |
332 | */ | |
333 | public Element evaluateAsElement(Node context) | |
334 | { | |
335 | 3 | NodeList nodelist = evaluate(context, XPathConstants.NODESET, NodeList.class); |
336 | 3 | int size = nodelist.getLength(); |
337 | 5 | for (int ii = 0 ; ii < size ; ii++) |
338 | { | |
339 | 4 | Node node = nodelist.item(ii); |
340 | 4 | if (node instanceof Element) |
341 | 2 | return (Element)node; |
342 | } | |
343 | 1 | return null; |
344 | } | |
345 | ||
346 | ||
347 | /** | |
348 | * Applies this expression to the specified node, requesting the | |
349 | * <code>STRING</code> return type. | |
350 | */ | |
351 | public String evaluateAsString(Node context) | |
352 | { | |
353 | 113 | return evaluate(context, XPathConstants.STRING, String.class); |
354 | } | |
355 | ||
356 | ||
357 | /** | |
358 | * Applies this expression to the specified node, requesting the | |
359 | * <code>NODESET</code> return type, and then retrieving the text | |
360 | * content for each returned node. | |
361 | * <p> | |
362 | * Note: this method calls <code>Node.getTextContent()</code> in | |
363 | * keeping with normal XPath behavior. If you want to get | |
364 | * only the immediate child text nodes, evaluate as a list | |
365 | * of elements and use <code>DomUtil.getText()</code>. | |
366 | * | |
367 | * @since 1.1.3 | |
368 | */ | |
369 | public List<String> evaluateAsStringList(Node context) | |
370 | { | |
371 | 1 | List<Node> nodes = evaluate(context); |
372 | 1 | List<String> result = new ArrayList<String>(nodes.size()); |
373 | 1 | for (Node node : nodes) |
374 | 2 | result.add(node.getTextContent()); |
375 | 1 | return result; |
376 | } | |
377 | ||
378 | ||
379 | /** | |
380 | * Applies this expression to the specified node, requesting the | |
381 | * <code>NUMBER</code> return type. | |
382 | */ | |
383 | public Number evaluateAsNumber(Node context) | |
384 | { | |
385 | 1 | return evaluate(context, XPathConstants.NUMBER, Number.class); |
386 | } | |
387 | ||
388 | ||
389 | /** | |
390 | * Applies this expression to the the specified node, requesting the | |
391 | * <code>BOOLEAN</code> return type. | |
392 | */ | |
393 | public Boolean evaluateAsBoolean(Node context) | |
394 | { | |
395 | 3 | return evaluate(context, XPathConstants.BOOLEAN, Boolean.class); |
396 | } | |
397 | ||
398 | ||
399 | //---------------------------------------------------------------------------- | |
400 | // Overrides of Object | |
401 | //---------------------------------------------------------------------------- | |
402 | ||
403 | /** | |
404 | * Two instances are considered equal if they have the same expression, | |
405 | * namespace mappings, variable mappings, and function mappings. Note | |
406 | * that instances with function mappings are all but guaranteed to be | |
407 | * not-equal, unless you override the <code>equals()</code> method on | |
408 | * the function implementation class. | |
409 | */ | |
410 | @Override | |
411 | public final boolean equals(Object obj) | |
412 | { | |
413 | 17 | if (obj instanceof XPathWrapper) |
414 | { | |
415 | 15 | XPathWrapper that = (XPathWrapper)obj; |
416 | 15 | return this._expr.equals(that._expr) |
417 | && this._nsResolver.equals(that._nsResolver) | |
418 | && this._variables.equals(that._variables) | |
419 | && this._functions.equals(that._functions); | |
420 | } | |
421 | 2 | return false; |
422 | } | |
423 | ||
424 | ||
425 | /** | |
426 | * Hash code is driven by the expression, ignoring differences between | |
427 | * namespaces, variables, and functions. | |
428 | */ | |
429 | @Override | |
430 | public int hashCode() | |
431 | { | |
432 | 18 | return _expr.hashCode(); |
433 | } | |
434 | ||
435 | ||
436 | /** | |
437 | * The string value is the expression. | |
438 | */ | |
439 | @Override | |
440 | public String toString() | |
441 | { | |
442 | 52 | return _expr; |
443 | } | |
444 | ||
445 | ||
446 | /** | |
447 | * Creates a <em>shallow</em> clone of this object, omitting the compiled | |
448 | * XPath expression. This method exists to support thread-local instances, | |
449 | * which will recompile the expression. | |
450 | */ | |
451 | @Override | |
452 | protected XPathWrapper clone() throws CloneNotSupportedException | |
453 | { | |
454 | 1 | XPathWrapper newInstance = (XPathWrapper)super.clone(); |
455 | 1 | newInstance._compiled = null; |
456 | 1 | return newInstance; |
457 | } | |
458 | ||
459 | ||
460 | //---------------------------------------------------------------------------- | |
461 | // Internals | |
462 | //---------------------------------------------------------------------------- | |
463 | ||
464 | /** | |
465 | * Compiles the expression, if it has not already been compiled. This is | |
466 | * called from the various <code>evaluate</code> methods, and ensures | |
467 | * that the caller has completely configured the wrapper prior to use. | |
468 | */ | |
469 | private void compileIfNeeded() | |
470 | { | |
471 | 202 | if (_compiled != null) |
472 | 17 | return; |
473 | ||
474 | try | |
475 | { | |
476 | 185 | XPathFactory fact = null; |
477 | 185 | synchronized (XPathFactory.class) |
478 | { | |
479 | 185 | fact = XPathFactory.newInstance(); |
480 | 185 | } |
481 | 185 | XPath xpath = fact.newXPath(); |
482 | 185 | xpath.setNamespaceContext(_nsResolver); |
483 | 185 | xpath.setXPathVariableResolver(new MyVariableResolver()); |
484 | 185 | xpath.setXPathFunctionResolver(_functions); |
485 | 185 | _compiled = xpath.compile(_expr); |
486 | } | |
487 | 1 | catch (XPathExpressionException ee) |
488 | { | |
489 | 1 | throw new XmlException("unable to compile: " + _expr, ee); |
490 | 184 | } |
491 | 184 | } |
492 | ||
493 | ||
494 | /** | |
495 | * The base evaluation method: compiles the XPath, applies it as instructed | |
496 | * by the caller, and casts the result (also as instructed). | |
497 | */ | |
498 | private <T> T evaluate(Node context, QName returnType, Class<T> castTo) | |
499 | { | |
500 | 202 | compileIfNeeded(); |
501 | try | |
502 | { | |
503 | 201 | return castTo.cast(_compiled.evaluate(context, returnType)); |
504 | } | |
505 | 2 | catch (Exception ee) |
506 | { | |
507 | 2 | throw new XmlException("unable to evaluate: " + _expr, ee); |
508 | } | |
509 | } | |
510 | ||
511 | ||
512 | /** | |
513 | * Resolver for variable references. | |
514 | */ | |
515 | 370 | private class MyVariableResolver |
516 | implements XPathVariableResolver | |
517 | { | |
518 | public Object resolveVariable(QName name) | |
519 | { | |
520 | 37 | return _variables.get(name); |
521 | } | |
522 | } | |
523 | } |