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