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