1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.jxpath;
19
20 import java.text.DecimalFormatSymbols;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Locale;
26
27 import org.apache.commons.jxpath.util.KeyManagerUtils;
28
29 /**
30 * JXPathContext provides APIs for the traversal of graphs of JavaBeans using the XPath syntax. Using JXPathContext, you can read and write properties of
31 * JavaBeans, arrays, collections and maps. JXPathContext uses JavaBeans introspection to enumerate and access JavaBeans properties.
32 * <p>
33 * JXPathContext allows alternative implementations. This is why instead of allocating JXPathContext directly, you should call a static {@code newContext}
34 * method. This method will utilize the {@link JXPathContextFactory} API to locate a suitable implementation of JXPath. Bundled with JXPath comes a default
35 * implementation called Reference Implementation.
36 * </p>
37 *
38 * <h2>JXPath Interprets XPath Syntax on Java Object Graphs</h2>
39 *
40 * JXPath uses an intuitive interpretation of the XPath syntax in the context of Java object graphs. Here are some examples:
41 *
42 * <h3>Example 1: JavaBean Property Access</h3>
43 *
44 * JXPath can be used to access properties of a JavaBean.
45 *
46 * <pre>
47 * public class Employee {
48 * public String getFirstName(){
49 * ...
50 * }
51 * }
52 *
53 * Employee emp = new Employee();
54 * ...
55 *
56 * JXPathContext context = JXPathContext.newContext(emp);
57 * String fName = (String)context.getValue("firstName");
58 * </pre>
59 *
60 * In this example, we are using JXPath to access a property of the {@code emp} bean. In this simple case the invocation of JXPath is equivalent to invocation
61 * of getFirstName() on the bean.
62 *
63 * <h3>Example 2: Nested Bean Property Access</h3> JXPath can traverse object graphs:
64 *
65 * <pre>
66 * public class Employee {
67 * public Address getHomeAddress(){
68 * ...
69 * }
70 * }
71 * public class Address {
72 * public String getStreetNumber(){
73 * ...
74 * }
75 * }
76 *
77 * Employee emp = new Employee();
78 * ...
79 *
80 * JXPathContext context = JXPathContext.newContext(emp);
81 * String sNumber = (String)context.getValue("homeAddress/streetNumber");
82 * </pre>
83 *
84 * In this case XPath is used to access a property of a nested bean.
85 * <p>
86 * A property identified by the XPath does not have to be a "leaf" property. For instance, we can extract the whole Address object in above example:
87 * </p>
88 *
89 * <pre>
90 *
91 * Address addr = (Address) context.getValue("homeAddress");
92 * </pre>
93 *
94 * <h3>Example 3: Collection Subscripts</h3>
95 * <p>
96 * JXPath can extract elements from arrays and collections.
97 * </p>
98 *
99 * <pre>
100 * public class Integers {
101 * public int[] getNumbers(){
102 * ...
103 * }
104 * }
105 *
106 * Integers ints = new Integers();
107 * ...
108 *
109 * JXPathContext context = JXPathContext.newContext(ints);
110 * Integer thirdInt = (Integer)context.getValue("numbers[3]");
111 * </pre>
112 *
113 * A collection can be an arbitrary array or an instance of java.util. Collection.
114 * <p>
115 * Note: in XPath the first element of a collection has index 1, not 0.
116 * </p>
117 *
118 * <h3>Example 4: Map Element Access</h3>
119 *
120 * JXPath supports maps. To get a value use its key.
121 *
122 * <pre>
123 * public class Employee {
124 * public Map getAddresses(){
125 * return addressMap;
126 * }
127 *
128 * public void addAddress(String key, Address address){
129 * addressMap.put(key, address);
130 * }
131 * ...
132 * }
133 *
134 * Employee emp = new Employee();
135 * emp.addAddress("home", new Address(...));
136 * emp.addAddress("office", new Address(...));
137 * ...
138 *
139 * JXPathContext context = JXPathContext.newContext(emp);
140 * String homeZipCode = (String)context.getValue("addresses/home/zipCode");
141 * </pre>
142 *
143 * <p>
144 * Often you will need to use the alternative syntax for accessing Map elements:
145 * </p>
146 *
147 * <pre>
148 *
149 * String homeZipCode = (String) context.getValue("addresses[@name='home']/zipCode");
150 * </pre>
151 *
152 * In this case, the key can be an expression, e.g. a variable.<br>
153 *
154 * Note: At this point JXPath only supports Maps that use strings for keys.<br>
155 * Note: JXPath supports the extended notion of Map: any object with dynamic properties can be handled by JXPath provided that its class is registered with the
156 * {@link JXPathIntrospector}.
157 *
158 * <h3>Example 5: Retrieving Multiple Results</h3>
159 *
160 * JXPath can retrieve multiple objects from a graph. Note that the method called in this case is not {@code getValue}, but {@code iterate}.
161 *
162 * <pre>{@code
163 * public class Author {
164 * public Book[] getBooks(){
165 * ...
166 * }
167 * }
168 *
169 * Author auth = new Author();
170 * ...
171 *
172 * JXPathContext context = JXPathContext.newContext(auth);
173 * Iterator threeBooks = context.iterate("books[position() < 4]");
174 * }</pre>
175 *
176 * This returns a list of at most three books from the array of all books written by the author.
177 *
178 * <h3>Example 6: Setting Properties</h3> JXPath can be used to modify property values.
179 *
180 * <pre>
181 * public class Employee {
182 * public Address getAddress() {
183 * ...
184 * }
185 *
186 * public void setAddress(Address address) {
187 * ...
188 * }
189 * }
190 *
191 * Employee emp = new Employee();
192 * Address addr = new Address();
193 * ...
194 *
195 * JXPathContext context = JXPathContext.newContext(emp);
196 * context.setValue("address", addr);
197 * context.setValue("address/zipCode", "90190");
198 *
199 * </pre>
200 *
201 * <h3>Example 7: Creating objects</h3>
202 * <p>
203 * JXPath can be used to create new objects. First, create a subclass of {@link AbstractFactory AbstractFactory} and
204 * install it on the JXPathContext. Then call {@link JXPathContext#createPath createPathAndSetValue()} instead of "setValue". JXPathContext will invoke your
205 * AbstractFactory when it discovers that an intermediate node of the path is <strong>null</strong>. It will not override existing nodes.
206 * </p>
207 *
208 * <pre>
209 * public class AddressFactory extends AbstractFactory {
210 * public boolean createObject(JXPathContext context,
211 * Pointer pointer, Object parent, String name, int index){
212 * if ((parent instanceof Employee) && name.equals("address"){
213 * ((Employee)parent).setAddress(new Address());
214 * return true;
215 * }
216 * return false;
217 * }
218 * }
219 *
220 * JXPathContext context = JXPathContext.newContext(emp);
221 * context.setFactory(new AddressFactory());
222 * context.createPathAndSetValue("address/zipCode", "90190");
223 * </pre>
224 *
225 * <h3>Example 8: Using Variables</h3>
226 * <p>
227 * JXPath supports the notion of variables. The XPath syntax for accessing variables is <em>"$varName"</em>.
228 * </p>
229 *
230 * <pre>
231 * public class Author {
232 * public Book[] getBooks(){
233 * ...
234 * }
235 * }
236 *
237 * Author auth = new Author();
238 * ...
239 *
240 * JXPathContext context = JXPathContext.newContext(auth);
241 * context.getVariables().declareVariable("index", Integer.valueOf(2));
242 *
243 * Book secondBook = (Book)context.getValue("books[$index]");
244 * </pre>
245 *
246 * <p>
247 * You can also set variables using JXPath:
248 * </p>
249 *
250 * <pre>
251 * context.setValue("$index", Integer.valueOf(3));
252 * </pre>
253 *
254 * <p>
255 * Note: you can only <em>change</em> the value of an existing variable this way, you cannot <em>define</em> a new variable.
256 * </p>
257 *
258 * <p>
259 * When a variable contains a JavaBean or a collection, you can traverse the bean or collection as well:
260 * </p>
261 *
262 * <pre>
263 * ...
264 * context.getVariables().declareVariable("book", myBook);
265 * String title = (String)context.getValue("$book/title);
266 *
267 * Book array[] = new Book[]{...};
268 *
269 * context.getVariables().declareVariable("books", array);
270 *
271 * String title = (String)context.getValue("$books[2]/title);
272 * </pre>
273 *
274 * <h3>Example 9: Using Nested Contexts</h3>
275 * <p>
276 * If you need to use the same set of variable while interpreting XPaths with different beans, it makes sense to put
277 * the variables in a separate context and specify that context as a parent context every time you allocate a new JXPathContext for a JavaBean.
278 * </p>
279 *
280 * <pre>
281 * JXPathContext varContext = JXPathContext.newContext(null);
282 * varContext.getVariables().declareVariable("title", "Java");
283 * JXPathContext context = JXPathContext.newContext(varContext, auth);
284 * Iterator javaBooks = context.iterate("books[title = $title]");
285 * </pre>
286 *
287 * <h3>Using Custom Variable Pools</h3>
288 * <p>
289 * By default, JXPathContext creates a HashMap of variables. However, you can substitute a custom implementation of the
290 * Variables interface to make JXPath work with an alternative source of variables. For example, you can define implementations of Variables that cover a
291 * servlet context, HTTP request or any similar structure.
292 * </p>
293 *
294 * <h3>Example 10: Using Standard Extension Functions</h3> Using the standard extension functions, you can call methods on objects, static methods on classes
295 * and create objects using any constructor. The class names should be fully qualified.
296 * <p>
297 * Here's how you can create new objects:
298 * </p>
299 *
300 * <pre>
301 *
302 * Book book = (Book) context.getValue("org.apache.commons.jxpath.example.Book.new ('John Updike')");
303 * </pre>
304 *
305 * <p>
306 * Here's how you can call static methods:
307 * </p>
308 *
309 * <pre>
310 *
311 * Book book = (Book) context.getValue("org. apache.commons.jxpath.example.Book.getBestBook('John Updike')");
312 * </pre>
313 *
314 * <p>
315 * Here's how you can call regular methods:
316 * </p>
317 *
318 * <pre>
319 *
320 * String firstName = (String) context.getValue("getAuthorsFirstName($book)");
321 * </pre>
322 *
323 * <p>
324 * As you can see, the target of the method is specified as the first parameter of the function.
325 * </p>
326 *
327 * <h3>Example 11: Using Custom Extension Functions</h3>
328 * <p>
329 * Collections of custom extension functions can be implemented as {@link Functions Functions} objects or
330 * as Java classes, whose methods become extenstion functions.
331 * </p>
332 * <p>
333 * Let's say the following class implements various formatting operations:
334 * </p>
335 *
336 * <pre>
337 * public class Formats {
338 * public static String date(Date d, String pattern){
339 * return new SimpleDateFormat(pattern).format(d);
340 * }
341 * ...
342 * }
343 * </pre>
344 *
345 * <p>
346 * We can register this class with a JXPathContext:
347 * </p>
348 *
349 * <pre>
350 * context.setFunctions(new ClassFunctions(Formats.class, "format"));
351 * ...
352 *
353 * context.getVariables().declareVariable("today", new Date());
354 * String today = (String)context.getValue("format:date($today, 'MM/dd/yyyy')");
355 *
356 * </pre>
357 *
358 * <p>
359 * You can also register whole packages of Java classes using PackageFunctions.
360 * </p>
361 * <p>
362 * Also, see {@link FunctionLibrary FunctionLibrary}, which is a class that allows you to register multiple sets of extension functions with the same
363 * JXPathContext.
364 * </p>
365 *
366 * <h2>Configuring JXPath</h2>
367 *
368 * <p>
369 * JXPath uses JavaBeans introspection to discover properties of JavaBeans. You can provide alternative property lists by supplying custom JXPathBeanInfo
370 * classes (see {@link JXPathBeanInfo JXPathBeanInfo}).
371 * </p>
372 *
373 * <h2>Notes</h2>
374 * <ul>
375 * <li>JXPath does not support DOM attributes for non-DOM objects. Even though XPaths like "para[@type='warning']" are legitimate, they will always produce
376 * empty results. The only attribute supported for JavaBeans is "name". The XPath "foo/bar" is equivalent to "foo[@name='bar']".</li>
377 *
378 * <li id='matches_no_property_in_the_graph'>The term <b>matches no property in the graph</b> is used throughout the documentation. It describes a property or
379 * path that can be determined as not belonging to the graph. Determining whether a property or path belongs to the graph depends on the type of object being
380 * used as {@code cotextBean} (see {@link #newContext(Object)}). It is only possible strongly typed models where a specific Java model is used as context. It is
381 * not possible with dynamic models such Maps or DOM implementations.
382 * <p>
383 * When a XPath does not match a property in the graph, the methods of this class that retrieve a pointer will generally behave in the following way, depending
384 * on the last value configured with {@link #setLenient(boolean)}:
385 * </p>
386 *
387 * <ol style='list-style:upper-alpha'>
388 * <li>If {@code lenient} is {@code false} (default) - methods will throw {@link JXPathNotFoundException}.
389 * <li>If {@code lenient} is {@code true} - methods will throw no exception and return a value appropriate for that method to express the absence: might be a
390 * Java {@code null} or a {@link Pointer} whose {@link Pointer#getValue()} returns {@code null}, depends on the method.
391 * </ol>
392 * </li>
393 * </ul>
394 *
395 * <p>
396 * See also:
397 * </p>
398 * <ul>
399 * <li>See <a href="http://www.w3schools.com/xpath">XPath Tutorial by W3Schools</a></li>
400 * <li>See also <a href="http://www.w3.org/TR/xpath">XML Path Language (XPath) Version 1.0</a></li>
401 * </ul>
402 *
403 * <p>
404 * You will also find more information and examples in the <a href="https://commons.apache.org/proper/jxpath/apidocs/index.html">JXPath User's Guide</a>
405 * </p>
406 */
407 public abstract class JXPathContext {
408
409 private static volatile JXPathContextFactory contextFactory;
410 private static volatile JXPathContext compilationContext;
411 private static final PackageFunctions GENERIC_FUNCTIONS = new PackageFunctions("", null);
412
413 /**
414 * Compiles the supplied XPath and returns an internal representation of the path that can then be evaluated. Use CompiledExpressions when you need to
415 * evaluate the same expression multiple times and there is a convenient place to cache CompiledExpression between invocations.
416 *
417 * @param xpath to compile
418 * @return CompiledExpression
419 */
420 public static CompiledExpression compile(final String xpath) {
421 if (compilationContext == null) {
422 compilationContext = newContext(null);
423 }
424 return compilationContext.compilePath(xpath);
425 }
426
427 /**
428 * Acquires a context factory and caches it.
429 *
430 * @return JXPathContextFactory
431 */
432 private static JXPathContextFactory getContextFactory() {
433 if (contextFactory == null) {
434 contextFactory = JXPathContextFactory.newInstance();
435 }
436 return contextFactory;
437 }
438
439 /**
440 * Creates a new JXPathContext with the specified bean as the root node and the specified parent context. Variables defined in a parent context can be
441 * referenced in XPaths passed to the child context.
442 *
443 * @param parentContext parent context
444 * @param contextBean Object
445 * @return JXPathContext
446 */
447 public static JXPathContext newContext(final JXPathContext parentContext, final Object contextBean) {
448 return getContextFactory().newContext(parentContext, contextBean);
449 }
450
451 /**
452 * Creates a new JXPathContext with the specified object as the root node.
453 *
454 * @param contextBean Object
455 * @return JXPathContext
456 */
457 public static JXPathContext newContext(final Object contextBean) {
458 return getContextFactory().newContext(null, contextBean);
459 }
460
461 /** Parent context */
462 protected JXPathContext parentContext;
463 /** Context bean */
464 protected Object contextBean;
465 /** Variables */
466 protected Variables vars;
467 /** Functions */
468 protected Functions functions;
469 /** AbstractFactory */
470 protected AbstractFactory factory;
471 /** IdentityManager */
472 protected IdentityManager idManager;
473 /** KeyManager */
474 protected KeyManager keyManager;
475 /** Decimal format map */
476 protected HashMap<String, DecimalFormatSymbols> decimalFormats;
477 private Locale locale;
478 private boolean lenientSet;
479 private boolean lenient;
480
481 /**
482 * This constructor should remain protected - it is to be overridden by subclasses, but never explicitly invoked by clients.
483 *
484 * @param parentContext parent context
485 * @param contextBean Object
486 */
487 protected JXPathContext(final JXPathContext parentContext, final Object contextBean) {
488 this.parentContext = parentContext;
489 this.contextBean = contextBean;
490 }
491
492 /**
493 * Overridden by each concrete implementation of JXPathContext to perform compilation. Is called by {@code compile()}.
494 *
495 * @param xpath to compile
496 * @return CompiledExpression
497 */
498 protected abstract CompiledExpression compilePath(String xpath);
499
500 /**
501 * Creates missing elements of the path by invoking an {@link AbstractFactory}, which should first be installed on the context by calling
502 * {@link #setFactory}.
503 * <p>
504 * Will throw an exception if the AbstractFactory fails to create an instance for a path element.
505 *
506 * @param xpath indicating destination to create
507 * @return pointer to new location
508 */
509 public abstract Pointer createPath(String xpath);
510
511 /**
512 * The same as setValue, except it creates intermediate elements of the path by invoking an {@link AbstractFactory}, which should first be installed on the
513 * context by calling {@link #setFactory}.
514 * <p>
515 * Will throw an exception if one of the following conditions occurs:
516 * <ul>
517 * <li>Elements of the XPath aleady exist, but the path does not in fact describe an existing property
518 * <li>The AbstractFactory fails to create an instance for an intermediate element.
519 * <li>The property is not writable (no public, non-static set method)
520 * </ul>
521 *
522 * @param xpath indicating position to create
523 * @param value to set
524 * @return pointer to new location
525 */
526 public abstract Pointer createPathAndSetValue(String xpath, Object value);
527
528 /**
529 * Returns the JavaBean associated with this context.
530 *
531 * @return Object
532 */
533 public Object getContextBean() {
534 return contextBean;
535 }
536
537 /**
538 * Returns a Pointer for the context bean.
539 *
540 * @return Pointer
541 */
542 public abstract Pointer getContextPointer();
543
544 /**
545 * Gets the named DecimalFormatSymbols.
546 *
547 * @param name key
548 * @return DecimalFormatSymbols
549 * @see #setDecimalFormatSymbols(String, DecimalFormatSymbols)
550 */
551 public synchronized DecimalFormatSymbols getDecimalFormatSymbols(final String name) {
552 if (decimalFormats == null) {
553 return parentContext == null ? null : parentContext.getDecimalFormatSymbols(name);
554 }
555 return decimalFormats.get(name);
556 }
557
558 /**
559 * Returns the AbstractFactory installed on this context. If none has been installed and this context has a parent context, returns the parent's factory.
560 * Otherwise returns null.
561 *
562 * @return AbstractFactory
563 */
564 public AbstractFactory getFactory() {
565 if (factory == null && parentContext != null) {
566 return parentContext.getFactory();
567 }
568 return factory;
569 }
570
571 /**
572 * Returns the set of functions installed on the context.
573 *
574 * @return Functions
575 */
576 public Functions getFunctions() {
577 if (functions != null) {
578 return functions;
579 }
580 if (parentContext == null) {
581 return GENERIC_FUNCTIONS;
582 }
583 return null;
584 }
585
586 /**
587 * Returns this context's identity manager. If none has been installed, returns the identity manager of the parent context.
588 *
589 * @return IdentityManager
590 */
591 public IdentityManager getIdentityManager() {
592 if (idManager == null && parentContext != null) {
593 return parentContext.getIdentityManager();
594 }
595 return idManager;
596 }
597
598 /**
599 * Returns this context's key manager. If none has been installed, returns the key manager of the parent context.
600 *
601 * @return KeyManager
602 */
603 public KeyManager getKeyManager() {
604 if (keyManager == null && parentContext != null) {
605 return parentContext.getKeyManager();
606 }
607 return keyManager;
608 }
609
610 /**
611 * Returns the locale set with setLocale. If none was set and the context has a parent, returns the parent's locale. Otherwise, returns Locale.getDefault().
612 *
613 * @return Locale
614 */
615 public synchronized Locale getLocale() {
616 if (locale == null) {
617 if (parentContext != null) {
618 return parentContext.getLocale();
619 }
620 locale = Locale.getDefault();
621 }
622 return locale;
623 }
624
625 /**
626 * Returns the namespace context pointer set with {@link #setNamespaceContextPointer(Pointer) setNamespaceContextPointer()} or, if none has been specified,
627 * the context pointer otherwise.
628 *
629 * @return The namespace context pointer.
630 */
631 public Pointer getNamespaceContextPointer() {
632 throw new UnsupportedOperationException("Namespace registration is not implemented by " + getClass());
633 }
634
635 /**
636 * Given a prefix, returns a registered namespace URI. If the requested prefix was not defined explicitly using the registerNamespace method, JXPathContext
637 * will then check the context node to see if the prefix is defined there. See {@link #setNamespaceContextPointer(Pointer) setNamespaceContextPointer}.
638 *
639 * @param prefix The namespace prefix to look up
640 * @return namespace URI or null if the prefix is undefined.
641 */
642 public String getNamespaceURI(final String prefix) {
643 throw new UnsupportedOperationException("Namespace registration is not implemented by " + getClass());
644 }
645
646 /**
647 * Locates a NodeSet by key/value.
648 *
649 * @param key string
650 * @param value object
651 * @return NodeSet found
652 */
653 public NodeSet getNodeSetByKey(final String key, final Object value) {
654 final KeyManager manager = getKeyManager();
655 if (manager != null) {
656 return KeyManagerUtils.getExtendedKeyManager(manager).getNodeSetByKey(this, key, value);
657 }
658 throw new JXPathException("Cannot find an element by key - " + "no KeyManager has been specified");
659 }
660
661 /**
662 * Returns the parent context of this context or null.
663 *
664 * @return JXPathContext
665 */
666 public JXPathContext getParentContext() {
667 return parentContext;
668 }
669
670 /**
671 * Traverses the XPath and returns a Pointer. A Pointer provides easy access to a property.
672 * <p>
673 * If the XPath <a href='#matches_no_property_in_the_graph'>matches no properties in the graph</a> the behavior depends on the value that has been
674 * configured with {@link #setLenient(boolean)}:
675 * </p>
676 * <ul>
677 * <li>{@code false} (default) the method will throw a {@link JXPathNotFoundException}.
678 * <li>{@code true} the method returns a pointer whose {@link Pointer#getValue()} method will always return null.
679 * </ul>
680 *
681 * @param xpath desired
682 * @return Pointer A {@link Pointer}, never {@code null}.
683 * @throws JXPathNotFoundException see method description.
684 */
685 public abstract Pointer getPointer(String xpath);
686
687 /**
688 * Locates a Node by its ID.
689 *
690 * @param id is the ID of the sought node.
691 * @return Pointer
692 */
693 public Pointer getPointerByID(final String id) {
694 final IdentityManager manager = getIdentityManager();
695 if (manager != null) {
696 return manager.getPointerByID(this, id);
697 }
698 throw new JXPathException("Cannot find an element by ID - " + "no IdentityManager has been specified");
699 }
700
701 /**
702 * Locates a Node by a key value.
703 *
704 * @param key string
705 * @param value string
706 * @return Pointer found
707 */
708 public Pointer getPointerByKey(final String key, final String value) {
709 final KeyManager manager = getKeyManager();
710 if (manager != null) {
711 return manager.getPointerByKey(this, key, value);
712 }
713 throw new JXPathException("Cannot find an element by key - " + "no KeyManager has been specified");
714 }
715
716 /**
717 * Gets the prefix associated with the specifed namespace URI.
718 *
719 * @param namespaceURI the ns URI to check.
720 * @return String prefix
721 * @since JXPath 1.3
722 */
723 public String getPrefix(final String namespaceURI) {
724 throw new UnsupportedOperationException("Namespace registration is not implemented by " + getClass());
725 }
726
727 /**
728 * Returns a JXPathContext that is relative to the current JXPathContext. The supplied pointer becomes the context pointer of the new context. The relative
729 * context inherits variables, extension functions, locale etc from the parent context.
730 *
731 * @param pointer Pointer
732 * @return JXPathContext
733 */
734 public abstract JXPathContext getRelativeContext(Pointer pointer);
735
736 /**
737 * Evaluates the XPath and returns the resulting object. Primitive types are wrapped into objects.
738 *
739 * @param xpath to evaluate
740 * @return Object found
741 */
742 public abstract Object getValue(String xpath);
743
744 /**
745 * Evaluates the xpath, converts the result to the specified class and returns the resulting object.
746 *
747 * @param xpath to evaluate
748 * @param requiredType required type
749 * @return Object found
750 */
751 public abstract Object getValue(String xpath, Class requiredType);
752
753 /**
754 * Returns the variable pool associated with the context. If no such pool was specified with the {@link #setVariables} method, returns the default
755 * implementation of Variables, {@link BasicVariables BasicVariables}.
756 *
757 * @return Variables
758 */
759 public Variables getVariables() {
760 if (vars == null) {
761 vars = new BasicVariables();
762 }
763 return vars;
764 }
765
766 /**
767 * Tests whether this JXPathContext is lenient.
768 *
769 * @return boolean
770 * @see #setLenient(boolean)
771 */
772 public synchronized boolean isLenient() {
773 if (!lenientSet && parentContext != null) {
774 return parentContext.isLenient();
775 }
776 return lenient;
777 }
778
779 /**
780 * Traverses the XPath and returns an Iterator of all results found for the path. If the XPath matches no properties in the graph, the Iterator will be
781 * empty, but not null.
782 *
783 * @param <E> the type of elements returned by the iterator.
784 * @param xpath to iterate
785 * @return Iterator
786 */
787 public abstract <E> Iterator<E> iterate(String xpath);
788
789 /**
790 * Traverses the XPath and returns an Iterator of Pointers. A Pointer provides easy access to a property. If the XPath matches no properties in the graph,
791 * the Iterator be empty, but not null.
792 *
793 * @param xpath to iterate
794 * @return Iterator
795 */
796 public abstract Iterator<Pointer> iteratePointers(String xpath);
797
798 /**
799 * Registers a namespace prefix.
800 *
801 * @param prefix A namespace prefix
802 * @param namespaceURI A URI for that prefix
803 */
804 public void registerNamespace(final String prefix, final String namespaceURI) {
805 throw new UnsupportedOperationException("Namespace registration is not implemented by " + getClass());
806 }
807
808 /**
809 * Removes all elements of the object graph described by the xpath.
810 *
811 * @param xpath indicating positions to remove
812 */
813 public abstract void removeAll(String xpath);
814
815 /**
816 * Removes the element of the object graph described by the xpath.
817 *
818 * @param xpath indicating position to remove
819 */
820 public abstract void removePath(String xpath);
821
822 /**
823 * Finds all nodes that match the specified XPath.
824 *
825 * @param xpath the xpath to be evaluated
826 * @return a list of found objects
827 */
828 public List selectNodes(final String xpath) {
829 final ArrayList list = new ArrayList();
830 final Iterator<Pointer> iterator = iteratePointers(xpath);
831 while (iterator.hasNext()) {
832 final Pointer pointer = iterator.next();
833 list.add(pointer.getNode());
834 }
835 return list;
836 }
837
838 /**
839 * Finds the first object that matches the specified XPath. It is equivalent to {@code getPointer(xpath).getNode()}. Note that this method produces the same
840 * result as {@code getValue()} on object models like JavaBeans, but a different result for DOM/JDOM etc., because it returns the Node itself, rather than
841 * its textual contents.
842 *
843 * @param xpath the xpath to be evaluated
844 * @return the found object
845 */
846 public Object selectSingleNode(final String xpath) {
847 final Pointer pointer = getPointer(xpath);
848 return pointer == null ? null : pointer.getNode();
849 }
850
851 /**
852 * Sets {@link DecimalFormatSymbols} for a given name. The DecimalFormatSymbols can be referenced as the third, optional argument in the invocation of
853 * {@code format-number (number,format,decimal-format-name)} function. By default, JXPath uses the symbols for the current locale.
854 *
855 * @param name the format name or null for default format.
856 * @param symbols DecimalFormatSymbols
857 */
858 public synchronized void setDecimalFormatSymbols(final String name, final DecimalFormatSymbols symbols) {
859 if (decimalFormats == null) {
860 decimalFormats = new HashMap<>();
861 }
862 decimalFormats.put(name, symbols);
863 }
864
865 /**
866 * Sets the ExceptionHandler used by this context, if any.
867 *
868 * @param exceptionHandler to set
869 * @since 1.4.0
870 */
871 public void setExceptionHandler(final ExceptionHandler exceptionHandler) {
872 throw new UnsupportedOperationException("ExceptionHandler registration is not implemented by " + getClass());
873 }
874
875 /**
876 * Install an abstract factory that should be used by the {@code createPath()} and {@code createPathAndSetValue()} methods.
877 *
878 * @param factory AbstractFactory
879 */
880 public void setFactory(final AbstractFactory factory) {
881 this.factory = factory;
882 }
883
884 /**
885 * Install a library of extension functions.
886 *
887 * @param functions Functions
888 * @see FunctionLibrary
889 */
890 public void setFunctions(final Functions functions) {
891 this.functions = functions;
892 }
893
894 /**
895 * Install an identity manager that will be used by the context to look up a node by its ID.
896 *
897 * @param idManager IdentityManager to set
898 */
899 public void setIdentityManager(final IdentityManager idManager) {
900 this.idManager = idManager;
901 }
902
903 /**
904 * Install a key manager that will be used by the context to look up a node by a key value.
905 *
906 * @param keyManager KeyManager
907 */
908 public void setKeyManager(final KeyManager keyManager) {
909 this.keyManager = keyManager;
910 }
911
912 /**
913 * If the context is in the lenient mode, then getValue() returns null for inexistent paths. Otherwise, a path that does not map to an existing property
914 * will throw an exception. Note that if the property exists, but its value is null, the exception is <em>not</em> thrown.
915 * <p>
916 * By default, lenient = false
917 *
918 * @param lenient flag
919 */
920 public synchronized void setLenient(final boolean lenient) {
921 this.lenient = lenient;
922 lenientSet = true;
923 }
924
925 /**
926 * Sets the locale for this context. The value of the "lang" attribute as well as the lang() function will be affected by the locale. By default, JXPath
927 * uses {@code Locale.getDefault()}
928 *
929 * @param locale Locale
930 */
931 public synchronized void setLocale(final Locale locale) {
932 this.locale = locale;
933 }
934
935 /**
936 * Namespace prefixes can be defined implicitly by specifying a pointer to a context where the namespaces are defined. By default, NamespaceContextPointer
937 * is the same as the Context Pointer, see {@link #getContextPointer() getContextPointer()}
938 *
939 * @param namespaceContextPointer The pointer to the context where prefixes used in XPath expressions should be resolved.
940 */
941 public void setNamespaceContextPointer(final Pointer namespaceContextPointer) {
942 throw new UnsupportedOperationException("Namespace registration is not implemented by " + getClass());
943 }
944
945 /**
946 * Modifies the value of the property described by the supplied xpath. Will throw an exception if one of the following conditions occurs:
947 * <ul>
948 * <li>The XPath does not in fact describe an existing property
949 * <li>The property is not writable (no public, non-static set method)
950 * </ul>
951 *
952 * @param xpath indicating position
953 * @param value to set
954 */
955 public abstract void setValue(String xpath, Object value);
956
957 /**
958 * Installs a custom implementation of the Variables interface.
959 *
960 * @param vars Variables
961 */
962 public void setVariables(final Variables vars) {
963 this.vars = vars;
964 }
965 }