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 = { "'", """ };
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 }