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 }