1 /*
   2  * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.imageio.metadata;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Iterator;
  30 import java.util.List;
  31 
  32 import org.w3c.dom.Attr;
  33 import org.w3c.dom.Document;
  34 import org.w3c.dom.Element;
  35 import org.w3c.dom.DOMException;
  36 import org.w3c.dom.NamedNodeMap;
  37 import org.w3c.dom.Node;
  38 import org.w3c.dom.NodeList;
  39 import org.w3c.dom.TypeInfo;
  40 import org.w3c.dom.UserDataHandler;
  41 
  42 
  43 class IIODOMException extends DOMException {
  44 
  45     public IIODOMException(short code, String message) {
  46         super(code, message);
  47     }
  48 }
  49 
  50 class IIONamedNodeMap implements NamedNodeMap {
  51 
  52     List nodes;
  53 
  54     public IIONamedNodeMap(List nodes) {
  55         this.nodes = nodes;
  56     }
  57 
  58     public int getLength() {
  59         return nodes.size();
  60     }
  61 
  62     public Node getNamedItem(String name) {
  63         Iterator iter = nodes.iterator();
  64         while (iter.hasNext()) {
  65             Node node = (Node)iter.next();
  66             if (name.equals(node.getNodeName())) {
  67                 return node;
  68             }
  69         }
  70 
  71         return null;
  72     }
  73 
  74     public Node item(int index) {
  75         Node node = (Node)nodes.get(index);
  76         return node;
  77     }
  78 
  79     public Node removeNamedItem(java.lang.String name) {
  80         throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
  81                                "This NamedNodeMap is read-only!");
  82     }
  83 
  84     public Node setNamedItem(Node arg) {
  85         throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
  86                                "This NamedNodeMap is read-only!");
  87     }
  88 
  89     /**
  90      * Equivalent to <code>getNamedItem(localName)</code>.
  91      */
  92     public Node getNamedItemNS(String namespaceURI, String localName) {
  93         return getNamedItem(localName);
  94     }
  95 
  96     /**
  97      * Equivalent to <code>setNamedItem(arg)</code>.
  98      */
  99     public Node setNamedItemNS(Node arg) {
 100         return setNamedItem(arg);
 101     }
 102 
 103     /**
 104      * Equivalent to <code>removeNamedItem(localName)</code>.
 105      */
 106     public Node removeNamedItemNS(String namespaceURI, String localName) {
 107         return removeNamedItem(localName);
 108     }
 109 }
 110 
 111 class IIONodeList implements NodeList {
 112 
 113     List nodes;
 114 
 115     public IIONodeList(List nodes) {
 116         this.nodes = nodes;
 117     }
 118 
 119     public int getLength() {
 120         return nodes.size();
 121     }
 122 
 123     public Node item(int index) {
 124         if (index < 0 || index > nodes.size()) {
 125             return null;
 126         }
 127         return (Node)nodes.get(index);
 128     }
 129 }
 130 
 131 class IIOAttr extends IIOMetadataNode implements Attr {
 132 
 133     Element owner;
 134     String name;
 135     String value;
 136 
 137     public IIOAttr(Element owner, String name, String value) {
 138         this.owner = owner;
 139         this.name = name;
 140         this.value = value;
 141     }
 142 
 143     public String getName() {
 144         return name;
 145     }
 146 
 147     public String getNodeName() {
 148         return name;
 149     }
 150 
 151     public short getNodeType() {
 152         return ATTRIBUTE_NODE;
 153     }
 154 
 155     public boolean getSpecified() {
 156         return true;
 157     }
 158 
 159     public String getValue() {
 160         return value;
 161     }
 162 
 163     public String getNodeValue() {
 164         return value;
 165     }
 166 
 167     public void setValue(String value) {
 168         this.value = value;
 169     }
 170 
 171     public void setNodeValue(String value) {
 172         this.value = value;
 173     }
 174 
 175     public Element getOwnerElement() {
 176         return owner;
 177     }
 178 
 179     public void setOwnerElement(Element owner) {
 180         this.owner = owner;
 181     }
 182 
 183     /** This method is new in the DOM L3 for Attr interface.
 184      * Could throw DOMException here, but its probably OK
 185      * to always return false. One reason for this, is we have no good
 186      * way to document this exception, since this class, IIOAttr,
 187      * is not a public class. The rest of the methods that throw
 188      * DOMException are publically documented as such on IIOMetadataNode.
 189      * @return false
 190      */
 191     public boolean isId() {
 192         return false;
 193     }
 194 
 195 
 196 }
 197 
 198 /**
 199  * A class representing a node in a meta-data tree, which implements
 200  * the <a
 201  * href="../../../../api/org/w3c/dom/Element.html">
 202  * <code>org.w3c.dom.Element</code></a> interface and additionally allows
 203  * for the storage of non-textual objects via the
 204  * <code>getUserObject</code> and <code>setUserObject</code> methods.
 205  *
 206  * <p> This class is not intended to be used for general XML
 207  * processing. In particular, <code>Element</code> nodes created
 208  * within the Image I/O API are not compatible with those created by
 209  * Sun's standard implementation of the <code>org.w3.dom</code> API.
 210  * In particular, the implementation is tuned for simple uses and may
 211  * not perform well for intensive processing.
 212  *
 213  * <p> Namespaces are ignored in this implementation.  The terms "tag
 214  * name" and "node name" are always considered to be synonymous.
 215  *
 216  * <em>Note:</em>
 217  * The DOM Level 3 specification added a number of new methods to the
 218  * {@code Node}, {@code Element} and {@code Attr} interfaces that are not
 219  * of value to the {@code IIOMetadataNode} implementation or specification.
 220  *
 221  * Calling such methods on an {@code IIOMetadataNode}, or an {@code Attr}
 222  * instance returned from an {@code IIOMetadataNode} will result in a
 223  * {@code DOMException} being thrown.
 224  *
 225  * @see IIOMetadata#getAsTree
 226  * @see IIOMetadata#setFromTree
 227  * @see IIOMetadata#mergeTree
 228  *
 229  */
 230 public class IIOMetadataNode implements Element, NodeList {
 231 
 232     /**
 233      * The name of the node as a <code>String</code>.
 234      */
 235     private String nodeName = null;
 236 
 237     /**
 238      * The value of the node as a <code>String</code>.  The Image I/O
 239      * API typically does not make use of the node value.
 240      */
 241     private String nodeValue = null;
 242 
 243     /**
 244      * The <code>Object</code> value associated with this node.
 245      */
 246     private Object userObject = null;
 247 
 248     /**
 249      * The parent node of this node, or <code>null</code> if this node
 250      * forms the root of its own tree.
 251      */
 252     private IIOMetadataNode parent = null;
 253 
 254     /**
 255      * The number of child nodes.
 256      */
 257     private int numChildren = 0;
 258 
 259     /**
 260      * The first (leftmost) child node of this node, or
 261      * <code>null</code> if this node is a leaf node.
 262      */
 263     private IIOMetadataNode firstChild = null;
 264 
 265     /**
 266      * The last (rightmost) child node of this node, or
 267      * <code>null</code> if this node is a leaf node.
 268      */
 269     private IIOMetadataNode lastChild = null;
 270 
 271     /**
 272      * The next (right) sibling node of this node, or
 273      * <code>null</code> if this node is its parent's last child node.
 274      */
 275     private IIOMetadataNode nextSibling = null;
 276 
 277     /**
 278      * The previous (left) sibling node of this node, or
 279      * <code>null</code> if this node is its parent's first child node.
 280      */
 281     private IIOMetadataNode previousSibling = null;
 282 
 283     /**
 284      * A <code>List</code> of <code>IIOAttr</code> nodes representing
 285      * attributes.
 286      */
 287     private List attributes = new ArrayList();
 288 
 289     /**
 290      * Constructs an empty <code>IIOMetadataNode</code>.
 291      */
 292     public IIOMetadataNode() {}
 293 
 294     /**
 295      * Constructs an <code>IIOMetadataNode</code> with a given node
 296      * name.
 297      *
 298      * @param nodeName the name of the node, as a <code>String</code>.
 299      */
 300     public IIOMetadataNode(String nodeName) {
 301         this.nodeName = nodeName;
 302     }
 303 
 304     /**
 305      * Check that the node is either <code>null</code> or an
 306      * <code>IIOMetadataNode</code>.
 307      */
 308     private void checkNode(Node node) throws DOMException {
 309         if (node == null) {
 310             return;
 311         }
 312         if (!(node instanceof IIOMetadataNode)) {
 313             throw new IIODOMException(DOMException.WRONG_DOCUMENT_ERR,
 314                                       "Node not an IIOMetadataNode!");
 315         }
 316     }
 317 
 318     // Methods from Node
 319 
 320     /**
 321      * Returns the node name associated with this node.
 322      *
 323      * @return the node name, as a <code>String</code>.
 324      */
 325     public String getNodeName() {
 326         return nodeName;
 327     }
 328 
 329     /**
 330      * Returns the value associated with this node.
 331      *
 332      * @return the node value, as a <code>String</code>.
 333      */
 334     public String getNodeValue(){
 335         return nodeValue;
 336     }
 337 
 338     /**
 339      * Sets the <code>String</code> value associated with this node.
 340      */
 341     public void setNodeValue(String nodeValue) {
 342         this.nodeValue = nodeValue;
 343     }
 344 
 345     /**
 346      * Returns the node type, which is always
 347      * <code>ELEMENT_NODE</code>.
 348      *
 349      * @return the <code>short</code> value <code>ELEMENT_NODE</code>.
 350      */
 351     public short getNodeType() {
 352         return ELEMENT_NODE;
 353     }
 354 
 355     /**
 356      * Returns the parent of this node.  A <code>null</code> value
 357      * indicates that the node is the root of its own tree.  To add a
 358      * node to an existing tree, use one of the
 359      * <code>insertBefore</code>, <code>replaceChild</code>, or
 360      * <code>appendChild</code> methods.
 361      *
 362      * @return the parent, as a <code>Node</code>.
 363      *
 364      * @see #insertBefore
 365      * @see #replaceChild
 366      * @see #appendChild
 367      */
 368     public Node getParentNode() {
 369         return parent;
 370     }
 371 
 372     /**
 373      * Returns a <code>NodeList</code> that contains all children of this node.
 374      * If there are no children, this is a <code>NodeList</code> containing
 375      * no nodes.
 376      *
 377      * @return the children as a <code>NodeList</code>
 378      */
 379     public NodeList getChildNodes() {
 380         return this;
 381     }
 382 
 383     /**
 384      * Returns the first child of this node, or <code>null</code> if
 385      * the node has no children.
 386      *
 387      * @return the first child, as a <code>Node</code>, or
 388      * <code>null</code>
 389      */
 390     public Node getFirstChild() {
 391         return firstChild;
 392     }
 393 
 394     /**
 395      * Returns the last child of this node, or <code>null</code> if
 396      * the node has no children.
 397      *
 398      * @return the last child, as a <code>Node</code>, or
 399      * <code>null</code>.
 400      */
 401     public Node getLastChild() {
 402         return lastChild;
 403     }
 404 
 405     /**
 406      * Returns the previous sibling of this node, or <code>null</code>
 407      * if this node has no previous sibling.
 408      *
 409      * @return the previous sibling, as a <code>Node</code>, or
 410      * <code>null</code>.
 411      */
 412     public Node getPreviousSibling() {
 413         return previousSibling;
 414     }
 415 
 416     /**
 417      * Returns the next sibling of this node, or <code>null</code> if
 418      * the node has no next sibling.
 419      *
 420      * @return the next sibling, as a <code>Node</code>, or
 421      * <code>null</code>.
 422      */
 423     public Node getNextSibling() {
 424         return nextSibling;
 425     }
 426 
 427     /**
 428      * Returns a <code>NamedNodeMap</code> containing the attributes of
 429      * this node.
 430      *
 431      * @return a <code>NamedNodeMap</code> containing the attributes of
 432      * this node.
 433      */
 434     public NamedNodeMap getAttributes() {
 435         return new IIONamedNodeMap(attributes);
 436     }
 437 
 438     /**
 439      * Returns <code>null</code>, since <code>IIOMetadataNode</code>s
 440      * do not belong to any <code>Document</code>.
 441      *
 442      * @return <code>null</code>.
 443      */
 444     public Document getOwnerDocument() {
 445         return null;
 446     }
 447 
 448     /**
 449      * Inserts the node <code>newChild</code> before the existing
 450      * child node <code>refChild</code>. If <code>refChild</code> is
 451      * <code>null</code>, insert <code>newChild</code> at the end of
 452      * the list of children.
 453      *
 454      * @param newChild the <code>Node</code> to insert.
 455      * @param refChild the reference <code>Node</code>.
 456      *
 457      * @return the node being inserted.
 458      *
 459      * @exception IllegalArgumentException if <code>newChild</code> is
 460      * <code>null</code>.
 461      */
 462     public Node insertBefore(Node newChild,
 463                              Node refChild) {
 464         if (newChild == null) {
 465             throw new IllegalArgumentException("newChild == null!");
 466         }
 467 
 468         checkNode(newChild);
 469         checkNode(refChild);
 470 
 471         IIOMetadataNode newChildNode = (IIOMetadataNode)newChild;
 472         IIOMetadataNode refChildNode = (IIOMetadataNode)refChild;
 473 
 474         // Siblings, can be null.
 475         IIOMetadataNode previous = null;
 476         IIOMetadataNode next = null;
 477 
 478         if (refChild == null) {
 479             previous = this.lastChild;
 480             next = null;
 481             this.lastChild = newChildNode;
 482         } else {
 483             previous = refChildNode.previousSibling;
 484             next = refChildNode;
 485         }
 486 
 487         if (previous != null) {
 488             previous.nextSibling = newChildNode;
 489         }
 490         if (next != null) {
 491             next.previousSibling = newChildNode;
 492         }
 493 
 494         newChildNode.parent = this;
 495         newChildNode.previousSibling = previous;
 496         newChildNode.nextSibling = next;
 497 
 498         // N.B.: O.K. if refChild == null
 499         if (this.firstChild == refChildNode) {
 500             this.firstChild = newChildNode;
 501         }
 502 
 503         ++numChildren;
 504         return newChildNode;
 505     }
 506 
 507     /**
 508      * Replaces the child node <code>oldChild</code> with
 509      * <code>newChild</code> in the list of children, and returns the
 510      * <code>oldChild</code> node.
 511      *
 512      * @param newChild the <code>Node</code> to insert.
 513      * @param oldChild the <code>Node</code> to be replaced.
 514      *
 515      * @return the node replaced.
 516      *
 517      * @exception IllegalArgumentException if <code>newChild</code> is
 518      * <code>null</code>.
 519      */
 520     public Node replaceChild(Node newChild,
 521                              Node oldChild) {
 522         if (newChild == null) {
 523             throw new IllegalArgumentException("newChild == null!");
 524         }
 525 
 526         checkNode(newChild);
 527         checkNode(oldChild);
 528 
 529         IIOMetadataNode newChildNode = (IIOMetadataNode)newChild;
 530         IIOMetadataNode oldChildNode = (IIOMetadataNode)oldChild;
 531 
 532         IIOMetadataNode previous = oldChildNode.previousSibling;
 533         IIOMetadataNode next = oldChildNode.nextSibling;
 534 
 535         if (previous != null) {
 536             previous.nextSibling = newChildNode;
 537         }
 538         if (next != null) {
 539             next.previousSibling = newChildNode;
 540         }
 541 
 542         newChildNode.parent = this;
 543         newChildNode.previousSibling = previous;
 544         newChildNode.nextSibling = next;
 545 
 546         if (firstChild == oldChildNode) {
 547             firstChild = newChildNode;
 548         }
 549         if (lastChild == oldChildNode) {
 550             lastChild = newChildNode;
 551         }
 552 
 553         oldChildNode.parent = null;
 554         oldChildNode.previousSibling = null;
 555         oldChildNode.nextSibling = null;
 556 
 557         return oldChildNode;
 558     }
 559 
 560     /**
 561      * Removes the child node indicated by <code>oldChild</code> from
 562      * the list of children, and returns it.
 563      *
 564      * @param oldChild the <code>Node</code> to be removed.
 565      *
 566      * @return the node removed.
 567      *
 568      * @exception IllegalArgumentException if <code>oldChild</code> is
 569      * <code>null</code>.
 570      */
 571     public Node removeChild(Node oldChild) {
 572         if (oldChild == null) {
 573             throw new IllegalArgumentException("oldChild == null!");
 574         }
 575         checkNode(oldChild);
 576 
 577         IIOMetadataNode oldChildNode = (IIOMetadataNode)oldChild;
 578 
 579         IIOMetadataNode previous = oldChildNode.previousSibling;
 580         IIOMetadataNode next = oldChildNode.nextSibling;
 581 
 582         if (previous != null) {
 583             previous.nextSibling = next;
 584         }
 585         if (next != null) {
 586             next.previousSibling = previous;
 587         }
 588 
 589         if (this.firstChild == oldChildNode) {
 590             this.firstChild = next;
 591         }
 592         if (this.lastChild == oldChildNode) {
 593             this.lastChild = previous;
 594         }
 595 
 596         oldChildNode.parent = null;
 597         oldChildNode.previousSibling = null;
 598         oldChildNode.nextSibling = null;
 599 
 600         --numChildren;
 601         return oldChildNode;
 602     }
 603 
 604     /**
 605      * Adds the node <code>newChild</code> to the end of the list of
 606      * children of this node.
 607      *
 608      * @param newChild the <code>Node</code> to insert.
 609      *
 610      * @return the node added.
 611      *
 612      * @exception IllegalArgumentException if <code>newChild</code> is
 613      * <code>null</code>.
 614      */
 615     public Node appendChild(Node newChild) {
 616         if (newChild == null) {
 617             throw new IllegalArgumentException("newChild == null!");
 618         }
 619         checkNode(newChild);
 620 
 621         // insertBefore will increment numChildren
 622         return insertBefore(newChild, null);
 623     }
 624 
 625     /**
 626      * Returns <code>true</code> if this node has child nodes.
 627      *
 628      * @return <code>true</code> if this node has children.
 629      */
 630     public boolean hasChildNodes() {
 631         return numChildren > 0;
 632     }
 633 
 634     /**
 635      * Returns a duplicate of this node.  The duplicate node has no
 636      * parent (<code>getParentNode</code> returns <code>null</code>).
 637      * If a shallow clone is being performed (<code>deep</code> is
 638      * <code>false</code>), the new node will not have any children or
 639      * siblings.  If a deep clone is being performed, the new node
 640      * will form the root of a complete cloned subtree.
 641      *
 642      * @param deep if <code>true</code>, recursively clone the subtree
 643      * under the specified node; if <code>false</code>, clone only the
 644      * node itself.
 645      *
 646      * @return the duplicate node.
 647      */
 648     public Node cloneNode(boolean deep) {
 649         IIOMetadataNode newNode = new IIOMetadataNode(this.nodeName);
 650         newNode.setUserObject(getUserObject());
 651         // Attributes
 652 
 653         if (deep) {
 654             for (IIOMetadataNode child = firstChild;
 655                  child != null;
 656                  child = child.nextSibling) {
 657                 newNode.appendChild(child.cloneNode(true));
 658             }
 659         }
 660 
 661         return newNode;
 662     }
 663 
 664     /**
 665      * Does nothing, since <code>IIOMetadataNode</code>s do not
 666      * contain <code>Text</code> children.
 667      */
 668     public void normalize() {
 669     }
 670 
 671     /**
 672      * Returns <code>false</code> since DOM features are not
 673      * supported.
 674      *
 675      * @return <code>false</code>.
 676      *
 677      * @param feature a <code>String</code>, which is ignored.
 678      * @param version a <code>String</code>, which is ignored.
 679      */
 680     public boolean isSupported(String feature, String version) {
 681         return false;
 682     }
 683 
 684     /**
 685      * Returns <code>null</code>, since namespaces are not supported.
 686      */
 687     public String getNamespaceURI() throws DOMException {
 688         return null;
 689     }
 690 
 691     /**
 692      * Returns <code>null</code>, since namespaces are not supported.
 693      *
 694      * @return <code>null</code>.
 695      *
 696      * @see #setPrefix
 697      */
 698     public String getPrefix() {
 699         return null;
 700     }
 701 
 702     /**
 703      * Does nothing, since namespaces are not supported.
 704      *
 705      * @param prefix a <code>String</code>, which is ignored.
 706      *
 707      * @see #getPrefix
 708      */
 709     public void setPrefix(String prefix) {
 710     }
 711 
 712     /**
 713      * Equivalent to <code>getNodeName</code>.
 714      *
 715      * @return the node name, as a <code>String</code>.
 716      */
 717     public String getLocalName() {
 718         return nodeName;
 719     }
 720 
 721     // Methods from Element
 722 
 723 
 724     /**
 725      * Equivalent to <code>getNodeName</code>.
 726      *
 727      * @return the node name, as a <code>String</code>
 728      */
 729     public String getTagName() {
 730         return nodeName;
 731     }
 732 
 733     /**
 734      * Retrieves an attribute value by name.
 735      * @param name The name of the attribute to retrieve.
 736      * @return The <code>Attr</code> value as a string, or the empty string
 737      * if that attribute does not have a specified or default value.
 738      */
 739     public String getAttribute(String name) {
 740         Attr attr = getAttributeNode(name);
 741         if (attr == null) {
 742             return "";
 743         }
 744         return attr.getValue();
 745     }
 746 
 747     /**
 748      * Equivalent to <code>getAttribute(localName)</code>.
 749      *
 750      * @see #setAttributeNS
 751      */
 752     public String getAttributeNS(String namespaceURI, String localName) {
 753         return getAttribute(localName);
 754     }
 755 
 756     public void setAttribute(String name, String value) {
 757         // Name must be valid unicode chars
 758         boolean valid = true;
 759         char[] chs = name.toCharArray();
 760         for (int i=0;i<chs.length;i++) {
 761             if (chs[i] >= 0xfffe) {
 762                 valid = false;
 763                 break;
 764             }
 765         }
 766         if (!valid) {
 767             throw new IIODOMException(DOMException.INVALID_CHARACTER_ERR,
 768                                       "Attribute name is illegal!");
 769         }
 770         removeAttribute(name, false);
 771         attributes.add(new IIOAttr(this, name, value));
 772     }
 773 
 774     /**
 775      * Equivalent to <code>setAttribute(qualifiedName, value)</code>.
 776      *
 777      * @see #getAttributeNS
 778      */
 779     public void setAttributeNS(String namespaceURI,
 780                                String qualifiedName, String value) {
 781         setAttribute(qualifiedName, value);
 782     }
 783 
 784     public void removeAttribute(String name) {
 785         removeAttribute(name, true);
 786     }
 787 
 788     private void removeAttribute(String name, boolean checkPresent) {
 789         int numAttributes = attributes.size();
 790         for (int i = 0; i < numAttributes; i++) {
 791             IIOAttr attr = (IIOAttr)attributes.get(i);
 792             if (name.equals(attr.getName())) {
 793                 attr.setOwnerElement(null);
 794                 attributes.remove(i);
 795                 return;
 796             }
 797         }
 798 
 799         // If we get here, the attribute doesn't exist
 800         if (checkPresent) {
 801             throw new IIODOMException(DOMException.NOT_FOUND_ERR,
 802                                       "No such attribute!");
 803         }
 804     }
 805 
 806     /**
 807      * Equivalent to <code>removeAttribute(localName)</code>.
 808      */
 809     public void removeAttributeNS(String namespaceURI,
 810                                   String localName) {
 811         removeAttribute(localName);
 812     }
 813 
 814     public Attr getAttributeNode(String name) {
 815         Node node = getAttributes().getNamedItem(name);
 816         return (Attr)node;
 817     }
 818 
 819     /**
 820      * Equivalent to <code>getAttributeNode(localName)</code>.
 821      *
 822      * @see #setAttributeNodeNS
 823      */
 824    public Attr getAttributeNodeNS(String namespaceURI,
 825                                    String localName) {
 826         return getAttributeNode(localName);
 827     }
 828 
 829     public Attr setAttributeNode(Attr newAttr) throws DOMException {
 830         Element owner = newAttr.getOwnerElement();
 831         if (owner != null) {
 832             if (owner == this) {
 833                 return null;
 834             } else {
 835                 throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR,
 836                                        "Attribute is already in use");
 837             }
 838         }
 839 
 840         IIOAttr attr;
 841         if (newAttr instanceof IIOAttr) {
 842             attr = (IIOAttr)newAttr;
 843             attr.setOwnerElement(this);
 844         } else {
 845             attr = new IIOAttr(this,
 846                                newAttr.getName(),
 847                                newAttr.getValue());
 848         }
 849 
 850         Attr oldAttr = getAttributeNode(attr.getName());
 851         if (oldAttr != null) {
 852             removeAttributeNode(oldAttr);
 853         }
 854 
 855         attributes.add(attr);
 856 
 857         return oldAttr;
 858     }
 859 
 860     /**
 861      * Equivalent to <code>setAttributeNode(newAttr)</code>.
 862      *
 863      * @see #getAttributeNodeNS
 864      */
 865     public Attr setAttributeNodeNS(Attr newAttr) {
 866         return setAttributeNode(newAttr);
 867     }
 868 
 869     public Attr removeAttributeNode(Attr oldAttr) {
 870         removeAttribute(oldAttr.getName());
 871         return oldAttr;
 872     }
 873 
 874     public NodeList getElementsByTagName(String name) {
 875         List l = new ArrayList();
 876         getElementsByTagName(name, l);
 877         return new IIONodeList(l);
 878     }
 879 
 880     private void getElementsByTagName(String name, List l) {
 881         if (nodeName.equals(name)) {
 882             l.add(this);
 883         }
 884 
 885         Node child = getFirstChild();
 886         while (child != null) {
 887             ((IIOMetadataNode)child).getElementsByTagName(name, l);
 888             child = child.getNextSibling();
 889         }
 890     }
 891 
 892     /**
 893      * Equivalent to <code>getElementsByTagName(localName)</code>.
 894      */
 895     public NodeList getElementsByTagNameNS(String namespaceURI,
 896                                            String localName) {
 897         return getElementsByTagName(localName);
 898     }
 899 
 900     public boolean hasAttributes() {
 901         return attributes.size() > 0;
 902     }
 903 
 904     public boolean hasAttribute(String name) {
 905         return getAttributeNode(name) != null;
 906     }
 907 
 908     /**
 909      * Equivalent to <code>hasAttribute(localName)</code>.
 910      */
 911     public boolean hasAttributeNS(String namespaceURI,
 912                                   String localName) {
 913         return hasAttribute(localName);
 914     }
 915 
 916     // Methods from NodeList
 917 
 918     public int getLength() {
 919         return numChildren;
 920     }
 921 
 922     public Node item(int index) {
 923         if (index < 0) {
 924             return null;
 925         }
 926 
 927         Node child = getFirstChild();
 928         while (child != null && index-- > 0) {
 929             child = child.getNextSibling();
 930         }
 931         return child;
 932     }
 933 
 934     /**
 935      * Returns the <code>Object</code> value associated with this node.
 936      *
 937      * @return the user <code>Object</code>.
 938      *
 939      * @see #setUserObject
 940      */
 941     public Object getUserObject() {
 942         return userObject;
 943     }
 944 
 945     /**
 946      * Sets the value associated with this node.
 947      *
 948      * @param userObject the user <code>Object</code>.
 949      *
 950      * @see #getUserObject
 951      */
 952     public void setUserObject(Object userObject) {
 953         this.userObject = userObject;
 954     }
 955 
 956     // Start of dummy methods for DOM L3.
 957 
 958     /**
 959      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
 960      * and will throw a {@code DOMException}.
 961      * @throws DOMException - always.
 962      */
 963     public void setIdAttribute(String name,
 964                                boolean isId)
 965                                throws DOMException {
 966         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
 967                                "Method not supported");
 968     }
 969 
 970     /**
 971      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
 972      * and will throw a {@code DOMException}.
 973      * @throws DOMException - always.
 974      */
 975     public void setIdAttributeNS(String namespaceURI,
 976                                  String localName,
 977                                  boolean isId)
 978                                  throws DOMException {
 979         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
 980                                "Method not supported");
 981     }
 982 
 983     /**
 984      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
 985      * and will throw a {@code DOMException}.
 986      * @throws DOMException - always.
 987      */
 988     public void setIdAttributeNode(Attr idAttr,
 989                                    boolean isId)
 990                                    throws DOMException {
 991         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
 992                                "Method not supported");
 993     }
 994 
 995     /**
 996      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
 997      * and will throw a {@code DOMException}.
 998      * @throws DOMException - always.
 999      */
