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