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