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    }