View Javadoc

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