KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > jxpath > ri > model > NodePointer


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.commons.jxpath.ri.model;
17
18 import java.util.Locale JavaDoc;
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 JavaDoc UNKNOWN_NAMESPACE = "<<unknown namespace>>";
46     private boolean attribute = false;
47     private transient Object JavaDoc 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 JavaDoc bean,
57         Locale JavaDoc 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 JavaDoc 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 JavaDoc locale;
103 // private NamespaceManager namespaceManager;
104

105     protected NodePointer(NodePointer parent) {
106         this.parent = parent;
107     }
108
109     protected NodePointer(NodePointer parent, Locale JavaDoc 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 JavaDoc getValue() {
212         NodePointer valuePointer = getValuePointer();
213         if (valuePointer != this) {
214             return valuePointer.getValue();
215         }
216         // Default behavior is to return the same as getNode()
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 JavaDoc 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 JavaDoc 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 JavaDoc getNode() {
305         return getValuePointer().getImmediateNode();
306     }
307     
308     public Object JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc testPrefix = testName.getPrefix();
358             String JavaDoc nodePrefix = nodeName.getPrefix();
359             if (!equalStrings(testPrefix, nodePrefix)) {
360                 String JavaDoc testNS = getNamespaceURI(testPrefix);
361                 String JavaDoc 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 JavaDoc s1, String JavaDoc 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 JavaDoc 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         // It is a no-op
404

405 // System.err.println("REMOVING: " + asPath() + " " + getClass());
406
// printPointerChain();
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 JavaDoc 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 JavaDoc 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 JavaDoc lang) {
492         Locale JavaDoc loc = getLocale();
493         String JavaDoc 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
// public void setNamespaceManager(NamespaceManager namespaceManager) {
505
// this.namespaceManager = namespaceManager;
506
// }
507
//
508
// public NamespaceManager getNamespaceManager() {
509
// if (namespaceManager != null) {
510
// return namespaceManager;
511
// }
512
// if (parent != null) {
513
// return parent.getNamespaceManager();
514
// }
515
// return null;
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 JavaDoc namespace) {
562         return null;
563     }
564
565     /**
566      * Decodes a namespace prefix to the corresponding URI.
567      */

568     public String JavaDoc getNamespaceURI(String JavaDoc prefix) {
569         return null;
570     }
571
572     /**
573      * Returns the namespace URI associated with this Pointer.
574      */

575     public String JavaDoc 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 JavaDoc prefix) {
584         if (prefix == null) {
585             return true;
586         }
587
588         String JavaDoc namespace = getNamespaceURI(prefix);
589         if (namespace == null) {
590             return false; // undefined namespace
591
}
592
593         return namespace.equals(getDefaultNamespaceURI());
594     }
595
596     protected String JavaDoc getDefaultNamespaceURI() {
597         return null;
598     }
599
600     /**
601      * Locates a node by ID.
602      */

603     public Pointer getPointerByID(JXPathContext context, String JavaDoc 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 JavaDoc key,
613         String JavaDoc value)
614     {
615         return context.getPointerByKey(key, value);
616     }
617
618     /**
619      * Returns an XPath that maps to this Pointer.
620      */

621     public String JavaDoc asPath() {
622         // If the parent of this node is a container, it is responsible
623
// for appended this node's part of the path.
624
if (parent != null && parent.isContainer()) {
625             return parent.asPath();
626         }
627
628         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
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 JavaDoc 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 JavaDoc ex) {
657             // Of course it is supported
658
ex.printStackTrace();
659         }
660         return null;
661     }
662
663     public String JavaDoc toString() {
664         return asPath();
665     }
666
667     public int compareTo(Object JavaDoc object) {
668         // Let it throw a ClassCastException
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         // Task 1: find the common parent
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 JavaDoc 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 }
Popular Tags