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