View Javadoc
1   /*
2    * $Header$
3    * $Revision$
4    * $Date$
5    *
6    * ====================================================================
7    *
8    * Copyright 2000-2002 bob mcwhirter & James Strachan.
9    * All rights reserved.
10   *
11   * Redistribution and use in source and binary forms, with or without
12   * modification, are permitted provided that the following conditions are
13   * met:
14   * 
15   *   * Redistributions of source code must retain the above copyright
16   *     notice, this list of conditions and the following disclaimer.
17   * 
18   *   * Redistributions in binary form must reproduce the above copyright
19   *     notice, this list of conditions and the following disclaimer in the
20   *     documentation and/or other materials provided with the distribution.
21   * 
22   *   * Neither the name of the Jaxen Project nor the names of its
23   *     contributors may be used to endorse or promote products derived 
24   *     from this software without specific prior written permission.
25   * 
26   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
27   * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28   * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
29   * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
30   * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
31   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
32   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
33   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37   *
38   * ====================================================================
39   * This software consists of voluntary contributions made by many 
40   * individuals on behalf of the Jaxen Project and was originally 
41   * created by bob mcwhirter <bob@werken.com> and 
42   * James Strachan <jstrachan@apache.org>.  For more information on the 
43   * Jaxen Project, please see <http://www.jaxen.org/>.
44   * 
45   * $Id$
46   */
47  
48  ////////////////////////////////////////////////////////////////////
49  // Inner class for a Namespace node.
50  ////////////////////////////////////////////////////////////////////
51  
52  package org.jaxen.dom;
53  
54  import java.lang.reflect.InvocationTargetException;
55  import java.lang.reflect.Method;
56  import java.util.HashMap;
57  
58  import org.jaxen.pattern.Pattern;
59  import org.w3c.dom.DOMException;
60  import org.w3c.dom.Document;
61  import org.w3c.dom.NamedNodeMap;
62  import org.w3c.dom.Node;
63  import org.w3c.dom.NodeList;
64  import org.w3c.dom.UserDataHandler;
65  
66  
67  /**
68   * Extension DOM2/DOM3 node type for a namespace node.
69   *
70   * <p>This class implements the DOM2 and DOM3 {@link Node} interface
71   * to allow namespace nodes to be included in the result
72   * set of an XPath selectNodes operation, even though DOM does
73   * not model namespaces in scope as separate nodes.</p>
74   *
75   * <p>
76   * While all of the DOM2 methods are implemented with reasonable
77   * defaults, there will be some unexpected surprises, so users are
78   * advised to test for NamespaceNodes and filter them out from the
79   * result sets as early as possible.
80   * </p>
81   *
82   * <ol>
83   *
84   * <li>The {@link #getNodeType} method returns {@link #NAMESPACE_NODE},
85   * which is not one of the usual DOM2 node types.  Generic code may
86   * fall unexpectedly out of switch statements, for example.</li>
87   *
88   * <li>The {@link #getOwnerDocument} method returns the owner document
89   * of the parent node, but that owner document will know nothing about
90   * the namespace node.</li>
91   *
92   * <li>The {@link #isSupported} method always returns false.</li>
93   *
94   * <li>The DOM3 methods sometimes throw UnsupportedOperationException.
95   *     They're here only to allow this class to be compiled with Java 1.5.
96   *     Do not call or rely on them.</li>
97   * </ol>
98   *
99   * <p>All attempts to modify a <code>NamespaceNode</code> will fail with a {@link
100  * DOMException} ({@link
101  * DOMException#NO_MODIFICATION_ALLOWED_ERR}).</p>
102  *
103  * @author David Megginson
104  * @author Elliotte Rusty Harold
105  * @see DocumentNavigator
106  */
107 public class NamespaceNode implements Node
108 {
109 
110     /**
111      * Constant: this is a NamespaceNode.
112      *
113      * @see #getNodeType
114      */
115     public final static short NAMESPACE_NODE = Pattern.NAMESPACE_NODE;
116 
117     // FIXME "Note: Numeric codes up to 200 are reserved to W3C for possible future use."
118     // We should be using higher codes. Here we're using 13, the same as DOM 3's type for XPathNamespace.
119     // However, that's only a note not a recommendation.
120 
121     /**
122      * Create a new NamespaceNode.
123      *
124      * @param parent the DOM node to which the namespace is attached
125      * @param name the namespace prefix
126      * @param value the namespace URI
127      */
128     public NamespaceNode (Node parent, String name, String value)
129     {
130         this.parent = parent;
131         this.name = name == null ? "" : name;
132         this.value = value;
133     }
134 
135 
136     /**
137      * Constructor.
138      *
139      * @param parent the DOM node to which the namespace is attached
140      * @param attribute the DOM attribute object containing the
141      *        namespace declaration
142      */
143     NamespaceNode (Node parent, Node attribute)
144     {
145         String attributeName = attribute.getNodeName();
146     
147         if (attributeName.equals("xmlns")) {
148             this.name = "";
149         }
150         else if (attributeName.startsWith("xmlns:")) {
151             this.name = attributeName.substring(6); // the part after "xmlns:"
152         }
153         else { // workaround for Crimson bug; Crimson incorrectly reports the prefix as the node name
154             this.name = attributeName;
155         }
156         this.parent = parent;
157         this.value = attribute.getNodeValue();
158     }
159 
160 
161 
162     ////////////////////////////////////////////////////////////////////
163     // Implementation of org.w3c.dom.Node.
164     ////////////////////////////////////////////////////////////////////
165 
166 
167     /**
168      * Get the namespace prefix.
169      *
170      * @return the namespace prefix, or "" for the default namespace
171      */
172     public String getNodeName ()
173     {
174         return name;
175     }
176 
177 
178     /**
179      * Get the namespace URI.
180      *
181      * @return the namespace URI
182      */
183     public String getNodeValue ()
184     {
185         return value;
186     }
187 
188 
189     /**
190      * Change the namespace URI (always fails).
191      *
192      * @param value the new URI
193      * @throws DOMException always
194      */
195     public void setNodeValue (String value) throws DOMException
196     {
197         disallowModification();
198     }
199 
200 
201     /**
202      * Get the node type.
203      *
204      * @return always {@link #NAMESPACE_NODE}.
205      */
206     public short getNodeType ()
207     {
208         return NAMESPACE_NODE;
209     }
210 
211 
212     /**
213      * Get the parent node.
214      *
215      * <p>This method returns the element that was queried for Namespaces
216      * in effect, <em>not</em> necessarily the actual element containing
217      * the Namespace declaration.</p>
218      *
219      * @return the parent node (not null)
220      */
221     public Node getParentNode ()
222     {
223         return parent;
224     }
225 
226 
227     /**
228      * Get the list of child nodes.
229      *
230      * @return an empty node list
231      */
232     public NodeList getChildNodes ()
233     {
234         return new EmptyNodeList();
235     }
236 
237 
238     /**
239      * Get the first child node.
240      *
241      * @return null
242      */
243     public Node getFirstChild ()
244     {
245         return null;
246     }
247 
248 
249     /**
250      * Get the last child node.
251      *
252      * @return null
253      */
254     public Node getLastChild ()
255     {
256         return null;
257     }
258 
259 
260     /**
261      * Get the previous sibling node.
262      *
263      * @return null
264      */
265     public Node getPreviousSibling ()
266     {
267         return null;
268     }
269 
270 
271     /**
272      * Get the next sibling node.
273      *
274      * @return null
275      */
276     public Node getNextSibling ()
277     {
278         return null;
279     }
280 
281 
282     /**
283      * Get the attribute nodes.
284      *
285      * @return null
286      */
287     public NamedNodeMap getAttributes ()
288     {
289         return null;
290     }
291 
292 
293     /**
294      * Get the owner document.
295      *
296      * @return the owner document <em>of the parent node</em>
297      */
298     public Document getOwnerDocument ()
299     {
300         if (parent == null) return null;
301         return parent.getOwnerDocument();
302     }
303 
304 
305     /**
306      * Insert a new child node (always fails).
307      * 
308      * @param newChild the node to add
309      * @param refChild ignored
310      * @return never
311      * @throws DOMException always
312      * @see Node#insertBefore
313      */
314     public Node insertBefore (Node newChild, Node refChild)
315     throws DOMException
316     {
317         disallowModification();
318         return null;
319     }
320 
321 
322     /**
323      * Replace a child node (always fails).
324      *
325      * @param newChild the node to add
326      * @param oldChild the child node to replace
327      * @return never
328      * @throws DOMException always
329      * @see Node#replaceChild
330      */
331     public Node replaceChild (Node newChild, Node oldChild) throws DOMException
332     {
333         disallowModification();
334         return null;
335     }
336 
337 
338     /**
339      * Remove a child node (always fails).
340      *
341      * @param oldChild the child node to remove
342      * @return never
343      * @throws DOMException always
344      * @see Node#removeChild
345      */
346     public Node removeChild(Node oldChild) throws DOMException
347     {
348         disallowModification();
349         return null;
350     }
351 
352 
353     /**
354      * Append a new child node (always fails).
355      *
356      * @param newChild the node to add
357      * @return never
358      * @throws DOMException always
359      * @see Node#appendChild
360      */
361     public Node appendChild(Node newChild) throws DOMException
362     {
363         disallowModification();
364         return null;
365     }
366 
367 
368     /**
369      * Test for child nodes.
370      *
371      * @return false
372      */
373     public boolean hasChildNodes()
374     {
375         return false;
376     }
377 
378 
379     /**
380      * Create a copy of this node.
381      *
382      * @param deep make a deep copy (no effect, since namespace nodes
383      *        don't have children).
384      * @return a new copy of this namespace node
385      */
386     public Node cloneNode (boolean deep)
387     {
388         return new NamespaceNode(parent, name, value);
389     }
390 
391 
392     /**
393      * Normalize the text descendants of this node.
394      *
395      * <p>This method has no effect, since namespace nodes have no
396      * descendants.</p>
397      */
398     public void normalize ()
399     {
400     // no op
401     }
402 
403 
404     /**
405      * Test if a DOM2 feature is supported. (None are.)
406      *
407      * @param feature the feature name
408      * @param version the feature version
409      * @return false
410      */
411     public boolean isSupported(String feature, String version)
412     {
413         return false;
414     }
415 
416 
417     /**
418      * Get the namespace URI of this node.
419      *
420      * <p>Namespace declarations are not themselves
421      * Namespace-qualified.</p>
422      *
423      * @return null
424      */
425     public String getNamespaceURI()
426     {
427        return null;
428     }
429 
430 
431     /**
432      * Get the namespace prefix of this node.
433      *
434      * <p>Namespace declarations are not themselves
435      * namespace-qualified.</p>
436      *
437      * @return null
438      * @see #getLocalName
439      */
440     public String getPrefix()
441     {
442         return null;
443     }
444 
445 
446     /**
447      * Change the namespace prefix of this node (always fails).
448      *
449      * @param prefix the new prefix
450      * @throws DOMException always thrown
451      */
452     public void setPrefix(String prefix)
453     throws DOMException
454     {
455         disallowModification();
456     }
457 
458 
459     /**
460      * Get the XPath name of the namespace node;; i.e. the
461      * namespace prefix.
462      *
463      * @return the namespace prefix
464      */
465     public String getLocalName ()
466     {
467         return name;
468     }
469 
470 
471     /**
472      * Test if this node has attributes.
473      *
474      * @return false
475      */
476     public boolean hasAttributes ()
477     {
478         return false;
479     }
480 
481 
482     /**
483      * Throw a NO_MODIFICATION_ALLOWED_ERR DOMException.
484      *
485      * @throws DOMException always thrown
486      */
487     private void disallowModification () throws DOMException
488     {
489         throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
490                    "Namespace node may not be modified");
491     }
492 
493 
494 
495     ////////////////////////////////////////////////////////////////////
496     // Override default methods from java.lang.Object.
497     ////////////////////////////////////////////////////////////////////
498 
499 
500     /**
501      * Generate a hash code for a namespace node.
502      *
503      * @return a hash code for this node
504      */
505     public int hashCode ()
506     {
507         return hashCode(parent) + hashCode(name) + hashCode(value);
508     }
509 
510 
511     /**
512      * Test for equivalence with another object.
513      *
514      * <p>Two Namespace nodes are considered equivalent if their parents,
515      * names, and values are equal.</p>
516      *
517      * @param o the object to test for equality
518      * @return true if the object is equivalent to this node, false
519      *         otherwise
520      */
521     public boolean equals (Object o)
522     {
523         if (o == this) return true;
524         else if (o == null) return false;
525         else if (o instanceof NamespaceNode) {
526             NamespaceNode ns = (NamespaceNode)o;
527             return (equals(parent, ns.getParentNode()) &&
528                 equals(name, ns.getNodeName()) &&
529                 equals(value, ns.getNodeValue()));
530         } else {
531             return false;
532         }
533     }
534 
535 
536     /**
537      * Helper method for generating a hash code.
538      *
539      * @param o the object for generating a hash code (possibly null)
540      * @return the object's hash code, or 0 if the object is null
541      * @see java.lang.Object#hashCode
542      */
543     private int hashCode (Object o)
544     {
545         return (o == null ? 0 : o.hashCode());
546     }
547 
548 
549     /**
550      * Helper method for comparing two objects.
551      *
552      * @param a the first object to compare (possibly null)
553      * @param b the second object to compare (possibly null)
554      * @return true if the objects are equivalent or are both null
555      * @see java.lang.Object#equals
556      */
557     private boolean equals (Object a, Object b)
558     {
559         return ((a == null && b == null) ||
560           (a != null && a.equals(b)));
561     }
562 
563 
564     ////////////////////////////////////////////////////////////////////
565     // Internal state.
566     ////////////////////////////////////////////////////////////////////
567 
568     private Node parent;
569     private String name;
570     private String value;
571 
572 
573 
574     ////////////////////////////////////////////////////////////////////
575     // Inner class: empty node list.
576     ////////////////////////////////////////////////////////////////////
577 
578 
579     /**
580      * A node list with no members.
581      *
582      * <p>This class is necessary for the {@link Node#getChildNodes}
583      * method, which must return an empty node list rather than
584      * null when there are no children.</p>
585      */
586     private static class EmptyNodeList implements NodeList
587     {
588 
589         /**
590          * @see NodeList#getLength
591          */
592         public int getLength ()
593         {
594             return 0;
595         }
596     
597     
598         /**
599          * @see NodeList#item
600          */
601         public Node item(int index)
602         {
603             return null;
604         }
605     
606     }
607 
608     ////////////////////////////////////////////////////////////////////
609     // DOM Level 3 methods
610     ////////////////////////////////////////////////////////////////////
611 
612     /**
613      * Return the base URI of the document containing this node. 
614      * This only works in DOM 3.
615      *
616      * @return null
617      */
618     public String getBaseURI() {
619         Class<Node> clazz = Node.class;
620         try {
621             Class[] args = new Class[0];
622             Method getBaseURI = clazz.getMethod("getBaseURI", args);
623             String base = (String) getBaseURI.invoke(this.getParentNode(), args);
624             return base;
625         }
626         catch (Exception ex) {
627             return null;
628         }
629     }
630 
631 
632     /**
633      * Compare relative position of this node to another nbode. (Always fails).
634      * This method is included solely for compatibility with the superclass.
635      * 
636      * @param other the node to compare to
637      *
638      * @return never
639      * @throws DOMException NOT_SUPPORTED_ERR
640      */
641     public short compareDocumentPosition(Node other) throws DOMException {
642         DOMException ex = new DOMException(
643           DOMException.NOT_SUPPORTED_ERR,
644           "DOM level 3 interfaces are not fully implemented in Jaxen's NamespaceNode class"
645         );
646         throw ex;
647     }
648 
649 
650     /**
651      * Return the namespace URI.
652      *
653      * @return the namespace URI
654      * @see #getNodeValue
655      */
656     public String getTextContent() {
657         return value;
658     }
659 
660 
661     /**
662      * Change the value of this node (always fails).
663      * This method is included solely for compatibility with the superclass.
664      *
665      * @param textContent the new content
666      * @throws DOMException always
667      */
668     public void setTextContent(String textContent) throws DOMException {
669         disallowModification();
670     }
671 
672 
673     /**
674      * Returns true if and only if this object represents the same XPath namespace node
675      * as the argument; that is, they have the same parent, the same prefix, and the
676      * same URI.
677      * 
678      * @param other the node to compare to
679      * @return true if this object represents the same XPath namespace node
680      *     as other; false otherwise
681      */
682     public boolean isSameNode(Node other)  {
683         boolean a = this.isEqualNode(other);
684         // a bit flaky (should really be 
685         // this.getParentNode().isEqual(other.getParentNode())
686         // but we want this to compile in Java 1.4 without problems
687         // Note that this will mess up code coverage since you can't cover both
688         // branches in the same VM
689         boolean b;
690         Node thisParent = this.getParentNode();
691         Node thatParent = other.getParentNode();
692         try {
693             Class<Node> clazz = Node.class;
694             Class[] args = {clazz};
695             Method isEqual = clazz.getMethod("isEqual", args);
696             Object[] args2 = new Object[1];
697             args2[0] = thatParent;
698             Boolean result = (Boolean) isEqual.invoke(thisParent, args2);
699             b = result.booleanValue();
700         }
701         catch (NoSuchMethodException ex) {
702             b = thisParent.equals(thatParent);
703         }
704         catch (InvocationTargetException ex) {
705             b = thisParent.equals(thatParent);
706         }
707         catch (IllegalAccessException ex) {
708             b = thisParent.equals(thatParent);
709         }
710         
711         return a && b;
712         
713     }
714 
715 
716     /**
717      * Return the prefix bound to this namespace URI within the scope
718      * of this node. 
719      * 
720      * @param namespaceURI the URI to find a prefix binding for
721      *
722      * @return a prefix matching this namespace URI
723      * @throws UnsupportedOperationException in DOM 2
724      */
725     public String lookupPrefix(String namespaceURI) {
726         // This could be fully implemented even in Java 1.4. See
727         // http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/namespaces-algorithms.html#lookupNamespaceURIAlgo
728         // It hardly seems worth the effort though.
729         
730         try {
731             Class<Node> clazz = Node.class;
732             Class[] argTypes = {String.class};
733             Method lookupPrefix = clazz.getMethod("lookupPrefix", argTypes);
734             String[] args = {namespaceURI};
735             String result = (String) lookupPrefix.invoke(parent, args);
736             return result;
737         }
738         catch (NoSuchMethodException ex) {
739             throw new UnsupportedOperationException("Cannot lookup prefixes in DOM 2");
740         }
741         catch (InvocationTargetException ex) {
742             throw new UnsupportedOperationException("Cannot lookup prefixes in DOM 2");
743         }
744         catch (IllegalAccessException ex) {
745             throw new UnsupportedOperationException("Cannot lookup prefixes in DOM 2");
746         }
747         
748     }
749 
750 
751     /**
752      * Return true if the specified URI is the default namespace in
753      * scope (always fails). This method is included solely for 
754      * compatibility with the superclass.
755      * 
756      * @param namespaceURI the URI to check
757      *
758      * @return never
759      * @throws UnsupportedOperationException always
760      */
761     public boolean isDefaultNamespace(String namespaceURI) {
762         return namespaceURI.equals(this.lookupNamespaceURI(null));
763     }
764 
765 
766     /**
767      * Return the namespace URI mapped to the specified
768      * prefix within the scope of this namespace node.
769      * 
770      * @param prefix the prefix to search for
771      *
772      * @return the namespace URI mapped to this prefix
773      * @throws UnsupportedOperationException in DOM 2
774      */
775     public String lookupNamespaceURI(String prefix) {
776         // This could be fully implemented even in Java 1.4. See
777         // http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/namespaces-algorithms.html#lookupNamespaceURIAlgo
778         // It hardly seems worth the effort though.
779         
780         try {
781             Class<Node> clazz = Node.class;
782             Class[] argTypes = {String.class};
783             Method lookupNamespaceURI = clazz.getMethod("lookupNamespaceURI", argTypes);
784             String[] args = {prefix};
785             String result = (String) lookupNamespaceURI.invoke(parent, args);
786             return result;
787         }
788         catch (NoSuchMethodException ex) {
789             throw new UnsupportedOperationException("Cannot lookup namespace URIs in DOM 2");
790         }
791         catch (InvocationTargetException ex) {
792             throw new UnsupportedOperationException("Cannot lookup namespace URIs in DOM 2");
793         }
794         catch (IllegalAccessException ex) {
795             throw new UnsupportedOperationException("Cannot lookup namespace URIs in DOM 2");
796         }
797     }
798 
799 
800     /**
801      * Returns true if this object binds the same prefix to the same URI.
802      * That is, this object has the same prefix and URI as the argument.
803      * 
804      * @param arg the node to compare to
805      * @return true if this object has the same prefix and URI as the argument; false otherwise
806      */
807     public boolean isEqualNode(Node arg) {
808         if (arg.getNodeType() == this.getNodeType()) {
809             NamespaceNode other = (NamespaceNode) arg;
810             if (other.name == null && this.name != null) return false;
811             else if (other.name != null && this.name == null) return false;
812             else if (other.value == null && this.value != null) return false;
813             else if (other.value != null && this.value == null) return false;
814             else if (other.name == null && this.name == null) {
815                 return other.value.equals(this.value);
816             }
817 
818             return other.name.equals(this.name) && other.value.equals(this.value);
819         }
820         return false;
821     }
822 
823 
824     /**
825      * Returns the value of the requested feature. Always returns null.
826      * 
827      * @return null
828      */
829     public Object getFeature(String feature, String version) {
830         return null;
831     }
832 
833     
834     // XXX userdata needs testing
835     private HashMap<String, Object> userData = new HashMap<String, Object>();
836 
837     /**
838      * Associates an object with a key. 
839      * 
840      * @param key the key by which the data will be retrieved
841      * @param data the object to store with the key
842      * @param handler ignored since namespace nodes cannot be imported, cloned, or renamed
843      * 
844      * @return the value previously associated with this key; or null
845      *     if there isn't any such previous value
846      */
847     public Object setUserData(String key, Object data, UserDataHandler handler) {
848         Object oldValue = getUserData(key);
849         userData.put(key, data);
850         return oldValue;
851     }
852 
853 
854     /**
855      * Returns the user data associated with the given key. 
856      * 
857      * @param key the lookup key
858      * 
859      * @return the object associated with the key; or null if no such object is available
860      */
861     public Object getUserData(String key) {
862         return userData.get(key);
863     }
864     
865 }
866 
867 // end of NamespaceNode.java