1000     public TypeInfo getSchemaTypeInfo() throws DOMException {
1001         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1002                                "Method not supported");
1003     }
1004 
1005     /**
1006      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
1007      * and will throw a {@code DOMException}.
1008      * @throws DOMException - always.
1009      */
1010     public Object setUserData(String key,
1011                               Object data,
1012                               UserDataHandler handler) throws DOMException {
1013         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1014                                "Method not supported");
1015     }
1016 
1017     /**
1018      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
1019      * and will throw a {@code DOMException}.
1020      * @throws DOMException - always.
1021      */
1022     public Object getUserData(String key) throws DOMException {
1023         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1024                                "Method not supported");
1025     }
1026 
1027     /**
1028      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
1029      * and will throw a {@code DOMException}.
1030      * @throws DOMException - always.
1031      */
1032     public Object getFeature(String feature, String version)
1033                               throws DOMException {
1034         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1035                                "Method not supported");
1036     }
1037 
1038     /**
1039      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
1040      * and will throw a {@code DOMException}.
1041      * @throws DOMException - always.
1042      */
1043     public boolean isSameNode(Node node) throws DOMException {
1044         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1045                                "Method not supported");
1046     }
1047 
1048     /**
1049      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
1050      * and will throw a {@code DOMException}.
1051      * @throws DOMException - always.
1052      */
1053     public boolean isEqualNode(Node node) throws DOMException {
1054         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1055                                "Method not supported");
1056     }
1057 
1058     /**
1059      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
1060      * and will throw a {@code DOMException}.
1061      * @throws DOMException - always.
1062      */
1063     public String lookupNamespaceURI(String prefix) throws DOMException {
1064         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1065                                "Method not supported");
1066     }
1067 
1068     /**
1069      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
1070      * and will throw a {@code DOMException}.
1071      * @throws DOMException - always.
1072      */
1073     public boolean isDefaultNamespace(String namespaceURI)
1074                                                throws DOMException {
1075         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1076                                "Method not supported");
1077     }
1078 
1079     /**
1080      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
1081      * and will throw a {@code DOMException}.
1082      * @throws DOMException - always.
1083      */
1084     public String lookupPrefix(String namespaceURI) throws DOMException {
1085         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1086                                "Method not supported");
1087     }
1088 
1089     /**
1090      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
1091      * and will throw a {@code DOMException}.
1092      * @throws DOMException - always.
1093      */
1094     public String getTextContent() throws DOMException {
1095         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1096                                "Method not supported");
1097     }
1098 
1099     /**
1100      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
1101      * and will throw a {@code DOMException}.
1102      * @throws DOMException - always.
1103      */
1104     public void setTextContent(String textContent) throws DOMException {
1105         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1106                                "Method not supported");
1107     }
1108 
1109     /**
1110      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
1111      * and will throw a {@code DOMException}.
1112      * @throws DOMException - always.
1113      */
1114     public short compareDocumentPosition(Node other)
1115                                          throws DOMException {
1116         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1117                                "Method not supported");
1118     }
1119 
1120     /**
1121      * This DOM Level 3 method is not supported for {@code IIOMetadataNode}
1122      * and will throw a {@code DOMException}.
1123      * @throws DOMException - always.
1124      */
1125     public String getBaseURI() throws DOMException {
1126         throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
1127                                "Method not supported");
1128     }
1129     //End of dummy methods for DOM L3.
1130 
1131 
1132 }