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