Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
XPathWrapperFactory |
|
| 1.8571428571428572;1.857 | ||||
XPathWrapperFactory$1 |
|
| 1.8571428571428572;1.857 | ||||
XPathWrapperFactory$2 |
|
| 1.8571428571428572;1.857 | ||||
XPathWrapperFactory$CacheType |
|
| 1.8571428571428572;1.857 |
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.HashMap; | |
18 | import java.util.Map; | |
19 | ||
20 | import javax.xml.namespace.QName; | |
21 | import javax.xml.xpath.XPathFunction; | |
22 | ||
23 | ||
24 | /** | |
25 | * A factory for producing {@link XPathWrapper} instances with the same | |
26 | * configuration (namespace bindings and functions), reducing the amount | |
27 | * of code needed when applying multiple expressions to the same complex | |
28 | * XML. | |
29 | * <p> | |
30 | * As with <code>XPathWrapper</code> itself, this object is intended to | |
31 | * be configured using the builder pattern: all configuration methods | |
32 | * return the factory instance. Binding methods are the same as | |
33 | * <code>XPathWrapper</code>, excepting the deprecated | |
34 | * <code>bindDefaultNamespace()</code>. | |
35 | * <p> | |
36 | * As a potential performance improvement, for XPath expressions that are | |
37 | * repeatedly executed, you can create an optional cache for created wrapper | |
38 | * instances. Note that changes to the factory configuration will not affect | |
39 | * cached wrapper instances. <em>Note:</em> At the present time, there is no | |
40 | * way to clear the cache. | |
41 | * <p> | |
42 | * The {@link #newXPath} method is reentrant, and may be invoked from multiple | |
43 | * threads concurrently. However, this class is not generally thread-safe: you | |
44 | * cannot configure in one thread and create wrapper instances in another. | |
45 | * | |
46 | * @since 1.1.12 | |
47 | */ | |
48 | public class XPathWrapperFactory | |
49 | implements Cloneable | |
50 | { | |
51 | /** | |
52 | * Defines the various options for caching. | |
53 | */ | |
54 | 5 | public enum CacheType |
55 | { | |
56 | /** | |
57 | * No cache. Each call to to <code>newXPath()</code> produces a new | |
58 | * instance. | |
59 | */ | |
60 | 1 | NONE, |
61 | ||
62 | /** | |
63 | * A simple cache, keyed by XPath expression. This should <em>not</em> | |
64 | * be used in a multi-threaded application. | |
65 | */ | |
66 | 1 | SIMPLE, |
67 | ||
68 | /** | |
69 | * A thread-local cache, keyed by XPath expression. | |
70 | */ | |
71 | 1 | THREADSAFE |
72 | } | |
73 | ||
74 | ||
75 | //---------------------------------------------------------------------------- | |
76 | // Instance Variables and Constructors | |
77 | //---------------------------------------------------------------------------- | |
78 | ||
79 | // only one of these will be not-null, and only if we use that cache type | |
80 | private Map<String,XPathWrapper> _simpleCache; | |
81 | private ThreadLocal<Map<String,XPathWrapper>> _threadsafeCache; | |
82 | ||
83 | // these will be shared between paths | |
84 | 8 | private Map<String,String> _namespaces = new HashMap<String,String>(); |
85 | 8 | private Map<QName,Object> _variables = new HashMap<QName,Object>(); |
86 | ||
87 | // this will be copied for each path | |
88 | 8 | private FunctionResolver _functions = new FunctionResolver(); |
89 | ||
90 | ||
91 | /** | |
92 | * Creates a factory that will always return new <code>XPathWrapper</code> | |
93 | * instances. | |
94 | */ | |
95 | public XPathWrapperFactory() | |
96 | { | |
97 | 4 | this(CacheType.NONE); |
98 | 4 | } |
99 | ||
100 | ||
101 | /** | |
102 | * Creates a factory that will optionally store <code>XPathWrapper</code> | |
103 | * instances in a cache. | |
104 | * | |
105 | * @param cacheType One of the {@link XPathWrapperFactory.CacheType} values. | |
106 | */ | |
107 | public XPathWrapperFactory(CacheType cacheType) | |
108 | 8 | { |
109 | 8 | switch (cacheType) |
110 | { | |
111 | case SIMPLE : | |
112 | 2 | _simpleCache = new HashMap<String,XPathWrapper>(); |
113 | 2 | break; |
114 | case THREADSAFE : | |
115 | 1 | _threadsafeCache = new ThreadLocal<Map<String,XPathWrapper>>() |
116 | 3 | { |
117 | @Override | |
118 | protected Map<String,XPathWrapper> initialValue() | |
119 | { | |
120 | 2 | return new HashMap<String,XPathWrapper>(); |
121 | } | |
122 | }; | |
123 | 1 | break; |
124 | default : | |
125 | // no cache, don't do anything | |
126 | } | |
127 | 8 | } |
128 | ||
129 | ||
130 | //---------------------------------------------------------------------------- | |
131 | // Public methods | |
132 | //---------------------------------------------------------------------------- | |
133 | ||
134 | /** | |
135 | * Adds a namespace binding, which will be used for subsequent wrappers | |
136 | * created from this factory. If you add multiple bindings with the same | |
137 | * namespace, only the last is retained. | |
138 | * | |
139 | * @param prefix The prefix used to reference this namespace in the | |
140 | * XPath expression. Note that this does <em>not</em> | |
141 | * need to be the same prefix used by the document. | |
142 | * @param nsURI The namespace URI to associate with this prefix. | |
143 | * | |
144 | * @return The wrapper, so that calls may be chained. | |
145 | */ | |
146 | public XPathWrapperFactory bindNamespace(String prefix, String nsURI) | |
147 | { | |
148 | 5 | _namespaces.put(prefix, nsURI); |
149 | 5 | return this; |
150 | } | |
151 | ||
152 | ||
153 | /** | |
154 | * Binds a value to a variable, replacing any previous value for that | |
155 | * variable. Unlike other configuration methods, this may be called | |
156 | * after calling <code>evaluate()</code>; the new values will be used | |
157 | * for subsequent evaluations. | |
158 | * | |
159 | * @param name The name of the variable; this is turned into a | |
160 | * <code>QName</code> without namespace. | |
161 | * @param value The value of the variable; the XPath evaluator must | |
162 | * be able to convert this value into a type usable in | |
163 | * an XPath expression. | |
164 | * | |
165 | * @return The wrapper, so that calls may be chained. | |
166 | */ | |
167 | public XPathWrapperFactory bindVariable(String name, Object value) | |
168 | { | |
169 | 5 | return bindVariable(new QName(name), value); |
170 | } | |
171 | ||
172 | ||
173 | /** | |
174 | * Binds a value to a variable, replacing any previous value for that | |
175 | * variable. Unlike other configuration methods, this may be called | |
176 | * after calling <code>evaluate()</code>; the new values will be used | |
177 | * for subsequent evaluations. | |
178 | * | |
179 | * @param name The fully-qualified name of the variable. | |
180 | * @param value The value of the variable; the XPath evaluator must | |
181 | * be able to convert this value into a type usable in | |
182 | * an XPath expression. | |
183 | * | |
184 | * @return The wrapper, so that calls may be chained. | |
185 | */ | |
186 | public XPathWrapperFactory bindVariable(QName name, Object value) | |
187 | { | |
188 | 6 | _variables.put(name, value); |
189 | 6 | return this; |
190 | } | |
191 | ||
192 | ||
193 | /** | |
194 | * Binds a self-describing function to this expression. Subsequent calls | |
195 | * with the same name/arity will silently replace the binding. | |
196 | * <p> | |
197 | * All XPath instances created from this factory will share a single | |
198 | * instance of the bound function. If your function maintains state, | |
199 | * you are responsible for proper synchronization. | |
200 | * <p> | |
201 | * Per the JDK documentation, user-defined functions must occupy their | |
202 | * own namespace. You must bind a namespace for this function, and use | |
203 | * the bound prefix to reference it in your expression. Alternatively, | |
204 | * you can call the variant of <code>bindFunction()</code> that binds | |
205 | * a prefix to the function's namespace. | |
206 | * | |
207 | * @param func The function. | |
208 | * | |
209 | * @return The wrapper, so that calls may be chained. | |
210 | */ | |
211 | public XPathWrapperFactory bindFunction(FunctionResolver.SelfDescribingFunction func) | |
212 | { | |
213 | 2 | _functions.addFunction(func); |
214 | 2 | return this; |
215 | } | |
216 | ||
217 | ||
218 | /** | |
219 | * Binds a self-describing function to this expression, along with the | |
220 | * prefix used to access that function. This also establishes a namespace | |
221 | * binding for that prefix. | |
222 | * <p> | |
223 | * All XPath instances created from this factory will share a single | |
224 | * instance of the bound function. If your function maintains state, | |
225 | * you are responsible for proper synchronization. | |
226 | * <p> | |
227 | * Per the JDK documentation, user-defined functions must occupy their | |
228 | * own namespace. This method will retrieve the namespace from the | |
229 | * function, and bind it to the passed prefix. | |
230 | * | |
231 | * @param func The function. | |
232 | * @param prefix The prefix to bind to this function's namespace. | |
233 | * | |
234 | * @return The wrapper, so that calls may be chained. | |
235 | */ | |
236 | public XPathWrapperFactory bindFunction(FunctionResolver.SelfDescribingFunction func, String prefix) | |
237 | { | |
238 | 1 | _functions.addFunction(func); |
239 | 1 | bindNamespace(prefix, func.getNamespaceUri()); |
240 | 1 | return this; |
241 | } | |
242 | ||
243 | ||
244 | /** | |
245 | * Binds a standard <code>XPathFunction</code> to this expression, | |
246 | * handling any number of arguments. Subsequent calls to this method | |
247 | * with the same name will silently replace the binding. | |
248 | * <p> | |
249 | * All XPath instances created from this factory will share a single | |
250 | * instance of the bound function. If your function maintains state, | |
251 | * you are responsible for proper synchronization. | |
252 | * <p> | |
253 | * Per the JDK documentation, user-defined functions must occupy their | |
254 | * own namespace. If the qualified name that you pass to this method | |
255 | * includes a prefix, the associated namespace will be bound to that | |
256 | * prefix. If not, you must bind the namespace explicitly. In either | |
257 | * case, you must refer to the function in your expression using a | |
258 | * bound prefix. | |
259 | * | |
260 | * @param name The qualified name for this function. Must contain | |
261 | * a name and namespace, may contain a prefix. | |
262 | * @param func The function to bind to this name. | |
263 | * | |
264 | * @return The wrapper, so that calls may be chained. | |
265 | */ | |
266 | public XPathWrapperFactory bindFunction(QName name, XPathFunction func) | |
267 | { | |
268 | 1 | _functions.addFunction(func, name); |
269 | 1 | return this; |
270 | } | |
271 | ||
272 | ||
273 | /** | |
274 | * Binds a standard <code>XPathFunction</code> to this expression, | |
275 | * handling a specific number of arguments. Subsequent calls to this | |
276 | * method with the same name and arity will silently replace the binding. | |
277 | * <p> | |
278 | * All XPath instances created from this factory will share a single | |
279 | * instance of the bound function. If your function maintains state, | |
280 | * you are responsible for proper synchronization. | |
281 | * <p> | |
282 | * Per the JDK documentation, user-defined functions must occupy their | |
283 | * own namespace. If the qualified name that you pass to this method | |
284 | * includes a prefix, the associated namespace will be bound to that | |
285 | * prefix. If not, you must bind the namespace explicitly. In either | |
286 | * case, you must refer to the function in your expression using a | |
287 | * bound prefix. | |
288 | * | |
289 | * @param name The qualified name for this function. Must contain | |
290 | * a name and namespace, may contain a prefix. | |
291 | * @param func The function to bind to this name. | |
292 | * @param arity The number of arguments accepted by this function. | |
293 | * | |
294 | * @return The wrapper, so that calls may be chained. | |
295 | */ | |
296 | public XPathWrapperFactory bindFunction(QName name, XPathFunction func, int arity) | |
297 | { | |
298 | 1 | _functions.addFunction(func, name, arity); |
299 | 1 | return this; |
300 | } | |
301 | ||
302 | ||
303 | /** | |
304 | * Binds a standard <code>XPathFunction</code> to this expression, | |
305 | * handling a specific range of arguments. Subsequent calls to this | |
306 | * method with the same name and range of arguments will silently | |
307 | * replace the binding. | |
308 | * <p> | |
309 | * All XPath instances created from this factory will share a single | |
310 | * instance of the bound function. If your function maintains state, | |
311 | * you are responsible for proper synchronization. | |
312 | * <p> | |
313 | * Per the JDK documentation, user-defined functions must occupy their | |
314 | * own namespace. If the qualified name that you pass to this method | |
315 | * includes a prefix, the associated namespace will be bound to that | |
316 | * prefix. If not, you must bind the namespace explicitly. In either | |
317 | * case, you must refer to the function in your expression using a | |
318 | * bound prefix. | |
319 | * | |
320 | * @param name The qualified name for this function. Must contain | |
321 | * a name and namespace, may contain a prefix. | |
322 | * @param func The function to bind to this name. | |
323 | * @param minArity The minimum number of arguments accepted by this | |
324 | * function. | |
325 | * @param maxArity The maximum number of arguments accepted by this | |
326 | * function. | |
327 | * | |
328 | * @return The wrapper, so that calls may be chained. | |
329 | */ | |
330 | public XPathWrapperFactory bindFunction( | |
331 | QName name, XPathFunction func, int minArity, int maxArity) | |
332 | { | |
333 | 1 | _functions.addFunction(func, name, minArity, maxArity); |
334 | 1 | return this; |
335 | } | |
336 | ||
337 | ||
338 | /** | |
339 | * Returns an {@link XPathWrapper} instance for the specified expression. | |
340 | * This may either be a new or cached instance. | |
341 | */ | |
342 | public XPathWrapper newXPath(String xpath) | |
343 | { | |
344 | 23 | XPathWrapper wrapper = retrieveFromCache(xpath); |
345 | 23 | if (wrapper != null) |
346 | 3 | return wrapper; |
347 | ||
348 | 20 | wrapper = new XPathWrapper(xpath); |
349 | ||
350 | 20 | for (Map.Entry<String,String> namespace : _namespaces.entrySet()) |
351 | 22 | wrapper.bindNamespace(namespace.getKey(), namespace.getValue()); |
352 | ||
353 | 20 | for (Map.Entry<QName,Object> variable : _variables.entrySet()) |
354 | 13 | wrapper.bindVariable(variable.getKey(), variable.getValue()); |
355 | ||
356 | 20 | wrapper.setFunctionResolver(_functions.clone()); |
357 | ||
358 | 20 | addToCache(xpath, wrapper); |
359 | 20 | return wrapper; |
360 | } | |
361 | ||
362 | ||
363 | //---------------------------------------------------------------------------- | |
364 | // Internals | |
365 | //---------------------------------------------------------------------------- | |
366 | ||
367 | private XPathWrapper retrieveFromCache(String xpath) | |
368 | { | |
369 | 23 | if (_simpleCache != null) |
370 | { | |
371 | 4 | return _simpleCache.get(xpath); |
372 | } | |
373 | 19 | else if (_threadsafeCache != null) |
374 | { | |
375 | 3 | return _threadsafeCache.get().get(xpath); |
376 | } | |
377 | ||
378 | 16 | return null; |
379 | } | |
380 | ||
381 | ||
382 | private void addToCache(String xpath, XPathWrapper wrapper) | |
383 | { | |
384 | 20 | if (_simpleCache != null) |
385 | { | |
386 | 2 | _simpleCache.put(xpath, wrapper); |
387 | } | |
388 | 18 | else if (_threadsafeCache != null) |
389 | { | |
390 | 2 | _threadsafeCache.get().put(xpath, wrapper); |
391 | } | |
392 | 20 | } |
393 | } |