View Javadoc

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  package org.apache.commons.jxpath.ri.model;
18  
19  import java.util.HashSet;
20  import java.util.Locale;
21  
22  import org.apache.commons.jxpath.AbstractFactory;
23  import org.apache.commons.jxpath.ExceptionHandler;
24  import org.apache.commons.jxpath.JXPathContext;
25  import org.apache.commons.jxpath.JXPathException;
26  import org.apache.commons.jxpath.JXPathNotFoundException;
27  import org.apache.commons.jxpath.NodeSet;
28  import org.apache.commons.jxpath.Pointer;
29  import org.apache.commons.jxpath.ri.Compiler;
30  import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
31  import org.apache.commons.jxpath.ri.NamespaceResolver;
32  import org.apache.commons.jxpath.ri.QName;
33  import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
34  import org.apache.commons.jxpath.ri.compiler.NodeTest;
35  import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
36  import org.apache.commons.jxpath.ri.model.beans.NullPointer;
37  
38  /**
39   * Common superclass for Pointers of all kinds.  A NodePointer maps to
40   * a deterministic XPath that represents the location of a node in an
41   * object graph. This XPath uses only simple axes: child, namespace and
42   * attribute and only simple, context-independent predicates.
43   *
44   * @author Dmitri Plotnikov
45   * @version $Revision: 1234255 $ $Date: 2012-01-20 22:11:46 -0500 (Fri, 20 Jan 2012) $
46   */
47  public abstract class NodePointer implements Pointer {
48  
49      /** Serialization version */
50      private static final long serialVersionUID = 8117201322861007777L;
51  
52      /** Whole collection index. */
53      public static final int WHOLE_COLLECTION = Integer.MIN_VALUE;
54  
55      /** Constant to indicate unknown namespace */
56      public static final String UNKNOWN_NAMESPACE = "<<unknown namespace>>";
57  
58      /** Index for this NodePointer */
59      protected int index = WHOLE_COLLECTION;
60  
61      private boolean attribute = false;
62      private NamespaceResolver namespaceResolver;
63      private ExceptionHandler exceptionHandler;
64      private transient Object rootNode;
65  
66      /**
67       * Allocates an entirely new NodePointer by iterating through all installed
68       * NodePointerFactories until it finds one that can create a pointer.
69       * @param name QName
70       * @param bean Object
71       * @param locale Locale
72       * @return NodePointer
73       */
74      public static NodePointer newNodePointer(
75          QName name,
76          Object bean,
77          Locale locale) {
78          NodePointer pointer = null;
79          if (bean == null) {
80              pointer = new NullPointer(name, locale);
81              return pointer;
82          }
83  
84          NodePointerFactory[] factories =
85              JXPathContextReferenceImpl.getNodePointerFactories();
86          for (int i = 0; i < factories.length; i++) {
87              pointer = factories[i].createNodePointer(name, bean, locale);
88              if (pointer != null) {
89                  return pointer;
90              }
91          }
92          throw new JXPathException(
93              "Could not allocate a NodePointer for object of "
94                  + bean.getClass());
95      }
96  
97      /**
98       * Allocates an new child NodePointer by iterating through all installed
99       * NodePointerFactories until it finds one that can create a pointer.
100      * @param parent pointer
101      * @param name QName
102      * @param bean Object
103      * @return NodePointer
104      */
105     public static NodePointer newChildNodePointer(
106         NodePointer parent,
107         QName name,
108         Object bean) {
109         NodePointerFactory[] factories =
110             JXPathContextReferenceImpl.getNodePointerFactories();
111         for (int i = 0; i < factories.length; i++) {
112             NodePointer pointer =
113                 factories[i].createNodePointer(parent, name, bean);
114             if (pointer != null) {
115                 return pointer;
116             }
117         }
118         throw new JXPathException(
119             "Could not allocate a NodePointer for object of "
120                 + bean.getClass());
121     }
122 
123     /** Parent pointer */
124     protected NodePointer parent;
125 
126     /** Locale */
127     protected Locale locale;
128 
129     /**
130      * Create a new NodePointer.
131      * @param parent Pointer
132      */
133     protected NodePointer(NodePointer parent) {
134         this.parent = parent;
135     }
136 
137     /**
138      * Create a new NodePointer.
139      * @param parent Pointer
140      * @param locale Locale
141      */
142     protected NodePointer(NodePointer parent, Locale locale) {
143         this.parent = parent;
144         this.locale = locale;
145     }
146 
147     /**
148      * Get the NamespaceResolver associated with this NodePointer.
149      * @return NamespaceResolver
150      */
151     public NamespaceResolver getNamespaceResolver() {
152         if (namespaceResolver == null && parent != null) {
153             namespaceResolver = parent.getNamespaceResolver();
154         }
155         return namespaceResolver;
156     }
157 
158     /**
159      * Set the NamespaceResolver for this NodePointer.
160      * @param namespaceResolver NamespaceResolver
161      */
162     public void setNamespaceResolver(NamespaceResolver namespaceResolver) {
163         this.namespaceResolver = namespaceResolver;
164     }
165 
166     /**
167      * Get the parent pointer.
168      * @return NodePointer
169      */
170     public NodePointer getParent() {
171         NodePointer pointer = parent;
172         while (pointer != null && pointer.isContainer()) {
173             pointer = pointer.getImmediateParentPointer();
174         }
175         return pointer;
176     }
177 
178     /**
179      * Get the immediate parent pointer.
180      * @return NodePointer
181      */
182     public NodePointer getImmediateParentPointer() {
183         return parent;
184     }
185 
186     /**
187      * Set to true if the pointer represents the "attribute::" axis.
188      * @param attribute boolean
189      */
190     public void setAttribute(boolean attribute) {
191         this.attribute = attribute;
192     }
193 
194     /**
195      * Returns true if the pointer represents the "attribute::" axis.
196      * @return boolean
197      */
198     public boolean isAttribute() {
199         return attribute;
200     }
201 
202     /**
203      * Returns true if this Pointer has no parent.
204      * @return boolean
205      */
206     public boolean isRoot() {
207         return parent == null;
208     }
209 
210     /**
211      * If true, this node does not have children
212      * @return boolean
213      */
214     public abstract boolean isLeaf();
215 
216     /**
217      * Learn whether this pointer is considered to be a node.
218      * @return boolean
219      * @deprecated Please use !isContainer()
220      */
221     public boolean isNode() {
222         return !isContainer();
223     }
224 
225     /**
226      * If true, this node is auxiliary and can only be used as an intermediate in
227      * the chain of pointers.
228      * @return boolean
229      */
230     public boolean isContainer() {
231         return false;
232     }
233 
234     /**
235      * If the pointer represents a collection, the index identifies
236      * an element of that collection.  The default value of <code>index</code>
237      * is <code>WHOLE_COLLECTION</code>, which just means that the pointer
238      * is not indexed at all.
239      * Note: the index on NodePointer starts with 0, not 1.
240      * @return int
241      */
242     public int getIndex() {
243         return index;
244     }
245 
246     /**
247      * Set the index of this NodePointer.
248      * @param index int
249      */
250     public void setIndex(int index) {
251         this.index = index;
252     }
253 
254     /**
255      * Returns <code>true</code> if the value of the pointer is an array or
256      * a Collection.
257      * @return boolean
258      */
259     public abstract boolean isCollection();
260 
261     /**
262      * If the pointer represents a collection (or collection element),
263      * returns the length of the collection.
264      * Otherwise returns 1 (even if the value is null).
265      * @return int
266      */
267     public abstract int getLength();
268 
269     /**
270      * By default, returns <code>getNode()</code>, can be overridden to
271      * return a "canonical" value, like for instance a DOM element should
272      * return its string value.
273      * @return Object value
274      */
275     public Object getValue() {
276         NodePointer valuePointer = getValuePointer();
277         if (valuePointer != this) {
278             return valuePointer.getValue();
279         }
280         // Default behavior is to return the same as getNode()
281         return getNode();
282     }
283 
284     /**
285      * If this pointer manages a transparent container, like a variable,
286      * this method returns the pointer to the contents.
287      * Only an auxiliary (non-node) pointer can (and should) return a
288      * value pointer other than itself.
289      * Note that you probably don't want to override
290      * <code>getValuePointer()</code> directly.  Override the
291      * <code>getImmediateValuePointer()</code> method instead.  The
292      * <code>getValuePointer()</code> method is calls
293      * <code>getImmediateValuePointer()</code> and, if the result is not
294      * <code>this</code>, invokes <code>getValuePointer()</code> recursively.
295      * The idea here is to open all nested containers. Let's say we have a
296      * container within a container within a container. The
297      * <code>getValuePointer()</code> method should then open all those
298      * containers and return the pointer to the ultimate contents. It does so
299      * with the above recursion.
300      * @return NodePointer
301      */
302     public NodePointer getValuePointer() {
303         NodePointer ivp = getImmediateValuePointer();
304         return ivp == this ? this : ivp.getValuePointer();
305     }
306 
307     /**
308      * @see #getValuePointer()
309      *
310      * @return NodePointer is either <code>this</code> or a pointer
311      *   for the immediately contained value.
312      */
313     public NodePointer getImmediateValuePointer() {
314         return this;
315     }
316 
317     /**
318      * An actual pointer points to an existing part of an object graph, even
319      * if it is null. A non-actual pointer represents a part that does not exist
320      * at all.
321      * For instance consider the pointer "/address/street".
322      * If both <em>address</em> and <em>street</em> are not null,
323      * the pointer is actual.
324      * If <em>address</em> is not null, but <em>street</em> is null,
325      * the pointer is still actual.
326      * If <em>address</em> is null, the pointer is not actual.
327      * (In JavaBeans) if <em>address</em> is not a property of the root bean,
328      * a Pointer for this path cannot be obtained at all - actual or otherwise.
329      * @return boolean
330      */
331     public boolean isActual() {
332         return index == WHOLE_COLLECTION || index >= 0 && index < getLength();
333     }
334 
335     /**
336      * Returns the name of this node. Can be null.
337      * @return QName
338      */
339     public abstract QName getName();
340 
341     /**
342      * Returns the value represented by the pointer before indexing.
343      * So, if the node represents an element of a collection, this
344      * method returns the collection itself.
345      * @return Object value
346      */
347     public abstract Object getBaseValue();
348 
349     /**
350      * Returns the object the pointer points to; does not convert it
351      * to a "canonical" type.
352      * @return Object node value
353      * @deprecated 1.1 Please use getNode()
354      */
355     public Object getNodeValue() {
356         return getNode();
357     }
358 
359     /**
360      * Returns the object the pointer points to; does not convert it
361      * to a "canonical" type. Opens containers, properties etc and returns
362      * the ultimate contents.
363      * @return Object node
364      */
365     public Object getNode() {
366         return getValuePointer().getImmediateNode();
367     }
368 
369     /**
370      * Get the root node.
371      * @return Object value of this pointer's root (top parent).
372      */
373     public synchronized Object getRootNode() {
374         if (rootNode == null) {
375             rootNode = parent == null ? getImmediateNode() : parent.getRootNode();
376         }
377         return rootNode;
378     }
379 
380     /**
381      * Returns the object the pointer points to; does not convert it
382      * to a "canonical" type.
383      * @return Object node
384      */
385     public abstract Object getImmediateNode();
386 
387     /**
388      * Converts the value to the required type and changes the corresponding
389      * object to that value.
390      * @param value the value to set
391      */
392     public abstract void setValue(Object value);
393 
394     /**
395      * Compares two child NodePointers and returns a positive number,
396      * zero or a positive number according to the order of the pointers.
397      * @param pointer1 first pointer to be compared
398      * @param pointer2 second pointer to be compared
399      * @return int per Java comparison conventions
400      */
401     public abstract int compareChildNodePointers(
402             NodePointer pointer1, NodePointer pointer2);
403 
404     /**
405      * Checks if this Pointer matches the supplied NodeTest.
406      * @param test the NodeTest to execute
407      * @return true if a match
408      */
409     public boolean testNode(NodeTest test) {
410         if (test == null) {
411             return true;
412         }
413         if (test instanceof NodeNameTest) {
414             if (isContainer()) {
415                 return false;
416             }
417             NodeNameTest nodeNameTest = (NodeNameTest) test;
418             QName testName = nodeNameTest.getNodeName();
419             QName nodeName = getName();
420             if (nodeName == null) {
421                 return false;
422             }
423 
424             String testPrefix = testName.getPrefix();
425             String nodePrefix = nodeName.getPrefix();
426             if (!safeEquals(testPrefix, nodePrefix)) {
427                 String testNS = getNamespaceURI(testPrefix);
428                 String nodeNS = getNamespaceURI(nodePrefix);
429                 if (!safeEquals(testNS, nodeNS)) {
430                     return false;
431                 }
432             }
433             if (nodeNameTest.isWildcard()) {
434                 return true;
435             }
436             return testName.getName().equals(nodeName.getName());
437         }
438         return test instanceof NodeTypeTest
439                 && ((NodeTypeTest) test).getNodeType() == Compiler.NODE_TYPE_NODE && isNode();
440     }
441 
442     /**
443      *  Called directly by JXPathContext. Must create path and
444      *  set value.
445      *  @param context the owning JXPathContext
446      *  @param value the new value to set
447      *  @return created NodePointer
448      */
449     public NodePointer createPath(JXPathContext context, Object value) {
450         setValue(value);
451         return this;
452     }
453 
454     /**
455      * Remove the node of the object graph this pointer points to.
456      */
457     public void remove() {
458         // It is a no-op
459 
460 //        System.err.println("REMOVING: " + asPath() + " " + getClass());
461 //        printPointerChain();
462     }
463 
464     /**
465      * Called by a child pointer when it needs to create a parent object.
466      * Must create an object described by this pointer and return
467      * a new pointer that properly describes the new object.
468      * @param context the owning JXPathContext
469      * @return created NodePointer
470      */
471     public NodePointer createPath(JXPathContext context) {
472         return this;
473     }
474 
475     /**
476      * Called by a child pointer if that child needs to assign the value
477      * supplied in the createPath(context, value) call to a non-existent
478      * node. This method may have to expand the collection in order to assign
479      * the element.
480      * @param context the owning JXPathCOntext
481      * @param name the QName at which a child should be created
482      * @param index child index.
483      * @param value node value to set
484      * @return created NodePointer
485      */
486     public NodePointer createChild(
487         JXPathContext context,
488         QName name,
489         int index,
490         Object value) {
491         throw new JXPathException("Cannot create an object for path "
492                 + asPath() + "/" + name + "[" + (index + 1) + "]"
493                 + ", operation is not allowed for this type of node");
494     }
495 
496     /**
497      * Called by a child pointer when it needs to create a parent object for a
498      * non-existent collection element. It may have to expand the collection,
499      * then create an element object and return a new pointer describing the
500      * newly created element.
501      * @param context the owning JXPathCOntext
502      * @param name the QName at which a child should be created
503      * @param index child index.
504      * @return created NodePointer
505      */
506     public NodePointer createChild(JXPathContext context, QName name, int index) {
507         throw new JXPathException("Cannot create an object for path "
508                 + asPath() + "/" + name + "[" + (index + 1) + "]"
509                 + ", operation is not allowed for this type of node");
510     }
511 
512     /**
513      * Called to create a non-existing attribute
514      * @param context the owning JXPathCOntext
515      * @param name the QName at which an attribute should be created
516      * @return created NodePointer
517      */
518     public NodePointer createAttribute(JXPathContext context, QName name) {
519         throw new JXPathException("Cannot create an attribute for path "
520                 + asPath() + "/@" + name
521                 + ", operation is not allowed for this type of node");
522     }
523 
524     /**
525      * If the Pointer has a parent, returns the parent's locale; otherwise
526      * returns the locale specified when this Pointer was created.
527      * @return Locale for this NodePointer
528      */
529     public Locale getLocale() {
530         if (locale == null && parent != null) {
531             locale = parent.getLocale();
532         }
533         return locale;
534     }
535 
536     /**
537      * Check whether our locale matches the specified language.
538      * @param lang String language to check
539      * @return true if the selected locale name starts
540      *              with the specified prefix <i>lang</i>, case-insensitive.
541      */
542     public boolean isLanguage(String lang) {
543         Locale loc = getLocale();
544         String name = loc.toString().replace('_', '-');
545         return name.toUpperCase(Locale.ENGLISH).startsWith(lang.toUpperCase(Locale.ENGLISH));
546     }
547 
548     /**
549      * Returns a NodeIterator that iterates over all children or all children
550      * that match the given NodeTest, starting with the specified one.
551      * @param test NodeTest to filter children
552      * @param reverse specified iteration direction
553      * @param startWith the NodePointer to start with
554      * @return NodeIterator
555      */
556     public NodeIterator childIterator(
557         NodeTest test,
558         boolean reverse,
559         NodePointer startWith) {
560         NodePointer valuePointer = getValuePointer();
561         return valuePointer == null || valuePointer == this ? null
562                 : valuePointer.childIterator(test, reverse, startWith);
563     }
564 
565     /**
566      * Returns a NodeIterator that iterates over all attributes of the current
567      * node matching the supplied node name (could have a wildcard).
568      * May return null if the object does not support the attributes.
569      * @param qname the attribute name to test
570      * @return NodeIterator
571      */
572     public NodeIterator attributeIterator(QName qname) {
573         NodePointer valuePointer = getValuePointer();
574         return valuePointer == null || valuePointer == this ? null
575                 : valuePointer.attributeIterator(qname);
576     }
577 
578     /**
579      * Returns a NodeIterator that iterates over all namespaces of the value
580      * currently pointed at.
581      * May return null if the object does not support the namespaces.
582      * @return NodeIterator
583      */
584     public NodeIterator namespaceIterator() {
585         return null;
586     }
587 
588     /**
589      * Returns a NodePointer for the specified namespace. Will return null
590      * if namespaces are not supported.
591      * Will return UNKNOWN_NAMESPACE if there is no such namespace.
592      * @param namespace incoming namespace
593      * @return NodePointer for <code>namespace</code>
594      */
595     public NodePointer namespacePointer(String namespace) {
596         return null;
597     }
598 
599     /**
600      * Decodes a namespace prefix to the corresponding URI.
601      * @param prefix prefix to decode
602      * @return String uri
603      */
604     public String getNamespaceURI(String prefix) {
605         return null;
606     }
607 
608     /**
609      * Returns the namespace URI associated with this Pointer.
610      * @return String uri
611      */
612     public String getNamespaceURI() {
613         return null;
614     }
615 
616     /**
617      * Returns true if the supplied prefix represents the
618      * default namespace in the context of the current node.
619      * @param prefix the prefix to check
620      * @return <code>true</code> if prefix is default
621      */
622     protected boolean isDefaultNamespace(String prefix) {
623         if (prefix == null) {
624             return true;
625         }
626 
627         String namespace = getNamespaceURI(prefix);
628         return namespace != null && namespace.equals(getDefaultNamespaceURI());
629     }
630 
631     /**
632      * Get the default ns uri
633      * @return String uri
634      */
635     protected String getDefaultNamespaceURI() {
636         return null;
637     }
638 
639     /**
640      * Locates a node by ID.
641      * @param context JXPathContext owning context
642      * @param id String id
643      * @return Pointer found
644      */
645     public Pointer getPointerByID(JXPathContext context, String id) {
646         return context.getPointerByID(id);
647     }
648 
649     /**
650      * Locates a node by key and value.
651      * @param context owning JXPathContext
652      * @param key key to search for
653      * @param value value to match
654      * @return Pointer found
655      */
656     public Pointer getPointerByKey(
657             JXPathContext context,
658             String key,
659             String value) {
660         return context.getPointerByKey(key, value);
661     }
662 
663     /**
664      * Find a NodeSet by key/value.
665      * @param context owning JXPathContext
666      * @param key key to search for
667      * @param value value to match
668      * @return NodeSet found
669      */
670     public NodeSet getNodeSetByKey(JXPathContext context, String key, Object value) {
671         return context.getNodeSetByKey(key, value);
672     }
673 
674     /**
675      * Returns an XPath that maps to this Pointer.
676      * @return String xpath expression
677      */
678     public String asPath() {
679         // If the parent of this node is a container, it is responsible
680         // for appended this node's part of the path.
681         if (parent != null && parent.isContainer()) {
682             return parent.asPath();
683         }
684 
685         StringBuffer buffer = new StringBuffer();
686         if (parent != null) {
687             buffer.append(parent.asPath());
688         }
689 
690         if (buffer.length() == 0
691             || buffer.charAt(buffer.length() - 1) != '/') {
692             buffer.append('/');
693         }
694         if (attribute) {
695             buffer.append('@');
696         }
697         buffer.append(getName());
698 
699         if (index != WHOLE_COLLECTION && isCollection()) {
700             buffer.append('[').append(index + 1).append(']');
701         }
702         return buffer.toString();
703     }
704 
705     /**
706      * Clone this NodePointer.
707      * @return cloned NodePointer
708      */
709     public Object clone() {
710         try {
711             NodePointer ptr = (NodePointer) super.clone();
712             if (parent != null) {
713                 ptr.parent = (NodePointer) parent.clone();
714             }
715             return ptr;
716         }
717         catch (CloneNotSupportedException ex) {
718             // Of course it is supported
719             ex.printStackTrace();
720         }
721         return null;
722     }
723 
724     public String toString() {
725         return asPath();
726     }
727 
728     public int compareTo(Object object) {
729         if (object == this) {
730             return 0;
731         }
732         // Let it throw a ClassCastException
733         NodePointer pointer = (NodePointer) object;
734         if (safeEquals(parent, pointer.parent)) {
735             return parent == null ? 0 : parent.compareChildNodePointers(this, pointer);
736         }
737 
738         // Task 1: find the common parent
739         int depth1 = 0;
740         NodePointer p1 = this;
741         HashSet parents1 = new HashSet();
742         while (p1 != null) {
743             depth1++;
744             p1 = p1.parent;
745             if (p1 != null) {
746                 parents1.add(p1);
747             }
748         }
749         boolean commonParentFound = false;
750         int depth2 = 0;
751         NodePointer p2 = pointer;
752         while (p2 != null) {
753             depth2++;
754             p2 = p2.parent;
755             if (parents1.contains(p2)) {
756                 commonParentFound = true;
757             }
758         }
759         //nodes from different graphs are equal, else continue comparison:
760         return commonParentFound ? compareNodePointers(this, depth1, pointer, depth2) : 0;
761     }
762 
763     /**
764      * Compare node pointers.
765      * @param p1 pointer 1
766      * @param depth1 depth 1
767      * @param p2 pointer 2
768      * @param depth2 depth 2
769      * @return comparison result: (< 0) -> (p1 lt p2); (0) -> (p1 eq p2); (> 0) -> (p1 gt p2)
770      */
771     private int compareNodePointers(
772         NodePointer p1,
773         int depth1,
774         NodePointer p2,
775         int depth2) {
776         if (depth1 < depth2) {
777             int r = compareNodePointers(p1, depth1, p2.parent, depth2 - 1);
778             return r == 0 ? -1 : r;
779         }
780         if (depth1 > depth2) {
781             int r = compareNodePointers(p1.parent, depth1 - 1, p2, depth2);
782             return r == 0 ? 1 : r;
783         }
784         //henceforth depth1 == depth2:
785         if (safeEquals(p1, p2)) {
786             return 0;
787         }
788         if (depth1 == 1) {
789             throw new JXPathException(
790                     "Cannot compare pointers that do not belong to the same tree: '"
791                     + p1 + "' and '" + p2 + "'");
792         }
793         int r = compareNodePointers(p1.parent, depth1 - 1, p2.parent, depth2 - 1);
794         return r == 0 ? p1.parent.compareChildNodePointers(p1, p2) : r;
795     }
796 
797     /**
798      * Print internal structure of a pointer for debugging
799      */
800     public void printPointerChain() {
801         printDeep(this, "");
802     }
803 
804     /**
805      * Set the exceptionHandler of this NodePointer.
806      * @param exceptionHandler the ExceptionHandler to set
807      */
808     public void setExceptionHandler(ExceptionHandler exceptionHandler) {
809         this.exceptionHandler = exceptionHandler;
810     }
811 
812     /**
813      * Handle a Throwable using an installed ExceptionHandler, if available.
814      * Public to facilitate calling for RI support; not truly intended for public consumption.
815      * @param t to handle
816      * @param originator context
817      */
818     public void handle(Throwable t, NodePointer originator) {
819         if (exceptionHandler != null) {
820             exceptionHandler.handle(t, originator);
821             return;
822         }
823         if (parent != null) {
824             parent.handle(t, originator);
825         }
826     }
827 
828     /**
829      * Handle a Throwable using an installed ExceptionHandler, if available.
830      * Public to facilitate calling for RI support; not truly intended for public consumption.
831      * @param t to handle
832      */
833     public void handle(Throwable t) {
834         handle(t, this);
835     }
836 
837     /**
838      * Return a string escaping single and double quotes.
839      * @param string string to treat
840      * @return string with any necessary changes made.
841      */
842     protected String escape(String string) {
843         final char[] c = new char[] { '\'', '"' };
844         final String[] esc = new String[] { "&apos;", "&quot;" };
845         StringBuffer sb = null;
846         for (int i = 0; sb == null && i < c.length; i++) {
847             if (string.indexOf(c[i]) >= 0) {
848                 sb = new StringBuffer(string);
849             }
850         }
851         if (sb == null) {
852             return string;
853         }
854         for (int i = 0; i < c.length; i++) {
855             if (string.indexOf(c[i]) < 0) {
856                 continue;
857             }
858             int pos = 0;
859             while (pos < sb.length()) {
860                 if (sb.charAt(pos) == c[i]) {
861                     sb.replace(pos, pos + 1, esc[i]);
862                     pos += esc[i].length();
863                 }
864                 else {
865                     pos++;
866                 }
867             }
868         }
869         return sb.toString();
870     }
871 
872     /**
873      * Get the AbstractFactory associated with the specified JXPathContext.
874      * @param context JXPathContext
875      * @return AbstractFactory
876      */
877     protected AbstractFactory getAbstractFactory(JXPathContext context) {
878         AbstractFactory factory = context.getFactory();
879         if (factory == null) {
880             throw new JXPathException(
881                 "Factory is not set on the JXPathContext - cannot create path: "
882                     + asPath());
883         }
884         return factory;
885     }
886 
887     /**
888      * Print deep
889      * @param pointer to print
890      * @param indent indentation level
891      */
892     private static void printDeep(NodePointer pointer, String indent) {
893         if (indent.length() == 0) {
894             System.err.println(
895                 "POINTER: "
896                     + pointer
897                     + "("
898                     + pointer.getClass().getName()
899                     + ")");
900         }
901         else {
902             System.err.println(
903                 indent
904                     + " of "
905                     + pointer
906                     + "("
907                     + pointer.getClass().getName()
908                     + ")");
909         }
910         if (pointer.getImmediateParentPointer() != null) {
911             printDeep(pointer.getImmediateParentPointer(), indent + "  ");
912         }
913     }
914 
915     private static boolean safeEquals(Object o1, Object o2) {
916         return o1 == o2 || o1 != null && o1.equals(o2);
917     }
918 
919     /**
920      * Verify the structure of a given NodePointer.
921      * @param nodePointer to check
922      * @return nodePointer
923      * @throws JXPathNotFoundException
924      */
925     public static NodePointer verify(NodePointer nodePointer) {
926         if (!nodePointer.isActual()) {
927             // We need to differentiate between pointers representing
928             // a non-existing property and ones representing a property
929             // whose value is null.  In the latter case, the pointer
930             // is going to have isActual == false, but its parent,
931             // which is a non-node pointer identifying the bean property,
932             // will return isActual() == true.
933             NodePointer parent = nodePointer.getImmediateParentPointer();
934             if (parent == null
935                 || !parent.isContainer()
936                 || !parent.isActual()) {
937                 throw new JXPathNotFoundException("No value for xpath: " + nodePointer);
938             }
939         }
940         return nodePointer;
941     }
942 }