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
26package javax.imageio.metadata;
27
28import java.util.ArrayList;
29import java.util.Iterator;
30import java.util.List;
31
32import org.w3c.dom.Attr;
33import org.w3c.dom.Document;
34import org.w3c.dom.Element;
35import org.w3c.dom.DOMException;
36import org.w3c.dom.NamedNodeMap;
37import org.w3c.dom.Node;
38import org.w3c.dom.NodeList;
39import org.w3c.dom.TypeInfo;
40import org.w3c.dom.UserDataHandler;
41
42
43class 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
51class 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
112class 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
132class 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 */
231public 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) || "*".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}
1137