1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.jxpath.ri.model;
17
18 import java.util.Locale;
19
20 import org.apache.commons.jxpath.JXPathContext;
21 import org.apache.commons.jxpath.JXPathException;
22 import org.apache.commons.jxpath.Pointer;
23 import org.apache.commons.jxpath.ri.Compiler;
24 import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
25 import org.apache.commons.jxpath.ri.NamespaceResolver;
26 import org.apache.commons.jxpath.ri.QName;
27 import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
28 import org.apache.commons.jxpath.ri.compiler.NodeTest;
29 import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
30 import org.apache.commons.jxpath.ri.model.beans.NullPointer;
31
32 /***
33 * Common superclass for Pointers of all kinds. A NodePointer maps to
34 * a deterministic XPath that represents the location of a node in an
35 * object graph. This XPath uses only simple axes: child, namespace and
36 * attribute and only simple, context-independent predicates.
37 *
38 * @author Dmitri Plotnikov
39 * @version $Revision: 1.25 $ $Date: 2004/04/01 02:55:32 $
40 */
41 public abstract class NodePointer implements Pointer {
42
43 public static final int WHOLE_COLLECTION = Integer.MIN_VALUE;
44 protected int index = WHOLE_COLLECTION;
45 public static final String UNKNOWN_NAMESPACE = "<<unknown namespace>>";
46 private boolean attribute = false;
47 private transient Object rootNode;
48 private NamespaceResolver namespaceResolver;
49
50 /***
51 * Allocates an entirely new NodePointer by iterating through all installed
52 * NodePointerFactories until it finds one that can create a pointer.
53 */
54 public static NodePointer newNodePointer(
55 QName name,
56 Object bean,
57 Locale locale)
58 {
59 NodePointer pointer = null;
60 if (bean == null) {
61 pointer = new NullPointer(name, locale);
62 return pointer;
63 }
64
65 NodePointerFactory[] factories =
66 JXPathContextReferenceImpl.getNodePointerFactories();
67 for (int i = 0; i < factories.length; i++) {
68 pointer = factories[i].createNodePointer(name, bean, locale);
69 if (pointer != null) {
70 return pointer;
71 }
72 }
73 throw new JXPathException(
74 "Could not allocate a NodePointer for object of "
75 + bean.getClass());
76 }
77
78 /***
79 * Allocates an new child NodePointer by iterating through all installed
80 * NodePointerFactories until it finds one that can create a pointer.
81 */
82 public static NodePointer newChildNodePointer(
83 NodePointer parent,
84 QName name,
85 Object bean)
86 {
87 NodePointerFactory[] factories =
88 JXPathContextReferenceImpl.getNodePointerFactories();
89 for (int i = 0; i < factories.length; i++) {
90 NodePointer pointer =
91 factories[i].createNodePointer(parent, name, bean);
92 if (pointer != null) {
93 return pointer;
94 }
95 }
96 throw new JXPathException(
97 "Could not allocate a NodePointer for object of "
98 + bean.getClass());
99 }
100
101 protected NodePointer parent;
102 protected Locale locale;
103
104
105 protected NodePointer(NodePointer parent) {
106 this.parent = parent;
107 }
108
109 protected NodePointer(NodePointer parent, Locale locale) {
110 this.parent = parent;
111 this.locale = locale;
112 }
113
114 public NamespaceResolver getNamespaceResolver() {
115 if (namespaceResolver == null && parent != null) {
116 namespaceResolver = parent.getNamespaceResolver();
117 }
118 return namespaceResolver;
119 }
120
121 public void setNamespaceResolver(NamespaceResolver namespaceResolver) {
122 this.namespaceResolver = namespaceResolver;
123 }
124
125 public NodePointer getParent() {
126 NodePointer pointer = parent;
127 while (pointer != null && pointer.isContainer()) {
128 pointer = pointer.getImmediateParentPointer();
129 }
130 return pointer;
131 }
132
133 public NodePointer getImmediateParentPointer() {
134 return parent;
135 }
136
137 /***
138 * Set to true if the pointer represents the "attribute::" axis.
139 */
140 public void setAttribute(boolean attribute) {
141 this.attribute = attribute;
142 }
143
144 /***
145 * Returns true if the pointer represents the "attribute::" axis.
146 */
147 public boolean isAttribute() {
148 return attribute;
149 }
150
151 /***
152 * Returns true if this Pointer has no parent.
153 */
154 public boolean isRoot() {
155 return parent == null;
156 }
157
158 /***
159 * If true, this node does not have children
160 */
161 public abstract boolean isLeaf();
162
163 /***
164 * @deprecated Please use !isContainer()
165 */
166 public boolean isNode() {
167 return !isContainer();
168 }
169
170 /***
171 * If true, this node is axiliary and can only be used as an intermediate in
172 * the chain of pointers.
173 */
174 public boolean isContainer() {
175 return false;
176 }
177
178 /***
179 * If the pointer represents a collection, the index identifies
180 * an element of that collection. The default value of <code>index</code>
181 * is <code>WHOLE_COLLECTION</code>, which just means that the pointer
182 * is not indexed at all.
183 * Note: the index on NodePointer starts with 0, not 1.
184 */
185 public int getIndex() {
186 return index;
187 }
188
189 public void setIndex(int index) {
190 this.index = index;
191 }
192
193 /***
194 * Returns <code>true</code> if the value of the pointer is an array or
195 * a Collection.
196 */
197 public abstract boolean isCollection();
198
199 /***
200 * If the pointer represents a collection (or collection element),
201 * returns the length of the collection.
202 * Otherwise returns 1 (even if the value is null).
203 */
204 public abstract int getLength();
205
206 /***
207 * By default, returns <code>getNode()</code>, can be overridden to
208 * return a "canonical" value, like for instance a DOM element should
209 * return its string value.
210 */
211 public Object getValue() {
212 NodePointer valuePointer = getValuePointer();
213 if (valuePointer != this) {
214 return valuePointer.getValue();
215 }
216
217 return getNode();
218 }
219
220 /***
221 * If this pointer manages a transparent container, like a variable,
222 * this method returns the pointer to the contents.
223 * Only an auxiliary (non-node) pointer can (and should) return a
224 * value pointer other than itself.
225 * Note that you probably don't want to override
226 * <code>getValuePointer()</code> directly. Override the
227 * <code>getImmediateValuePointer()</code> method instead. The
228 * <code>getValuePointer()</code> method is calls
229 * <code>getImmediateValuePointer()</code> and, if the result is not
230 * <code>this</code>, invokes <code>getValuePointer()</code> recursively.
231 * The idea here is to open all nested containers. Let's say we have a
232 * container within a container within a container. The
233 * <code>getValuePointer()</code> method should then open all those
234 * containers and return the pointer to the ultimate contents. It does so
235 * with the above recursion.
236 */
237 public NodePointer getValuePointer() {
238 NodePointer ivp = getImmediateValuePointer();
239 if (ivp != this) {
240 return ivp.getValuePointer();
241 }
242 return this;
243 }
244
245 /***
246 * @see #getValuePointer()
247 *
248 * @return NodePointer is either <code>this</code> or a pointer
249 * for the immediately contained value.
250 */
251 public NodePointer getImmediateValuePointer() {
252 return this;
253 }
254
255 /***
256 * An actual pointer points to an existing part of an object graph, even
257 * if it is null. A non-actual pointer represents a part that does not exist
258 * at all.
259 * For instance consider the pointer "/address/street".
260 * If both <em>address</em> and <em>street</em> are not null,
261 * the pointer is actual.
262 * If <em>address</em> is not null, but <em>street</em> is null,
263 * the pointer is still actual.
264 * If <em>address</em> is null, the pointer is not actual.
265 * (In JavaBeans) if <em>address</em> is not a property of the root bean,
266 * a Pointer for this path cannot be obtained at all - actual or otherwise.
267 */
268 public boolean isActual() {
269 if (index == WHOLE_COLLECTION) {
270 return true;
271 }
272 else {
273 return index >= 0 && index < getLength();
274 }
275 }
276
277 /***
278 * Returns the name of this node. Can be null.
279 */
280 public abstract QName getName();
281
282 /***
283 * Returns the value represented by the pointer before indexing.
284 * So, if the node represents an element of a collection, this
285 * method returns the collection itself.
286 */
287 public abstract Object getBaseValue();
288
289 /***
290 * Returns the object the pointer points to; does not convert it
291 * to a "canonical" type.
292 *
293 * @deprecated 1.1 Please use getNode()
294 */
295 public Object getNodeValue() {
296 return getNode();
297 }
298
299 /***
300 * Returns the object the pointer points to; does not convert it
301 * to a "canonical" type. Opens containers, properties etc and returns
302 * the ultimate contents.
303 */
304 public Object getNode() {
305 return getValuePointer().getImmediateNode();
306 }
307
308 public Object getRootNode() {
309 if (rootNode == null) {
310 if (parent != null) {
311 rootNode = parent.getRootNode();
312 }
313 else {
314 rootNode = getImmediateNode();
315 }
316 }
317 return rootNode;
318 }
319
320 /***
321 * Returns the object the pointer points to; does not convert it
322 * to a "canonical" type.
323 */
324 public abstract Object getImmediateNode();
325
326 /***
327 * Converts the value to the required type and changes the corresponding
328 * object to that value.
329 */
330 public abstract void setValue(Object value);
331
332 /***
333 * Compares two child NodePointers and returns a positive number,
334 * zero or a positive number according to the order of the pointers.
335 */
336 public abstract int compareChildNodePointers(
337 NodePointer pointer1, NodePointer pointer2);
338
339 /***
340 * Checks if this Pointer matches the supplied NodeTest.
341 */
342 public boolean testNode(NodeTest test) {
343 if (test == null) {
344 return true;
345 }
346 else if (test instanceof NodeNameTest) {
347 if (isContainer()) {
348 return false;
349 }
350 NodeNameTest nodeNameTest = (NodeNameTest) test;
351 QName testName = nodeNameTest.getNodeName();
352 QName nodeName = getName();
353 if (nodeName == null) {
354 return false;
355 }
356
357 String testPrefix = testName.getPrefix();
358 String nodePrefix = nodeName.getPrefix();
359 if (!equalStrings(testPrefix, nodePrefix)) {
360 String testNS = getNamespaceURI(testPrefix);
361 String nodeNS = getNamespaceURI(nodePrefix);
362 if (!equalStrings(testNS, nodeNS)) {
363 return false;
364 }
365 }
366 if (nodeNameTest.isWildcard()) {
367 return true;
368 }
369 return testName.getName().equals(nodeName.getName());
370 }
371 else if (test instanceof NodeTypeTest) {
372 if (((NodeTypeTest) test).getNodeType()
373 == Compiler.NODE_TYPE_NODE) {
374 return isNode();
375 }
376 }
377 return false;
378 }
379
380 private static boolean equalStrings(String s1, String s2) {
381 if (s1 == null && s2 != null) {
382 return false;
383 }
384 if (s1 != null && !s1.equals(s2)) {
385 return false;
386 }
387 return true;
388 }
389
390 /***
391 * Called directly by JXPathContext. Must create path and
392 * set value.
393 */
394 public NodePointer createPath(JXPathContext context, Object value) {
395 setValue(value);
396 return this;
397 }
398
399 /***
400 * Remove the node of the object graph this pointer points to.
401 */
402 public void remove() {
403
404
405
406
407 }
408
409 /***
410 * Called by a child pointer when it needs to create a parent object.
411 * Must create an object described by this pointer and return
412 * a new pointer that properly describes the new object.
413 */
414 public NodePointer createPath(JXPathContext context) {
415 return this;
416 }
417
418 /***
419 * Called by a child pointer if that child needs to assign the value
420 * supplied in the createPath(context, value) call to a non-existent
421 * node. This method may have to expand the collection in order to assign
422 * the element.
423 */
424 public NodePointer createChild(
425 JXPathContext context,
426 QName name,
427 int index,
428 Object value)
429 {
430 throw new JXPathException(
431 "Cannot create an object for path "
432 + asPath()
433 + "/"
434 + name
435 + "["
436 + (index + 1)
437 + "]"
438 + ", operation is not allowed for this type of node");
439 }
440
441 /***
442 * Called by a child pointer when it needs to create a parent object
443 * for a non-existent collection element. It may have to expand the
444 * collection, then create an element object and return a new pointer
445 * describing the newly created element.
446 */
447 public NodePointer createChild(
448 JXPathContext context,
449 QName name,
450 int index)
451 {
452 throw new JXPathException(
453 "Cannot create an object for path "
454 + asPath()
455 + "/"
456 + name
457 + "["
458 + (index + 1)
459 + "]"
460 + ", operation is not allowed for this type of node");
461 }
462
463 /***
464 * Called to create a non-existing attribute
465 */
466 public NodePointer createAttribute(JXPathContext context, QName name) {
467 throw new JXPathException(
468 "Cannot create an attribute for path "
469 + asPath() + "/@" + name
470 + ", operation is not allowed for this type of node");
471 }
472
473 /***
474 * If the Pointer has a parent, returns the parent's locale;
475 * otherwise returns the locale specified when this Pointer
476 * was created.
477 */
478 public Locale getLocale() {
479 if (locale == null) {
480 if (parent != null) {
481 locale = parent.getLocale();
482 }
483 }
484 return locale;
485 }
486
487 /***
488 * Returns true if the selected locale name starts
489 * with the specified prefix <i>lang</i>, case-insensitive.
490 */
491 public boolean isLanguage(String lang) {
492 Locale loc = getLocale();
493 String name = loc.toString().replace('_', '-');
494 return name.toUpperCase().startsWith(lang.toUpperCase());
495 }
496
497 /***
498 // * Installs the supplied manager as the namespace manager for this node
499 // * pointer. The {@link #getNamespaceURI(String) getNamespaceURI(prefix)}
500 // * uses this manager to resolve namespace prefixes.
501 // *
502 // * @param namespaceManager
503 // */
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518 /***
519 * Returns a NodeIterator that iterates over all children or all children
520 * that match the given NodeTest, starting with the specified one.
521 */
522 public NodeIterator childIterator(
523 NodeTest test,
524 boolean reverse,
525 NodePointer startWith)
526 {
527 NodePointer valuePointer = getValuePointer();
528 if (valuePointer != null && valuePointer != this) {
529 return valuePointer.childIterator(test, reverse, startWith);
530 }
531 return null;
532 }
533
534 /***
535 * Returns a NodeIterator that iterates over all attributes of the current
536 * node matching the supplied node name (could have a wildcard).
537 * May return null if the object does not support the attributes.
538 */
539 public NodeIterator attributeIterator(QName qname) {
540 NodePointer valuePointer = getValuePointer();
541 if (valuePointer != null && valuePointer != this) {
542 return valuePointer.attributeIterator(qname);
543 }
544 return null;
545 }
546
547 /***
548 * Returns a NodeIterator that iterates over all namespaces of the value
549 * currently pointed at.
550 * May return null if the object does not support the namespaces.
551 */
552 public NodeIterator namespaceIterator() {
553 return null;
554 }
555
556 /***
557 * Returns a NodePointer for the specified namespace. Will return null
558 * if namespaces are not supported.
559 * Will return UNKNOWN_NAMESPACE if there is no such namespace.
560 */
561 public NodePointer namespacePointer(String namespace) {
562 return null;
563 }
564
565 /***
566 * Decodes a namespace prefix to the corresponding URI.
567 */
568 public String getNamespaceURI(String prefix) {
569 return null;
570 }
571
572 /***
573 * Returns the namespace URI associated with this Pointer.
574 */
575 public String getNamespaceURI() {
576 return null;
577 }
578
579 /***
580 * Returns true if the supplied prefix represents the
581 * default namespace in the context of the current node.
582 */
583 protected boolean isDefaultNamespace(String prefix) {
584 if (prefix == null) {
585 return true;
586 }
587
588 String namespace = getNamespaceURI(prefix);
589 if (namespace == null) {
590 return false;
591 }
592
593 return namespace.equals(getDefaultNamespaceURI());
594 }
595
596 protected String getDefaultNamespaceURI() {
597 return null;
598 }
599
600 /***
601 * Locates a node by ID.
602 */
603 public Pointer getPointerByID(JXPathContext context, String id) {
604 return context.getPointerByID(id);
605 }
606
607 /***
608 * Locates a node by key and value.
609 */
610 public Pointer getPointerByKey(
611 JXPathContext context,
612 String key,
613 String value)
614 {
615 return context.getPointerByKey(key, value);
616 }
617
618 /***
619 * Returns an XPath that maps to this Pointer.
620 */
621 public String asPath() {
622
623
624 if (parent != null && parent.isContainer()) {
625 return parent.asPath();
626 }
627
628 StringBuffer buffer = new StringBuffer();
629 if (parent != null) {
630 buffer.append(parent.asPath());
631 }
632
633 if (buffer.length() == 0
634 || buffer.charAt(buffer.length() - 1) != '/') {
635 buffer.append('/');
636 }
637 if (attribute) {
638 buffer.append('@');
639 }
640 buffer.append(getName());
641
642 if (index != WHOLE_COLLECTION && isCollection()) {
643 buffer.append('[').append(index + 1).append(']');
644 }
645 return buffer.toString();
646 }
647
648 public Object clone() {
649 try {
650 NodePointer ptr = (NodePointer) super.clone();
651 if (parent != null) {
652 ptr.parent = (NodePointer) parent.clone();
653 }
654 return ptr;
655 }
656 catch (CloneNotSupportedException ex) {
657
658 ex.printStackTrace();
659 }
660 return null;
661 }
662
663 public String toString() {
664 return asPath();
665 }
666
667 public int compareTo(Object object) {
668
669 NodePointer pointer = (NodePointer) object;
670 if (parent == pointer.parent) {
671 if (parent == null) {
672 return 0;
673 }
674 return parent.compareChildNodePointers(this, pointer);
675 }
676
677
678 int depth1 = 0;
679 NodePointer p1 = this;
680 while (p1 != null) {
681 depth1++;
682 p1 = p1.parent;
683 }
684 int depth2 = 0;
685 NodePointer p2 = pointer;
686 while (p2 != null) {
687 depth2++;
688 p2 = p2.parent;
689 }
690 return compareNodePointers(this, depth1, pointer, depth2);
691 }
692
693 private int compareNodePointers(
694 NodePointer p1,
695 int depth1,
696 NodePointer p2,
697 int depth2)
698 {
699 if (depth1 < depth2) {
700 int r = compareNodePointers(p1, depth1, p2.parent, depth2 - 1);
701 if (r != 0) {
702 return r;
703 }
704 return -1;
705 }
706 else if (depth1 > depth2) {
707 int r = compareNodePointers(p1.parent, depth1 - 1, p2, depth2);
708 if (r != 0) {
709 return r;
710 }
711 return 1;
712 }
713 if (p1 == null && p2 == null) {
714 return 0;
715 }
716
717 if (p1 != null && p1.equals(p2)) {
718 return 0;
719 }
720
721 if (depth1 == 1) {
722 throw new JXPathException(
723 "Cannot compare pointers that do not belong to the same tree: '"
724 + p1
725 + "' and '"
726 + p2
727 + "'");
728 }
729 int r =
730 compareNodePointers(p1.parent, depth1 - 1, p2.parent, depth2 - 1);
731 if (r != 0) {
732 return r;
733 }
734
735 return p1.parent.compareChildNodePointers(p1, p2);
736 }
737
738 /***
739 * Print internal structure of a pointer for debugging
740 */
741 public void printPointerChain() {
742 printDeep(this, "");
743 }
744
745 private static void printDeep(NodePointer pointer, String indent) {
746 if (indent.length() == 0) {
747 System.err.println(
748 "POINTER: "
749 + pointer
750 + "("
751 + pointer.getClass().getName()
752 + ")");
753 }
754 else {
755 System.err.println(
756 indent
757 + " of "
758 + pointer
759 + "("
760 + pointer.getClass().getName()
761 + ")");
762 }
763 if (pointer.getImmediateParentPointer() != null) {
764 printDeep(pointer.getImmediateParentPointer(), indent + " ");
765 }
766 }
767 }