    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}