1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.jxpath.ri.model;
18
19 import java.util.HashSet;
20 import java.util.Locale;
21
22 import org.apache.commons.jxpath.AbstractFactory;
23 import org.apache.commons.jxpath.ExceptionHandler;
24 import org.apache.commons.jxpath.JXPathContext;
25 import org.apache.commons.jxpath.JXPathException;
26 import org.apache.commons.jxpath.JXPathNotFoundException;
27 import org.apache.commons.jxpath.NodeSet;
28 import org.apache.commons.jxpath.Pointer;
29 import org.apache.commons.jxpath.ri.Compiler;
30 import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
31 import org.apache.commons.jxpath.ri.NamespaceResolver;
32 import org.apache.commons.jxpath.ri.QName;
33 import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
34 import org.apache.commons.jxpath.ri.compiler.NodeTest;
35 import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
36 import org.apache.commons.jxpath.ri.model.beans.NullPointer;
37
38 /**
39 * Common superclass for Pointers of all kinds. A NodePointer maps to
40 * a deterministic XPath that represents the location of a node in an
41 * object graph. This XPath uses only simple axes: child, namespace and
42 * attribute and only simple, context-independent predicates.
43 *
44 * @author Dmitri Plotnikov
45 * @version $Revision: 1234255 $ $Date: 2012-01-20 22:11:46 -0500 (Fri, 20 Jan 2012) $
46 */
47 public abstract class NodePointer implements Pointer {
48
49 /** Serialization version */
50 private static final long serialVersionUID = 8117201322861007777L;
51
52 /** Whole collection index. */
53 public static final int WHOLE_COLLECTION = Integer.MIN_VALUE;
54
55 /** Constant to indicate unknown namespace */
56 public static final String UNKNOWN_NAMESPACE = "<<unknown namespace>>";
57
58 /** Index for this NodePointer */
59 protected int index = WHOLE_COLLECTION;
60
61 private boolean attribute = false;
62 private NamespaceResolver namespaceResolver;
63 private ExceptionHandler exceptionHandler;
64 private transient Object rootNode;
65
66 /**
67 * Allocates an entirely new NodePointer by iterating through all installed
68 * NodePointerFactories until it finds one that can create a pointer.
69 * @param name QName
70 * @param bean Object
71 * @param locale Locale
72 * @return NodePointer
73 */
74 public static NodePointer newNodePointer(
75 QName name,
76 Object bean,
77 Locale locale) {
78 NodePointer pointer = null;
79 if (bean == null) {
80 pointer = new NullPointer(name, locale);
81 return pointer;
82 }
83
84 NodePointerFactory[] factories =
85 JXPathContextReferenceImpl.getNodePointerFactories();
86 for (int i = 0; i < factories.length; i++) {
87 pointer = factories[i].createNodePointer(name, bean, locale);
88 if (pointer != null) {
89 return pointer;
90 }
91 }
92 throw new JXPathException(
93 "Could not allocate a NodePointer for object of "
94 + bean.getClass());
95 }
96
97 /**
98 * Allocates an new child NodePointer by iterating through all installed
99 * 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 }