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