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