001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.jxpath.ri.model;
017:
018: import java.util.Locale;
019:
020: import org.apache.commons.jxpath.JXPathContext;
021: import org.apache.commons.jxpath.JXPathException;
022: import org.apache.commons.jxpath.Pointer;
023: import org.apache.commons.jxpath.ri.Compiler;
024: import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
025: import org.apache.commons.jxpath.ri.NamespaceResolver;
026: import org.apache.commons.jxpath.ri.QName;
027: import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
028: import org.apache.commons.jxpath.ri.compiler.NodeTest;
029: import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
030: import org.apache.commons.jxpath.ri.model.beans.NullPointer;
031:
032: /**
033: * Common superclass for Pointers of all kinds. A NodePointer maps to
034: * a deterministic XPath that represents the location of a node in an
035: * object graph. This XPath uses only simple axes: child, namespace and
036: * attribute and only simple, context-independent predicates.
037: *
038: * @author Dmitri Plotnikov
039: * @version $Revision: 1.25 $ $Date: 2004/04/01 02:55:32 $
040: */
041: public abstract class NodePointer implements Pointer {
042:
043: public static final int WHOLE_COLLECTION = Integer.MIN_VALUE;
044: protected int index = WHOLE_COLLECTION;
045: public static final String UNKNOWN_NAMESPACE = "<<unknown namespace>>";
046: private boolean attribute = false;
047: private transient Object rootNode;
048: private NamespaceResolver namespaceResolver;
049:
050: /**
051: * Allocates an entirely new NodePointer by iterating through all installed
052: * NodePointerFactories until it finds one that can create a pointer.
053: */
054: public static NodePointer newNodePointer(QName name, Object bean,
055: Locale locale) {
056: NodePointer pointer = null;
057: if (bean == null) {
058: pointer = new NullPointer(name, locale);
059: return pointer;
060: }
061:
062: NodePointerFactory[] factories = JXPathContextReferenceImpl
063: .getNodePointerFactories();
064: for (int i = 0; i < factories.length; i++) {
065: pointer = factories[i]
066: .createNodePointer(name, bean, locale);
067: if (pointer != null) {
068: return pointer;
069: }
070: }
071: throw new JXPathException(
072: "Could not allocate a NodePointer for object of "
073: + bean.getClass());
074: }
075:
076: /**
077: * Allocates an new child NodePointer by iterating through all installed
078: * NodePointerFactories until it finds one that can create a pointer.
079: */
080: public static NodePointer newChildNodePointer(NodePointer parent,
081: QName name, Object bean) {
082: NodePointerFactory[] factories = JXPathContextReferenceImpl
083: .getNodePointerFactories();
084: for (int i = 0; i < factories.length; i++) {
085: NodePointer pointer = factories[i].createNodePointer(
086: parent, name, bean);
087: if (pointer != null) {
088: return pointer;
089: }
090: }
091: throw new JXPathException(
092: "Could not allocate a NodePointer for object of "
093: + bean.getClass());
094: }
095:
096: protected NodePointer parent;
097: protected Locale locale;
098:
099: // private NamespaceManager namespaceManager;
100:
101: protected NodePointer(NodePointer parent) {
102: this .parent = parent;
103: }
104:
105: protected NodePointer(NodePointer parent, Locale locale) {
106: this .parent = parent;
107: this .locale = locale;
108: }
109:
110: public NamespaceResolver getNamespaceResolver() {
111: if (namespaceResolver == null && parent != null) {
112: namespaceResolver = parent.getNamespaceResolver();
113: }
114: return namespaceResolver;
115: }
116:
117: public void setNamespaceResolver(NamespaceResolver namespaceResolver) {
118: this .namespaceResolver = namespaceResolver;
119: }
120:
121: public NodePointer getParent() {
122: NodePointer pointer = parent;
123: while (pointer != null && pointer.isContainer()) {
124: pointer = pointer.getImmediateParentPointer();
125: }
126: return pointer;
127: }
128:
129: public NodePointer getImmediateParentPointer() {
130: return parent;
131: }
132:
133: /**
134: * Set to true if the pointer represents the "attribute::" axis.
135: */
136: public void setAttribute(boolean attribute) {
137: this .attribute = attribute;
138: }
139:
140: /**
141: * Returns true if the pointer represents the "attribute::" axis.
142: */
143: public boolean isAttribute() {
144: return attribute;
145: }
146:
147: /**
148: * Returns true if this Pointer has no parent.
149: */
150: public boolean isRoot() {
151: return parent == null;
152: }
153:
154: /**
155: * If true, this node does not have children
156: */
157: public abstract boolean isLeaf();
158:
159: /**
160: * @deprecated Please use !isContainer()
161: */
162: public boolean isNode() {
163: return !isContainer();
164: }
165:
166: /**
167: * If true, this node is axiliary and can only be used as an intermediate in
168: * the chain of pointers.
169: */
170: public boolean isContainer() {
171: return false;
172: }
173:
174: /**
175: * If the pointer represents a collection, the index identifies
176: * an element of that collection. The default value of <code>index</code>
177: * is <code>WHOLE_COLLECTION</code>, which just means that the pointer
178: * is not indexed at all.
179: * Note: the index on NodePointer starts with 0, not 1.
180: */
181: public int getIndex() {
182: return index;
183: }
184:
185: public void setIndex(int index) {
186: this .index = index;
187: }
188:
189: /**
190: * Returns <code>true</code> if the value of the pointer is an array or
191: * a Collection.
192: */
193: public abstract boolean isCollection();
194:
195: /**
196: * If the pointer represents a collection (or collection element),
197: * returns the length of the collection.
198: * Otherwise returns 1 (even if the value is null).
199: */
200: public abstract int getLength();
201:
202: /**
203: * By default, returns <code>getNode()</code>, can be overridden to
204: * return a "canonical" value, like for instance a DOM element should
205: * return its string value.
206: */
207: public Object getValue() {
208: NodePointer valuePointer = getValuePointer();
209: if (valuePointer != this ) {
210: return valuePointer.getValue();
211: }
212: // Default behavior is to return the same as getNode()
213: return getNode();
214: }
215:
216: /**
217: * If this pointer manages a transparent container, like a variable,
218: * this method returns the pointer to the contents.
219: * Only an auxiliary (non-node) pointer can (and should) return a
220: * value pointer other than itself.
221: * Note that you probably don't want to override
222: * <code>getValuePointer()</code> directly. Override the
223: * <code>getImmediateValuePointer()</code> method instead. The
224: * <code>getValuePointer()</code> method is calls
225: * <code>getImmediateValuePointer()</code> and, if the result is not
226: * <code>this</code>, invokes <code>getValuePointer()</code> recursively.
227: * The idea here is to open all nested containers. Let's say we have a
228: * container within a container within a container. The
229: * <code>getValuePointer()</code> method should then open all those
230: * containers and return the pointer to the ultimate contents. It does so
231: * with the above recursion.
232: */
233: public NodePointer getValuePointer() {
234: NodePointer ivp = getImmediateValuePointer();
235: if (ivp != this ) {
236: return ivp.getValuePointer();
237: }
238: return this ;
239: }
240:
241: /**
242: * @see #getValuePointer()
243: *
244: * @return NodePointer is either <code>this</code> or a pointer
245: * for the immediately contained value.
246: */
247: public NodePointer getImmediateValuePointer() {
248: return this ;
249: }
250:
251: /**
252: * An actual pointer points to an existing part of an object graph, even
253: * if it is null. A non-actual pointer represents a part that does not exist
254: * at all.
255: * For instance consider the pointer "/address/street".
256: * If both <em>address</em> and <em>street</em> are not null,
257: * the pointer is actual.
258: * If <em>address</em> is not null, but <em>street</em> is null,
259: * the pointer is still actual.
260: * If <em>address</em> is null, the pointer is not actual.
261: * (In JavaBeans) if <em>address</em> is not a property of the root bean,
262: * a Pointer for this path cannot be obtained at all - actual or otherwise.
263: */
264: public boolean isActual() {
265: if (index == WHOLE_COLLECTION) {
266: return true;
267: } else {
268: return index >= 0 && index < getLength();
269: }
270: }
271:
272: /**
273: * Returns the name of this node. Can be null.
274: */
275: public abstract QName getName();
276:
277: /**
278: * Returns the value represented by the pointer before indexing.
279: * So, if the node represents an element of a collection, this
280: * method returns the collection itself.
281: */
282: public abstract Object getBaseValue();
283:
284: /**
285: * Returns the object the pointer points to; does not convert it
286: * to a "canonical" type.
287: *
288: * @deprecated 1.1 Please use getNode()
289: */
290: public Object getNodeValue() {
291: return getNode();
292: }
293:
294: /**
295: * Returns the object the pointer points to; does not convert it
296: * to a "canonical" type. Opens containers, properties etc and returns
297: * the ultimate contents.
298: */
299: public Object getNode() {
300: return getValuePointer().getImmediateNode();
301: }
302:
303: public Object getRootNode() {
304: if (rootNode == null) {
305: if (parent != null) {
306: rootNode = parent.getRootNode();
307: } else {
308: rootNode = getImmediateNode();
309: }
310: }
311: return rootNode;
312: }
313:
314: /**
315: * Returns the object the pointer points to; does not convert it
316: * to a "canonical" type.
317: */
318: public abstract Object getImmediateNode();
319:
320: /**
321: * Converts the value to the required type and changes the corresponding
322: * object to that value.
323: */
324: public abstract void setValue(Object value);
325:
326: /**
327: * Compares two child NodePointers and returns a positive number,
328: * zero or a positive number according to the order of the pointers.
329: */
330: public abstract int compareChildNodePointers(NodePointer pointer1,
331: NodePointer pointer2);
332:
333: /**
334: * Checks if this Pointer matches the supplied NodeTest.
335: */
336: public boolean testNode(NodeTest test) {
337: if (test == null) {
338: return true;
339: } else if (test instanceof NodeNameTest) {
340: if (isContainer()) {
341: return false;
342: }
343: NodeNameTest nodeNameTest = (NodeNameTest) test;
344: QName testName = nodeNameTest.getNodeName();
345: QName nodeName = getName();
346: if (nodeName == null) {
347: return false;
348: }
349:
350: String testPrefix = testName.getPrefix();
351: String nodePrefix = nodeName.getPrefix();
352: if (!equalStrings(testPrefix, nodePrefix)) {
353: String testNS = getNamespaceURI(testPrefix);
354: String nodeNS = getNamespaceURI(nodePrefix);
355: if (!equalStrings(testNS, nodeNS)) {
356: return false;
357: }
358: }
359: if (nodeNameTest.isWildcard()) {
360: return true;
361: }
362: return testName.getName().equals(nodeName.getName());
363: } else if (test instanceof NodeTypeTest) {
364: if (((NodeTypeTest) test).getNodeType() == Compiler.NODE_TYPE_NODE) {
365: return isNode();
366: }
367: }
368: return false;
369: }
370:
371: private static boolean equalStrings(String s1, String s2) {
372: if (s1 == null && s2 != null) {
373: return false;
374: }
375: if (s1 != null && !s1.equals(s2)) {
376: return false;
377: }
378: return true;
379: }
380:
381: /**
382: * Called directly by JXPathContext. Must create path and
383: * set value.
384: */
385: public NodePointer createPath(JXPathContext context, Object value) {
386: setValue(value);
387: return this ;
388: }
389:
390: /**
391: * Remove the node of the object graph this pointer points to.
392: */
393: public void remove() {
394: // It is a no-op
395:
396: // System.err.println("REMOVING: " + asPath() + " " + getClass());
397: // printPointerChain();
398: }
399:
400: /**
401: * Called by a child pointer when it needs to create a parent object.
402: * Must create an object described by this pointer and return
403: * a new pointer that properly describes the new object.
404: */
405: public NodePointer createPath(JXPathContext context) {
406: return this ;
407: }
408:
409: /**
410: * Called by a child pointer if that child needs to assign the value
411: * supplied in the createPath(context, value) call to a non-existent
412: * node. This method may have to expand the collection in order to assign
413: * the element.
414: */
415: public NodePointer createChild(JXPathContext context, QName name,
416: int index, Object value) {
417: throw new JXPathException("Cannot create an object for path "
418: + asPath() + "/" + name + "[" + (index + 1) + "]"
419: + ", operation is not allowed for this type of node");
420: }
421:
422: /**
423: * Called by a child pointer when it needs to create a parent object
424: * for a non-existent collection element. It may have to expand the
425: * collection, then create an element object and return a new pointer
426: * describing the newly created element.
427: */
428: public NodePointer createChild(JXPathContext context, QName name,
429: int index) {
430: throw new JXPathException("Cannot create an object for path "
431: + asPath() + "/" + name + "[" + (index + 1) + "]"
432: + ", operation is not allowed for this type of node");
433: }
434:
435: /**
436: * Called to create a non-existing attribute
437: */
438: public NodePointer createAttribute(JXPathContext context, QName name) {
439: throw new JXPathException(
440: "Cannot create an attribute for path "
441: + asPath()
442: + "/@"
443: + name
444: + ", operation is not allowed for this type of node");
445: }
446:
447: /**
448: * If the Pointer has a parent, returns the parent's locale;
449: * otherwise returns the locale specified when this Pointer
450: * was created.
451: */
452: public Locale getLocale() {
453: if (locale == null) {
454: if (parent != null) {
455: locale = parent.getLocale();
456: }
457: }
458: return locale;
459: }
460:
461: /**
462: * Returns true if the selected locale name starts
463: * with the specified prefix <i>lang</i>, case-insensitive.
464: */
465: public boolean isLanguage(String lang) {
466: Locale loc = getLocale();
467: String name = loc.toString().replace('_', '-');
468: return name.toUpperCase().startsWith(lang.toUpperCase());
469: }
470:
471: // /**
472: // * Installs the supplied manager as the namespace manager for this node
473: // * pointer. The {@link #getNamespaceURI(String) getNamespaceURI(prefix)}
474: // * uses this manager to resolve namespace prefixes.
475: // *
476: // * @param namespaceManager
477: // */
478: // public void setNamespaceManager(NamespaceManager namespaceManager) {
479: // this.namespaceManager = namespaceManager;
480: // }
481: //
482: // public NamespaceManager getNamespaceManager() {
483: // if (namespaceManager != null) {
484: // return namespaceManager;
485: // }
486: // if (parent != null) {
487: // return parent.getNamespaceManager();
488: // }
489: // return null;
490: // }
491: //
492: /**
493: * Returns a NodeIterator that iterates over all children or all children
494: * that match the given NodeTest, starting with the specified one.
495: */
496: public NodeIterator childIterator(NodeTest test, boolean reverse,
497: NodePointer startWith) {
498: NodePointer valuePointer = getValuePointer();
499: if (valuePointer != null && valuePointer != this ) {
500: return valuePointer.childIterator(test, reverse, startWith);
501: }
502: return null;
503: }
504:
505: /**
506: * Returns a NodeIterator that iterates over all attributes of the current
507: * node matching the supplied node name (could have a wildcard).
508: * May return null if the object does not support the attributes.
509: */
510: public NodeIterator attributeIterator(QName qname) {
511: NodePointer valuePointer = getValuePointer();
512: if (valuePointer != null && valuePointer != this ) {
513: return valuePointer.attributeIterator(qname);
514: }
515: return null;
516: }
517:
518: /**
519: * Returns a NodeIterator that iterates over all namespaces of the value
520: * currently pointed at.
521: * May return null if the object does not support the namespaces.
522: */
523: public NodeIterator namespaceIterator() {
524: return null;
525: }
526:
527: /**
528: * Returns a NodePointer for the specified namespace. Will return null
529: * if namespaces are not supported.
530: * Will return UNKNOWN_NAMESPACE if there is no such namespace.
531: */
532: public NodePointer namespacePointer(String namespace) {
533: return null;
534: }
535:
536: /**
537: * Decodes a namespace prefix to the corresponding URI.
538: */
539: public String getNamespaceURI(String prefix) {
540: return null;
541: }
542:
543: /**
544: * Returns the namespace URI associated with this Pointer.
545: */
546: public String getNamespaceURI() {
547: return null;
548: }
549:
550: /**
551: * Returns true if the supplied prefix represents the
552: * default namespace in the context of the current node.
553: */
554: protected boolean isDefaultNamespace(String prefix) {
555: if (prefix == null) {
556: return true;
557: }
558:
559: String namespace = getNamespaceURI(prefix);
560: if (namespace == null) {
561: return false; // undefined namespace
562: }
563:
564: return namespace.equals(getDefaultNamespaceURI());
565: }
566:
567: protected String getDefaultNamespaceURI() {
568: return null;
569: }
570:
571: /**
572: * Locates a node by ID.
573: */
574: public Pointer getPointerByID(JXPathContext context, String id) {
575: return context.getPointerByID(id);
576: }
577:
578: /**
579: * Locates a node by key and value.
580: */
581: public Pointer getPointerByKey(JXPathContext context, String key,
582: String value) {
583: return context.getPointerByKey(key, value);
584: }
585:
586: /**
587: * Returns an XPath that maps to this Pointer.
588: */
589: public String asPath() {
590: // If the parent of this node is a container, it is responsible
591: // for appended this node's part of the path.
592: if (parent != null && parent.isContainer()) {
593: return parent.asPath();
594: }
595:
596: StringBuffer buffer = new StringBuffer();
597: if (parent != null) {
598: buffer.append(parent.asPath());
599: }
600:
601: if (buffer.length() == 0
602: || buffer.charAt(buffer.length() - 1) != '/') {
603: buffer.append('/');
604: }
605: if (attribute) {
606: buffer.append('@');
607: }
608: buffer.append(getName());
609:
610: if (index != WHOLE_COLLECTION && isCollection()) {
611: buffer.append('[').append(index + 1).append(']');
612: }
613: return buffer.toString();
614: }
615:
616: public Object clone() {
617: try {
618: NodePointer ptr = (NodePointer) super .clone();
619: if (parent != null) {
620: ptr.parent = (NodePointer) parent.clone();
621: }
622: return ptr;
623: } catch (CloneNotSupportedException ex) {
624: // Of course it is supported
625: ex.printStackTrace();
626: }
627: return null;
628: }
629:
630: public String toString() {
631: return asPath();
632: }
633:
634: public int compareTo(Object object) {
635: // Let it throw a ClassCastException
636: NodePointer pointer = (NodePointer) object;
637: if (parent == pointer.parent) {
638: if (parent == null) {
639: return 0;
640: }
641: return parent.compareChildNodePointers(this , pointer);
642: }
643:
644: // Task 1: find the common parent
645: int depth1 = 0;
646: NodePointer p1 = this ;
647: while (p1 != null) {
648: depth1++;
649: p1 = p1.parent;
650: }
651: int depth2 = 0;
652: NodePointer p2 = pointer;
653: while (p2 != null) {
654: depth2++;
655: p2 = p2.parent;
656: }
657: return compareNodePointers(this , depth1, pointer, depth2);
658: }
659:
660: private int compareNodePointers(NodePointer p1, int depth1,
661: NodePointer p2, int depth2) {
662: if (depth1 < depth2) {
663: int r = compareNodePointers(p1, depth1, p2.parent,
664: depth2 - 1);
665: if (r != 0) {
666: return r;
667: }
668: return -1;
669: } else if (depth1 > depth2) {
670: int r = compareNodePointers(p1.parent, depth1 - 1, p2,
671: depth2);
672: if (r != 0) {
673: return r;
674: }
675: return 1;
676: }
677: if (p1 == null && p2 == null) {
678: return 0;
679: }
680:
681: if (p1 != null && p1.equals(p2)) {
682: return 0;
683: }
684:
685: if (depth1 == 1) {
686: throw new JXPathException(
687: "Cannot compare pointers that do not belong to the same tree: '"
688: + p1 + "' and '" + p2 + "'");
689: }
690: int r = compareNodePointers(p1.parent, depth1 - 1, p2.parent,
691: depth2 - 1);
692: if (r != 0) {
693: return r;
694: }
695:
696: return p1.parent.compareChildNodePointers(p1, p2);
697: }
698:
699: /**
700: * Print internal structure of a pointer for debugging
701: */
702: public void printPointerChain() {
703: printDeep(this , "");
704: }
705:
706: private static void printDeep(NodePointer pointer, String indent) {
707: if (indent.length() == 0) {
708: System.err.println("POINTER: " + pointer + "("
709: + pointer.getClass().getName() + ")");
710: } else {
711: System.err.println(indent + " of " + pointer + "("
712: + pointer.getClass().getName() + ")");
713: }
714: if (pointer.getImmediateParentPointer() != null) {
715: printDeep(pointer.getImmediateParentPointer(), indent
716: + " ");
717: }
718: }
719: }
|