View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jxpath.ri.model;
19  
20  import java.util.HashSet;
21  import java.util.Locale;
22  
23  import org.apache.commons.jxpath.AbstractFactory;
24  import org.apache.commons.jxpath.ExceptionHandler;
25  import org.apache.commons.jxpath.JXPathContext;
26  import org.apache.commons.jxpath.JXPathException;
27  import org.apache.commons.jxpath.JXPathNotFoundException;
28  import org.apache.commons.jxpath.NodeSet;
29  import org.apache.commons.jxpath.Pointer;
30  import org.apache.commons.jxpath.ri.Compiler;
31  import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
32  import org.apache.commons.jxpath.ri.NamespaceResolver;
33  import org.apache.commons.jxpath.ri.QName;
34  import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
35  import org.apache.commons.jxpath.ri.compiler.NodeTest;
36  import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
37  import org.apache.commons.jxpath.ri.model.beans.NullPointer;
38  
39  /**
40   * 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
41   * XPath uses only simple axes: child, namespace and attribute and only simple, context-independent predicates.
42   */
43  public abstract class NodePointer implements Pointer {
44  
45      /** Serialization version */
46      private static final long serialVersionUID = 8117201322861007777L;
47      /** Whole collection index. */
48      public static final int WHOLE_COLLECTION = Integer.MIN_VALUE;
49      /** Constant to indicate unknown namespace */
50      public static final String UNKNOWN_NAMESPACE = "<<unknown namespace>>";
51  
52      /**
53       * Allocates an new child NodePointer by iterating through all installed NodePointerFactories until it finds one that can create a pointer.
54       *
55       * @param parent pointer
56       * @param qName   QName
57       * @param bean   Object
58       * @return NodePointer
59       */
60      public static NodePointer newChildNodePointer(final NodePointer parent, final QName qName, final Object bean) {
61          final NodePointerFactory[] factories = JXPathContextReferenceImpl.getNodePointerFactories();
62          for (final NodePointerFactory element : factories) {
63              final NodePointer pointer = element.createNodePointer(parent, qName, bean);
64              if (pointer != null) {
65                  return pointer;
66              }
67          }
68          throw new JXPathException("Could not allocate a NodePointer for object of " + bean.getClass());
69      }
70  
71      /**
72       * Allocates an entirely new NodePointer by iterating through all installed NodePointerFactories until it finds one that can create a pointer.
73       *
74       * @param qName   QName
75       * @param bean   Object
76       * @param locale Locale
77       * @return NodePointer
78       */
79      public static NodePointer newNodePointer(final QName qName, final Object bean, final Locale locale) {
80          NodePointer pointer;
81          if (bean == null) {
82              pointer = new NullPointer(qName, locale);
83              return pointer;
84          }
85          final NodePointerFactory[] factories = JXPathContextReferenceImpl.getNodePointerFactories();
86          for (final NodePointerFactory element : factories) {
87              pointer = element.createNodePointer(qName, bean, locale);
88              if (pointer != null) {
89                  return pointer;
90              }
91          }
92          throw new JXPathException("Could not allocate a NodePointer for object of " + bean.getClass());
93      }
94  
95      /**
96       * Print deep
97       *
98       * @param pointer to print
99       * @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 }