001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.jxpath.ri.model;
018
019import java.util.HashSet;
020import java.util.Locale;
021
022import org.apache.commons.jxpath.AbstractFactory;
023import org.apache.commons.jxpath.ExceptionHandler;
024import org.apache.commons.jxpath.JXPathContext;
025import org.apache.commons.jxpath.JXPathException;
026import org.apache.commons.jxpath.JXPathNotFoundException;
027import org.apache.commons.jxpath.NodeSet;
028import org.apache.commons.jxpath.Pointer;
029import org.apache.commons.jxpath.ri.Compiler;
030import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
031import org.apache.commons.jxpath.ri.NamespaceResolver;
032import org.apache.commons.jxpath.ri.QName;
033import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
034import org.apache.commons.jxpath.ri.compiler.NodeTest;
035import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
036import org.apache.commons.jxpath.ri.model.beans.NullPointer;
037
038/**
039 * Common superclass for Pointers of all kinds.  A NodePointer maps to
040 * a deterministic XPath that represents the location of a node in an
041 * object graph. This XPath uses only simple axes: child, namespace and
042 * attribute and only simple, context-independent predicates.
043 *
044 * @author Dmitri Plotnikov
045 * @version $Revision: 1234255 $ $Date: 2012-01-21 04:11:46 +0100 (Sa, 21 Jan 2012) $
046 */
047public abstract class NodePointer implements Pointer {
048
049    /** Serialization version */
050    private static final long serialVersionUID = 8117201322861007777L;
051
052    /** Whole collection index. */
053    public static final int WHOLE_COLLECTION = Integer.MIN_VALUE;
054
055    /** Constant to indicate unknown namespace */
056    public static final String UNKNOWN_NAMESPACE = "<<unknown namespace>>";
057
058    /** Index for this NodePointer */
059    protected int index = WHOLE_COLLECTION;
060
061    private boolean attribute = false;
062    private NamespaceResolver namespaceResolver;
063    private ExceptionHandler exceptionHandler;
064    private transient Object rootNode;
065
066    /**
067     * Allocates an entirely new NodePointer by iterating through all installed
068     * NodePointerFactories until it finds one that can create a pointer.
069     * @param name QName
070     * @param bean Object
071     * @param locale Locale
072     * @return NodePointer
073     */
074    public static NodePointer newNodePointer(
075        QName name,
076        Object bean,
077        Locale locale) {
078        NodePointer pointer = null;
079        if (bean == null) {
080            pointer = new NullPointer(name, locale);
081            return pointer;
082        }
083
084        NodePointerFactory[] factories =
085            JXPathContextReferenceImpl.getNodePointerFactories();
086        for (int i = 0; i < factories.length; i++) {
087            pointer = factories[i].createNodePointer(name, bean, locale);
088            if (pointer != null) {
089                return pointer;
090            }
091        }
092        throw new JXPathException(
093            "Could not allocate a NodePointer for object of "
094                + bean.getClass());
095    }
096
097    /**
098     * Allocates an new child NodePointer by iterating through all installed
099     * 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}