Merge from mainline
[official-gcc.git] / libjava / classpath / gnu / xml / dom / DomNode.java
blob93f7c6f64f3818f84a9e5d985b5470b5a94140a7
1 /* DomNode.java --
2 Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
38 package gnu.xml.dom;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.Iterator;
43 import java.util.Map;
45 import org.w3c.dom.Document;
46 import org.w3c.dom.DOMException;
47 import org.w3c.dom.DOMImplementation;
48 import org.w3c.dom.NamedNodeMap;
49 import org.w3c.dom.Node;
50 import org.w3c.dom.NodeList;
51 import org.w3c.dom.Text;
52 import org.w3c.dom.UserDataHandler;
53 import org.w3c.dom.events.DocumentEvent;
54 import org.w3c.dom.events.Event;
55 import org.w3c.dom.events.EventException;
56 import org.w3c.dom.events.EventListener;
57 import org.w3c.dom.events.EventTarget;
58 import org.w3c.dom.events.MutationEvent;
59 import org.w3c.dom.traversal.NodeFilter;
60 import org.w3c.dom.traversal.NodeIterator;
62 /**
63 * <p> "Node", "EventTarget", and "DocumentEvent" implementation.
64 * This provides most of the core DOM functionality; only more
65 * specialized features are provided by subclasses. Those subclasses may
66 * have some particular constraints they must implement, by overriding
67 * methods defined here. Such constraints are noted here in the method
68 * documentation. </p>
70 * <p> Note that you can create events with type names prefixed with "USER-",
71 * and pass them through this DOM. This lets you use the DOM event scheme
72 * for application specific purposes, although you must use a predefined event
73 * structure (such as MutationEvent) to pass data along with those events.
74 * Test for existence of this feature with the "USER-Events" DOM feature
75 * name.</p>
77 * <p> Other kinds of events you can send include the "html" events,
78 * like "load", "unload", "abort", "error", and "blur"; and the mutation
79 * events. If this DOM has been compiled with mutation event support
80 * enabled, it will send mutation events when you change parts of the
81 * tree; otherwise you may create and send such events yourself, but
82 * they won't be generated by the DOM itself. </p>
84 * <p> Note that there is a namespace-aware name comparison method,
85 * <em>nameAndTypeEquals</em>, which compares the names (and types) of
86 * two nodes in conformance with the "Namespaces in XML" specification.
87 * While mostly intended for use with elements and attributes, this should
88 * also be helpful for ProcessingInstruction nodes and some others which
89 * do not have namespace URIs.
91 * @author David Brownell
92 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
94 public abstract class DomNode
95 implements Node, NodeList, EventTarget, DocumentEvent, Cloneable, Comparable
98 // package private
99 //final static String xmlNamespace = "http://www.w3.org/XML/1998/namespace";
100 //final static String xmlnsURI = "http://www.w3.org/2000/xmlns/";
102 // tunable
103 // NKIDS_* affects arrays of children (which grow)
104 // (currently) fixed size:
105 // ANCESTORS_* is for event capture/bubbling, # ancestors
106 // NOTIFICATIONS_* is for per-node event delivery, # events
107 private static final int NKIDS_DELTA = 8;
108 private static final int ANCESTORS_INIT = 20;
109 private static final int NOTIFICATIONS_INIT = 10;
111 // tunable: enable mutation events or not? Enabling it costs about
112 // 10-15% in DOM construction time, last time it was measured.
114 // package private !!!
115 static final boolean reportMutations = true;
117 // locking protocol changeable only within this class
118 private static final Object lockNode = new Object();
120 // NON-FINAL class data
122 // Optimize event dispatch by not allocating memory each time
123 private static boolean dispatchDataLock;
124 private static DomNode[] ancestors = new DomNode[ANCESTORS_INIT];
125 private static ListenerRecord[] notificationSet
126 = new ListenerRecord[NOTIFICATIONS_INIT];
128 // Ditto for the (most common) event object itself!
129 private static boolean eventDataLock;
130 private static DomEvent.DomMutationEvent mutationEvent
131 = new DomEvent.DomMutationEvent(null);
134 // PER-INSTANCE DATA
137 DomDocument owner;
138 DomNode parent; // parent node;
139 DomNode previous; // previous sibling node
140 DomNode next; // next sibling node
141 DomNode first; // first child node
142 DomNode last; // last child node
143 int index; // index of this node in its parent's children
144 int depth; // depth of the node in the document
145 int length; // number of children
146 final short nodeType;
148 // Bleech ... "package private" so a builder can populate entity refs.
149 // writable during construction. DOM spec is nasty.
150 boolean readonly;
152 // event registrations
153 private HashSet listeners;
154 private int nListeners;
156 // DOM Level 3 userData dictionary.
157 private HashMap userData;
158 private HashMap userDataHandlers;
161 // Some of the methods here are declared 'final' because
162 // knowledge about their implementation is built into this
163 // class -- for both integrity and performance.
167 * Reduces space utilization for this node.
169 public void compact()
174 * Constructs a node and associates it with its owner. Only
175 * Document and DocumentType nodes may be created with no owner,
176 * and DocumentType nodes get an owner as soon as they are
177 * associated with a document.
179 protected DomNode(short nodeType, DomDocument owner)
181 this.nodeType = nodeType;
183 if (owner == null)
185 // DOM calls never go down this path
186 if (nodeType != DOCUMENT_NODE && nodeType != DOCUMENT_TYPE_NODE)
188 throw new IllegalArgumentException ("no owner!");
191 this.owner = owner;
192 this.listeners = new HashSet();
197 * <b>DOM L1</b>
198 * Returns null; Element subclasses must override this method.
200 public NamedNodeMap getAttributes()
202 return null;
206 * <b>DOM L2></b>
207 * Returns true iff this is an element node with attributes.
209 public boolean hasAttributes()
211 return false;
215 * <b>DOM L1</b>
216 * Returns a list, possibly empty, of the children of this node.
217 * In this implementation, to conserve memory, nodes are the same
218 * as their list of children. This can have ramifications for
219 * subclasses, which may need to provide their own getLength method
220 * for reasons unrelated to the NodeList method of the same name.
222 public NodeList getChildNodes()
224 return this;
228 * <b>DOM L1</b>
229 * Returns the first child of this node, or null if there are none.
231 public Node getFirstChild()
233 return first;
237 * <b>DOM L1</b>
238 * Returns the last child of this node, or null if there are none.
240 public Node getLastChild()
242 return last;
246 * <b>DOM L1</b>
247 * Returns true if this node has children.
249 public boolean hasChildNodes()
251 return length != 0;
256 * Exposes the internal "readonly" flag. In DOM, children of
257 * entities and entity references are readonly, as are the
258 * objects associated with DocumentType objets.
260 public final boolean isReadonly()
262 return readonly;
266 * Sets the internal "readonly" flag so this subtree can't be changed.
267 * Subclasses need to override this method for any associated content
268 * that's not a child node, such as an element's attributes or the
269 * (few) declarations associated with a DocumentType.
271 public void makeReadonly()
273 readonly = true;
274 for (DomNode child = first; child != null; child = child.next)
276 child.makeReadonly();
281 * Used to adopt a node to a new document.
283 void setOwner(DomDocument doc)
285 this.owner = doc;
286 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
288 ctx.setOwner(doc);
292 // just checks the node for inclusion -- may be called many
293 // times (docfrag) before anything is allowed to change
294 private void checkMisc(DomNode child)
296 if (readonly && !owner.building)
298 throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
299 null, this, 0);
301 for (DomNode ctx = this; ctx != null; ctx = ctx.parent)
303 if (child == ctx)
305 throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
306 "can't make ancestor into a child",
307 this, 0);
311 DomDocument owner = (nodeType == DOCUMENT_NODE) ? (DomDocument) this :
312 this.owner;
313 DomDocument childOwner = child.owner;
314 short childNodeType = child.nodeType;
316 if (childOwner != owner)
318 // new in DOM L2, this case -- patch it up later, in reparent()
319 if (!(childNodeType == DOCUMENT_TYPE_NODE && childOwner == null))
321 throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
322 null, child, 0);
326 // enforce various structural constraints
327 switch (nodeType)
329 case DOCUMENT_NODE:
330 switch (childNodeType)
332 case ELEMENT_NODE:
333 case PROCESSING_INSTRUCTION_NODE:
334 case COMMENT_NODE:
335 case DOCUMENT_TYPE_NODE:
336 return;
338 break;
340 case ATTRIBUTE_NODE:
341 switch (childNodeType)
343 case TEXT_NODE:
344 case ENTITY_REFERENCE_NODE:
345 return;
347 break;
349 case DOCUMENT_FRAGMENT_NODE:
350 case ENTITY_REFERENCE_NODE:
351 case ELEMENT_NODE:
352 case ENTITY_NODE:
353 switch (childNodeType)
355 case ELEMENT_NODE:
356 case TEXT_NODE:
357 case COMMENT_NODE:
358 case PROCESSING_INSTRUCTION_NODE:
359 case CDATA_SECTION_NODE:
360 case ENTITY_REFERENCE_NODE:
361 return;
363 break;
364 case DOCUMENT_TYPE_NODE:
365 if (!owner.building)
366 break;
367 switch (childNodeType)
369 case COMMENT_NODE:
370 case PROCESSING_INSTRUCTION_NODE:
371 return;
373 break;
375 if (owner.checkingWellformedness)
377 throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
378 "can't append " +
379 nodeTypeToString(childNodeType) +
380 " to node of type " +
381 nodeTypeToString(nodeType),
382 this, 0);
386 // Here's hoping a good optimizer will detect the case when the
387 // next several methods are never called, and won't allocate
388 // object code space of any kind. (Case: not reporting any
389 // mutation events. We can also remove some static variables
390 // listed above.)
392 private void insertionEvent(DomEvent.DomMutationEvent event,
393 DomNode target)
395 if (owner == null || owner.building)
397 return;
399 boolean doFree = false;
401 if (event == null)
403 event = getMutationEvent();
405 if (event != null)
407 doFree = true;
409 else
411 event = new DomEvent.DomMutationEvent(null);
413 event.initMutationEvent("DOMNodeInserted",
414 true /* bubbles */, false /* nocancel */,
415 this /* related */, null, null, null, (short) 0);
416 target.dispatchEvent(event);
418 // XXX should really visit every descendant of 'target'
419 // and sent a DOMNodeInsertedIntoDocument event to it...
420 // bleech, there's no way to keep that acceptably fast.
422 if (doFree)
424 event.target = null;
425 event.relatedNode = null;
426 event.currentNode = null;
427 eventDataLock = false;
428 } // else we created work for the GC
431 private void removalEvent(DomEvent.DomMutationEvent event,
432 DomNode target)
434 if (owner == null || owner.building)
436 return;
438 boolean doFree = false;
440 if (event == null)
442 event = getMutationEvent();
444 if (event != null)
446 doFree = true;
448 else
450 event = new DomEvent.DomMutationEvent(null);
452 event.initMutationEvent("DOMNodeRemoved",
453 true /* bubbles */, false /* nocancel */,
454 this /* related */, null, null, null, (short) 0);
455 target.dispatchEvent(event);
457 // XXX should really visit every descendant of 'target'
458 // and sent a DOMNodeRemovedFromDocument event to it...
459 // bleech, there's no way to keep that acceptably fast.
461 event.target = null;
462 event.relatedNode = null;
463 event.currentNode = null;
464 if (doFree)
466 eventDataLock = false;
468 // else we created more work for the GC
472 // Avoid creating lots of memory management work, by using a simple
473 // allocation strategy for the mutation event objects that get used
474 // at least once per tree modification. We can't use stack allocation,
475 // so we do the next simplest thing -- more or less, static allocation.
476 // Concurrent notifications should be rare, anyway.
478 // Returns the preallocated object, which needs to be carefully freed,
479 // or null to indicate the caller needs to allocate their own.
481 static private DomEvent.DomMutationEvent getMutationEvent()
483 synchronized (lockNode)
485 if (eventDataLock)
487 return null;
489 eventDataLock = true;
490 return mutationEvent;
494 // NOTE: this is manually inlined in the insertion
495 // and removal event methods above; change in sync.
496 static private void freeMutationEvent()
498 // clear fields to enable GC
499 mutationEvent.clear();
500 eventDataLock = false;
503 void setDepth(int depth)
505 this.depth = depth;
506 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
508 ctx.setDepth(depth + 1);
513 * <b>DOM L1</b>
514 * Appends the specified node to this node's list of children.
515 * Document subclasses must override this to enforce the restrictions
516 * that there be only one element and document type child.
518 * <p> Causes a DOMNodeInserted mutation event to be reported.
519 * Will first cause a DOMNodeRemoved event to be reported if the
520 * parameter already has a parent. If the new child is a document
521 * fragment node, both events will be reported for each child of
522 * the fragment; the order in which children are removed and
523 * inserted is implementation-specific.
525 * <p> If this DOM has been compiled without mutation event support,
526 * these events will not be reported.
528 public Node appendChild(Node newChild)
532 DomNode child = (DomNode) newChild;
534 if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
536 // Append all nodes in the fragment to this node
537 for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
539 checkMisc(ctx);
541 for (DomNode ctx = child.first; ctx != null; )
543 DomNode ctxNext = ctx.next;
544 appendChild(ctx);
545 ctx = ctxNext;
548 else
550 checkMisc(child);
551 if (child.parent != null)
553 child.parent.removeChild(child);
555 child.parent = this;
556 child.index = length++;
557 child.setDepth(depth + 1);
558 child.next = null;
559 if (last == null)
561 first = child;
562 child.previous = null;
564 else
566 last.next = child;
567 child.previous = last;
569 last = child;
571 if (reportMutations)
573 insertionEvent(null, child);
577 return child;
579 catch (ClassCastException e)
581 throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
582 null, newChild, 0);
587 * <b>DOM L1</b>
588 * Inserts the specified node in this node's list of children.
589 * Document subclasses must override this to enforce the restrictions
590 * that there be only one element and document type child.
592 * <p> Causes a DOMNodeInserted mutation event to be reported. Will
593 * first cause a DOMNodeRemoved event to be reported if the newChild
594 * parameter already has a parent. If the new child is a document
595 * fragment node, both events will be reported for each child of
596 * the fragment; the order in which children are removed and inserted
597 * is implementation-specific.
599 * <p> If this DOM has been compiled without mutation event support,
600 * these events will not be reported.
602 public Node insertBefore(Node newChild, Node refChild)
604 if (refChild == null)
606 return appendChild(newChild);
611 DomNode child = (DomNode) newChild;
612 DomNode ref = (DomNode) refChild;
614 if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
616 // Append all nodes in the fragment to this node
617 for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
619 checkMisc(ctx);
621 for (DomNode ctx = child.first; ctx != null; )
623 DomNode ctxNext = ctx.next;
624 insertBefore(ctx, ref);
625 ctx = ctxNext;
628 else
630 checkMisc(child);
631 if (ref == null || ref.parent != this)
633 throw new DomDOMException(DOMException.NOT_FOUND_ERR,
634 null, ref, 0);
636 if (ref == child)
638 throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
639 "can't insert node before itself",
640 ref, 0);
643 if (child.parent != null)
645 child.parent.removeChild(child);
647 child.parent = this;
648 int i = ref.index;
649 child.setDepth(depth + 1);
650 child.next = ref;
651 if (ref.previous != null)
653 ref.previous.next = child;
655 child.previous = ref.previous;
656 ref.previous = child;
657 if (first == ref)
659 first = child;
661 // index renumbering
662 for (DomNode ctx = child; ctx != null; ctx = ctx.next)
664 ctx.index = i++;
667 if (reportMutations)
669 insertionEvent(null, child);
673 return child;
675 catch (ClassCastException e)
677 throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
678 null, newChild, 0);
683 * <b>DOM L1</b>
684 * Replaces the specified node in this node's list of children.
685 * Document subclasses must override this to test the restrictions
686 * that there be only one element and document type child.
688 * <p> Causes DOMNodeRemoved and DOMNodeInserted mutation event to be
689 * reported. Will cause another DOMNodeRemoved event to be reported if
690 * the newChild parameter already has a parent. These events may be
691 * delivered in any order, except that the event reporting removal
692 * from such an existing parent will always be delivered before the
693 * event reporting its re-insertion as a child of some other node.
694 * The order in which children are removed and inserted is implementation
695 * specific.
697 * <p> If your application needs to depend on the in which those removal
698 * and insertion events are delivered, don't use this API. Instead,
699 * invoke the removeChild and insertBefore methods directly, to guarantee
700 * a specific delivery order. Similarly, don't use document fragments,
701 * Otherwise your application code may not work on a DOM which implements
702 * this method differently.
704 * <p> If this DOM has been compiled without mutation event support,
705 * these events will not be reported.
707 public Node replaceChild(Node newChild, Node refChild)
711 DomNode child = (DomNode) newChild;
712 DomNode ref = (DomNode) refChild;
714 DomEvent.DomMutationEvent event = getMutationEvent();
715 boolean doFree = (event != null);
717 if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
719 // Append all nodes in the fragment to this node
720 for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
722 checkMisc(ctx);
724 if (ref == null || ref.parent != this)
726 throw new DomDOMException(DOMException.NOT_FOUND_ERR,
727 null, ref, 0);
730 if (reportMutations)
732 removalEvent(event, ref);
734 length--;
735 length += child.length;
737 if (child.length == 0)
739 // Removal
740 if (ref.previous != null)
742 ref.previous.next = ref.next;
744 if (ref.next != null)
746 ref.next.previous = ref.previous;
748 if (first == ref)
750 first = ref.next;
752 if (last == ref)
754 last = ref.previous;
757 else
759 int i = ref.index;
760 for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
762 // Insertion
763 ctx.parent = this;
764 ctx.index = i++;
765 ctx.setDepth(ref.depth);
766 if (ctx == child.first)
768 ctx.previous = ref.previous;
770 if (ctx == child.last)
772 ctx.next = ref.next;
775 if (first == ref)
777 first = child.first;
779 if (last == ref)
781 last = child.last;
785 else
787 checkMisc(child);
788 if (ref == null || ref.parent != this)
790 throw new DomDOMException(DOMException.NOT_FOUND_ERR,
791 null, ref, 0);
794 if (reportMutations)
796 removalEvent(event, ref);
799 if (child.parent != null)
801 child.parent.removeChild(child);
803 child.parent = this;
804 child.index = ref.index;
805 child.setDepth(ref.depth);
806 if (ref.previous != null)
808 ref.previous.next = child;
810 child.previous = ref.previous;
811 if (ref.next != null)
813 ref.next.previous = child;
815 child.next = ref.next;
816 if (first == ref)
818 first = child;
820 if (last == ref)
822 last = child;
825 if (reportMutations)
827 insertionEvent(event, child);
829 if (doFree)
831 freeMutationEvent();
834 ref.parent = null;
835 ref.index = 0;
836 ref.setDepth(0);
837 ref.previous = null;
838 ref.next = null;
840 return ref;
842 catch (ClassCastException e)
844 throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
845 null, newChild, 0);
850 * <b>DOM L1</b>
851 * Removes the specified child from this node's list of children,
852 * or else reports an exception.
854 * <p> Causes a DOMNodeRemoved mutation event to be reported.
856 * <p> If this DOM has been compiled without mutation event support,
857 * these events will not be reported.
859 public Node removeChild(Node refChild)
863 DomNode ref = (DomNode) refChild;
865 if (ref == null || ref.parent != this)
867 throw new DomDOMException(DOMException.NOT_FOUND_ERR,
868 null, ref, 0);
870 if (readonly && !owner.building)
872 throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
873 null, this, 0);
876 for (DomNode child = first; child != null; child = child.next)
878 if (child == ref)
880 if (reportMutations)
882 removalEvent(null, child);
885 length--;
886 if (ref.previous != null)
888 ref.previous.next = ref.next;
890 if (ref.next != null)
892 ref.next.previous = ref.previous;
894 if (first == ref)
896 first = ref.next;
898 if (last == ref)
900 last = ref.previous;
902 // renumber indices
903 int i = 0;
904 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
906 ctx.index = i++;
908 ref.parent = null;
909 ref.setDepth(0);
910 ref.index = 0;
911 ref.previous = null;
912 ref.next = null;
914 return ref;
917 throw new DomDOMException(DOMException.NOT_FOUND_ERR,
918 "that's no child of mine", refChild, 0);
920 catch (ClassCastException e)
922 throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
923 null, refChild, 0);
928 * <b>DOM L1 (NodeList)</b>
929 * Returns the item with the specified index in this NodeList,
930 * else null.
932 public Node item(int index)
934 DomNode child = first;
935 int count = 0;
936 while (child != null && count < index)
938 child = child.next;
939 count++;
941 return child;
945 * <b>DOM L1 (NodeList)</b>
946 * Returns the number of elements in this NodeList.
947 * (Note that many interfaces have a "Length" property, not just
948 * NodeList, and if a node subtype must implement one of those,
949 * it will also need to override getChildNodes.)
951 public int getLength()
953 return length;
957 * Minimize extra space consumed by this node to hold children and event
958 * listeners.
960 public void trimToSize()
965 * <b>DOM L1</b>
966 * Returns the previous sibling, if one is known.
968 public Node getNextSibling()
970 return next;
974 * <b>DOM L1</b>
975 * Returns the previous sibling, if one is known.
977 public Node getPreviousSibling()
979 return previous;
983 * <b>DOM L1</b>
984 * Returns the parent node, if one is known.
986 public Node getParentNode()
988 return parent;
992 * <b>DOM L2</b>
993 * Consults the DOM implementation to determine if the requested
994 * feature is supported. DocumentType subclasses must override
995 * this method, and associate themselves directly with the
996 * DOMImplementation node used. (This method relies on being able
997 * to access the DOMImplementation from the owner document, but
998 * DocumentType nodes can be created without an owner.)
1000 public boolean isSupported(String feature, String version)
1002 Document doc = owner;
1003 DOMImplementation impl = null;
1005 if (doc == null && nodeType == DOCUMENT_NODE)
1007 doc = (Document) this;
1010 if (doc == null)
1012 // possible for DocumentType
1013 throw new IllegalStateException ("unbound ownerDocument");
1016 impl = doc.getImplementation();
1017 return impl.hasFeature(feature, version);
1021 * <b>DOM L1 (modified in L2)</b>
1022 * Returns the owner document. This is only null for Document nodes,
1023 * and (new in L2) for DocumentType nodes which have not yet been
1024 * associated with the rest of their document.
1026 final public Document getOwnerDocument()
1028 return owner;
1032 * <b>DOM L1</b>
1033 * Does nothing; this must be overridden (along with the
1034 * getNodeValue method) for nodes with a non-null defined value.
1036 public void setNodeValue(String value)
1041 * <b>DOM L1</b>
1042 * Returns null; this must be overridden for nodes types with
1043 * a defined value, along with the setNodeValue method.
1045 public String getNodeValue()
1047 return null;
1050 /** This forces GCJ compatibility.
1051 * Without this method GCJ is unable to compile to byte code.
1053 public final short getNodeType()
1055 return nodeType;
1058 /** This forces GCJ compatibility.
1059 * Without this method GCJ seems unable to natively compile GNUJAXP.
1061 public abstract String getNodeName();
1064 * <b>DOM L2</b>
1065 * Does nothing; this must be overridden (along with the
1066 * getPrefix method) for element and attribute nodes.
1068 public void setPrefix(String prefix)
1073 * <b>DOM L2</b>
1074 * Returns null; this must be overridden for element and
1075 * attribute nodes.
1077 public String getPrefix()
1079 return null;
1083 * <b>DOM L2</b>
1084 * Returns null; this must be overridden for element and
1085 * attribute nodes.
1087 public String getNamespaceURI()
1089 return null;
1093 * <b>DOM L2</b>
1094 * Returns the node name; this must be overridden for element and
1095 * attribute nodes.
1097 public String getLocalName()
1099 return null;
1103 * <b>DOM L1</b>
1104 * Returns a clone of this node which optionally includes cloned
1105 * versions of child nodes. Clones are always mutable, except for
1106 * entity reference nodes.
1108 public Node cloneNode(boolean deep)
1110 DomNode node = (DomNode) clone();
1112 if (deep)
1114 DomDocument doc = (nodeType == DOCUMENT_NODE) ?
1115 (DomDocument) node : node.owner;
1116 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
1118 DomNode newChild = (DomNode) ctx.cloneNode(deep);
1119 newChild.setOwner(doc);
1120 node.appendChild(newChild);
1124 if (nodeType == ENTITY_REFERENCE_NODE)
1126 node.makeReadonly();
1128 notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node);
1129 return node;
1132 void notifyUserDataHandlers(short op, Node src, Node dst)
1134 if (userDataHandlers != null)
1136 for (Iterator i = userDataHandlers.entrySet().iterator(); i.hasNext(); )
1138 Map.Entry entry = (Map.Entry) i.next();
1139 String key = (String) entry.getKey();
1140 UserDataHandler handler = (UserDataHandler) entry.getValue();
1141 Object data = userData.get(key);
1142 handler.handle(op, key, data, src, dst);
1148 * Clones this node; roughly equivalent to cloneNode(false).
1149 * Element subclasses must provide a new implementation which
1150 * invokes this method to handle the basics, and then arranges
1151 * to clone any element attributes directly. Attribute subclasses
1152 * must make similar arrangements, ensuring that existing ties to
1153 * elements are broken by cloning.
1155 public Object clone()
1159 DomNode node = (DomNode) super.clone();
1161 node.parent = null;
1162 node.depth = 0;
1163 node.index = 0;
1164 node.length = 0;
1165 node.first = null;
1166 node.last = null;
1167 node.previous = null;
1168 node.next = null;
1170 node.readonly = false;
1171 node.listeners = new HashSet();
1172 node.nListeners = 0;
1173 return node;
1176 catch (CloneNotSupportedException x)
1178 throw new Error("clone didn't work");
1182 // the elements-by-tagname stuff is needed for both
1183 // elements and documents ... this is in lieu of a
1184 // common base class between Node and NodeNS.
1187 * <b>DOM L1</b>
1188 * Creates a NodeList giving array-style access to elements with
1189 * the specified name. Access is fastest if indices change by
1190 * small values, and the DOM is not modified.
1192 public NodeList getElementsByTagName(String tag)
1194 return new ShadowList(null, tag);
1198 * <b>DOM L2</b>
1199 * Creates a NodeList giving array-style access to elements with
1200 * the specified namespace and local name. Access is fastest if
1201 * indices change by small values, and the DOM is not modified.
1203 public NodeList getElementsByTagNameNS(String namespace, String local)
1205 return new ShadowList(namespace, local);
1210 // This shadow class is GC-able even when the live list it shadows
1211 // can't be, because of event registration hookups. Its finalizer
1212 // makes that live list become GC-able.
1214 final class ShadowList
1215 implements NodeList
1218 private LiveNodeList liveList;
1220 ShadowList(String ns, String local)
1222 liveList = new LiveNodeList(ns, local);
1225 public void finalize()
1227 liveList.detach();
1228 liveList = null;
1231 public Node item(int index)
1233 return liveList.item(index);
1236 public int getLength()
1238 return liveList.getLength();
1242 final class LiveNodeList
1243 implements NodeList, EventListener, NodeFilter
1246 private final boolean matchAnyURI;
1247 private final boolean matchAnyName;
1248 private final String elementURI;
1249 private final String elementName;
1251 private DomIterator current;
1252 private int lastIndex;
1254 LiveNodeList(String uri, String name)
1256 elementURI = uri;
1257 elementName = name;
1258 matchAnyURI = "*".equals(uri);
1259 matchAnyName = "*".equals(name);
1261 DomNode.this.addEventListener("DOMNodeInserted", this, true);
1262 DomNode.this.addEventListener("DOMNodeRemoved", this, true);
1265 void detach()
1267 if (current != null)
1268 current.detach();
1269 current = null;
1271 DomNode.this.removeEventListener("DOMNodeInserted", this, true);
1272 DomNode.this.removeEventListener("DOMNodeRemoved", this, true);
1275 public short acceptNode(Node element)
1277 if (element == DomNode.this)
1279 return FILTER_SKIP;
1282 // use namespace-aware matching ...
1283 if (elementURI != null)
1285 if (!(matchAnyURI
1286 || elementURI.equals(element.getNamespaceURI())))
1288 return FILTER_SKIP;
1290 if (!(matchAnyName
1291 || elementName.equals(element.getLocalName())))
1293 return FILTER_SKIP;
1296 // ... or qName-based kind.
1298 else
1300 if (!(matchAnyName
1301 || elementName.equals(element.getNodeName())))
1303 return FILTER_SKIP;
1306 return FILTER_ACCEPT;
1309 private DomIterator createIterator()
1311 return new DomIterator(DomNode.this,
1312 NodeFilter.SHOW_ELEMENT,
1313 this, /* filter */
1314 true /* expand entity refs */
1318 public void handleEvent(Event e)
1320 MutationEvent mutation = (MutationEvent) e;
1321 Node related = mutation.getRelatedNode();
1323 // XXX if it's got children ... check all kids too, they
1324 // will invalidate our saved index
1326 if (related.getNodeType() != Node.ELEMENT_NODE ||
1327 related.getNodeName() != elementName ||
1328 related.getNamespaceURI() != elementURI)
1330 return;
1333 if (current != null)
1334 current.detach();
1335 current = null;
1338 public Node item(int index)
1340 if (current == null)
1342 current = createIterator();
1343 lastIndex = -1;
1346 // last node or before? go backwards
1347 if (index <= lastIndex) {
1348 while (index != lastIndex) {
1349 current.previousNode ();
1350 lastIndex--;
1352 Node ret = current.previousNode ();
1353 current.detach();
1354 current = null;
1355 return ret;
1358 // somewhere after last node
1359 while (++lastIndex != index)
1360 current.nextNode ();
1362 Node ret = current.nextNode ();
1363 current.detach();
1364 current = null;
1365 return ret;
1368 public int getLength()
1370 int retval = 0;
1371 NodeIterator iter = createIterator();
1373 while (iter.nextNode() != null)
1375 retval++;
1377 iter.detach();
1378 return retval;
1384 // EventTarget support
1386 static final class ListenerRecord
1389 String type;
1390 EventListener listener;
1391 boolean useCapture;
1393 // XXX use JDK 1.2 java.lang.ref.WeakReference to listener,
1394 // and we can both get rid of "shadow" classes and remove
1395 // the need for applications to apply similar trix ... but
1396 // JDK 1.2 support isn't generally available yet
1398 ListenerRecord(String type, EventListener listener, boolean useCapture)
1400 this.type = type.intern();
1401 this.listener = listener;
1402 this.useCapture = useCapture;
1405 public boolean equals(Object o)
1407 ListenerRecord rec = (ListenerRecord)o;
1408 return listener == rec.listener
1409 && useCapture == rec.useCapture
1410 && type == rec.type;
1413 public int hashCode()
1415 return listener.hashCode() ^ type.hashCode();
1420 * <b>DOM L2 (Events)</b>
1421 * Returns an instance of the specified type of event object.
1422 * Understands about DOM Mutation, HTML, and UI events.
1424 * <p>If the name of the event type begins with "USER-", then an object
1425 * implementing the "Event" class will be returned; this provides a
1426 * limited facility for application-defined events to use the DOM event
1427 * infrastructure. Alternatively, use one of the standard DOM event
1428 * classes and initialize it using use such a "USER-" event type name;
1429 * or defin, instantiate, and initialize an application-specific subclass
1430 * of DomEvent and pass that to dispatchEvent().
1432 * @param eventType Identifies the particular DOM feature module
1433 * defining the type of event, such as "MutationEvents".
1434 * <em>The event "name" is a different kind of "type".</em>
1436 public Event createEvent(String eventType)
1438 eventType = eventType.toLowerCase();
1440 if ("mutationevents".equals(eventType))
1442 return new DomEvent.DomMutationEvent(null);
1445 if ("htmlevents".equals(eventType)
1446 || "events".equals(eventType)
1447 || "user-events".equals(eventType))
1449 return new DomEvent(null);
1452 if ("uievents".equals(eventType))
1454 return new DomEvent.DomUIEvent(null);
1457 // mouse events
1459 throw new DomDOMException(DOMException.NOT_SUPPORTED_ERR,
1460 eventType, null, 0);
1464 * <b>DOM L2 (Events)</b>
1465 * Registers an event listener's interest in a class of events.
1467 public final void addEventListener(String type,
1468 EventListener listener,
1469 boolean useCapture)
1471 // prune duplicates
1472 ListenerRecord record;
1474 record = new ListenerRecord(type, listener, useCapture);
1475 listeners.add(record);
1476 nListeners = listeners.size();
1479 // XXX this exception should be discarded from DOM
1481 // this class can be instantiated, unlike the one in the spec
1482 static final class DomEventException
1483 extends EventException
1486 DomEventException()
1488 super(UNSPECIFIED_EVENT_TYPE_ERR, "unspecified event type");
1494 * <b>DOM L2 (Events)</b>
1495 * Delivers an event to all relevant listeners, returning true if the
1496 * caller should perform their default action. Note that the event
1497 * must have been provided by the createEvent() method on this
1498 * class, else it can't be dispatched.
1500 * @see #createEvent
1502 * @exception NullPointerException When a null event is passed.
1503 * @exception ClassCastException When the event wasn't provided by
1504 * the createEvent method, or otherwise isn't a DomEvent.
1505 * @exception EventException If the event type wasn't specified
1507 public final boolean dispatchEvent(Event event)
1508 throws EventException
1510 DomEvent e = (DomEvent) event;
1511 DomNode[] ancestors = null;
1512 int ancestorMax = 0;
1513 boolean haveDispatchDataLock = false;
1515 if (e.type == null)
1517 throw new DomEventException();
1520 e.doDefault = true;
1521 e.target = this;
1524 // Typical case: one nonrecursive dispatchEvent call at a time
1525 // for this class. If that's our case, we can avoid allocating
1526 // garbage, which is overall a big win. Even with advanced GCs
1527 // that deal well with short-lived garbage, and wayfast allocators,
1528 // it still helps.
1530 // Remember -- EVERY mutation goes though here at least once.
1532 // When populating a DOM tree, trying to send mutation events is
1533 // the primary cost; this dominates the critical path.
1537 DomNode current;
1538 int index;
1539 boolean haveAncestorRegistrations = false;
1540 ListenerRecord[] notificationSet;
1541 int ancestorLen;
1543 synchronized (lockNode)
1545 if (!dispatchDataLock)
1547 haveDispatchDataLock = dispatchDataLock = true;
1548 notificationSet = DomNode.notificationSet;
1549 ancestors = DomNode.ancestors;
1551 else
1553 notificationSet = new ListenerRecord[NOTIFICATIONS_INIT];
1554 ancestors = new DomNode[ANCESTORS_INIT];
1556 ancestorLen = ancestors.length;
1559 // XXX autogrow ancestors ... based on statistics
1561 // Climb to the top of this subtree and handle capture, letting
1562 // each node (from the top down) capture until one stops it or
1563 // until we get to this one.
1565 for (index = 0, current = parent;
1566 current != null && index < ancestorLen;
1567 index++, current = current.parent)
1569 if (current.nListeners != 0)
1571 haveAncestorRegistrations = true;
1573 ancestors [index] = current;
1575 if (current != null)
1577 throw new RuntimeException("dispatchEvent capture stack size");
1580 ancestorMax = index;
1581 e.stop = false;
1583 if (haveAncestorRegistrations)
1585 e.eventPhase = Event.CAPTURING_PHASE;
1586 while (!e.stop && index-- > 0)
1588 current = ancestors [index];
1589 if (current.nListeners != 0)
1591 notifyNode(e, current, true, notificationSet);
1596 // Always deliver events to the target node (this)
1597 // unless stopPropagation was called. If we saw
1598 // no registrations yet (typical!), we never will.
1599 if (!e.stop && nListeners != 0)
1601 e.eventPhase = Event.AT_TARGET;
1602 notifyNode (e, this, false, notificationSet);
1604 else if (!haveAncestorRegistrations)
1606 e.stop = true;
1609 // If the event bubbles and propagation wasn't halted,
1610 // walk back up the ancestor list. Stop bubbling when
1611 // any bubbled event handler stops it.
1613 if (!e.stop && e.bubbles)
1615 e.eventPhase = Event.BUBBLING_PHASE;
1616 for (index = 0;
1617 !e.stop
1618 && index < ancestorMax
1619 && (current = ancestors[index]) != null;
1620 index++)
1622 if (current.nListeners != 0)
1624 notifyNode(e, current, false, notificationSet);
1628 e.eventPhase = 0;
1630 // Caller chooses whether to perform the default
1631 // action based on return from this method.
1632 return e.doDefault;
1635 finally
1637 if (haveDispatchDataLock)
1639 // synchronize to force write ordering
1640 synchronized (lockNode)
1642 // null out refs to ensure they'll be GC'd
1643 for (int i = 0; i < ancestorMax; i++)
1645 ancestors [i] = null;
1647 // notificationSet handled by notifyNode
1649 dispatchDataLock = false;
1655 private void notifyNode(DomEvent e,
1656 DomNode current,
1657 boolean capture,
1658 ListenerRecord[] notificationSet)
1660 int count = 0;
1661 Iterator iter;
1663 iter = current.listeners.iterator();
1665 // do any of this set of listeners get notified?
1666 while (iter.hasNext())
1668 ListenerRecord rec = (ListenerRecord)iter.next();
1670 if (rec.useCapture != capture)
1672 continue;
1674 if (!e.type.equals (rec.type))
1676 continue;
1678 if (count >= notificationSet.length)
1680 // very simple growth algorithm
1681 int len = Math.max(notificationSet.length, 1);
1682 ListenerRecord[] tmp = new ListenerRecord[len * 2];
1683 System.arraycopy(notificationSet, 0, tmp, 0,
1684 notificationSet.length);
1685 notificationSet = tmp;
1687 notificationSet[count++] = rec;
1689 iter = null;
1691 // Notify just those listeners
1692 e.currentNode = current;
1693 for (int i = 0; i < count; i++)
1697 iter = current.listeners.iterator();
1698 // Late in the DOM CR process (3rd or 4th CR?) the
1699 // removeEventListener spec became asymmetric with respect
1700 // to addEventListener ... effect is now immediate.
1701 while (iter.hasNext())
1703 ListenerRecord rec = (ListenerRecord)iter.next();
1705 if (rec.equals(notificationSet[i]))
1707 notificationSet[i].listener.handleEvent(e);
1708 break;
1711 iter = null;
1713 catch (Exception x)
1715 // ignore all exceptions
1717 notificationSet[i] = null; // free for GC
1722 * <b>DOM L2 (Events)</b>
1723 * Unregisters an event listener.
1725 public final void removeEventListener(String type,
1726 EventListener listener,
1727 boolean useCapture)
1729 listeners.remove(new ListenerRecord(type, listener, useCapture));
1730 nListeners = listeners.size();
1731 // no exceptions reported
1735 * <b>DOM L1 (relocated in DOM L2)</b>
1736 * In this node and all contained nodes (including attributes if
1737 * relevant) merge adjacent text nodes. This is done while ignoring
1738 * text which happens to use CDATA delimiters).
1740 public final void normalize()
1742 // Suspend readonly status
1743 boolean saved = readonly;
1744 readonly = false;
1745 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
1747 boolean saved2 = ctx.readonly;
1748 ctx.readonly = false;
1749 switch (ctx.nodeType)
1751 case TEXT_NODE:
1752 case CDATA_SECTION_NODE:
1753 while (ctx.next != null &&
1754 (ctx.next.nodeType == TEXT_NODE ||
1755 ctx.next.nodeType == CDATA_SECTION_NODE))
1757 Text text = (Text) ctx;
1758 text.appendData(ctx.next.getNodeValue());
1759 removeChild(ctx.next);
1761 break;
1762 case ELEMENT_NODE:
1763 NamedNodeMap attrs = ctx.getAttributes();
1764 int len = attrs.getLength();
1765 for (int i = 0; i < len; i++)
1767 DomNode attr = (DomNode) attrs.item(i);
1768 boolean saved3 = attr.readonly;
1769 attr.readonly = false;
1770 attr.normalize();
1771 attr.readonly = saved3;
1773 // Fall through
1774 case DOCUMENT_NODE:
1775 case DOCUMENT_FRAGMENT_NODE:
1776 case ATTRIBUTE_NODE:
1777 case ENTITY_REFERENCE_NODE:
1778 ctx.normalize();
1779 break;
1781 ctx.readonly = saved2;
1783 readonly = saved;
1787 * Returns true iff node types match, and either (a) both nodes have no
1788 * namespace and their getNodeName() values are the same, or (b) both
1789 * nodes have the same getNamespaceURI() and same getLocalName() values.
1791 * <p>Note that notion of a "Per-Element-Type" attribute name scope, as
1792 * found in a non-normative appendix of the XML Namespaces specification,
1793 * is not supported here. Your application must implement that notion,
1794 * typically by not bothering to check nameAndTypeEquals for attributes
1795 * without namespace URIs unless you already know their elements are
1796 * nameAndTypeEquals.
1798 public boolean nameAndTypeEquals(Node other)
1800 if (other == this)
1802 return true;
1804 // node types must match
1805 if (nodeType != other.getNodeType())
1807 return false;
1810 // if both have namespaces, do a "full" comparision
1811 // this is a "global" partition
1812 String ns1 = this.getNamespaceURI();
1813 String ns2 = other.getNamespaceURI();
1815 if (ns1 != null && ns2 != null)
1817 return ns1.equals(ns2) &&
1818 equal(getLocalName(), other.getLocalName());
1821 // if neither has a namespace, this is a "no-namespace" name.
1822 if (ns1 == null && ns2 == null)
1824 if (!getNodeName().equals(other.getNodeName()))
1826 return false;
1828 // can test the non-normative "per-element-type" scope here.
1829 // if this is an attribute node and both nodes have been bound
1830 // to elements (!!), then return the nameAndTypeEquals()
1831 // comparison of those elements.
1832 return true;
1835 // otherwise they're unequal: one scoped, one not.
1836 return false;
1839 // DOM Level 3 methods
1841 public String getBaseURI()
1843 return (parent != null) ? parent.getBaseURI() : null;
1846 public short compareDocumentPosition(Node other)
1847 throws DOMException
1849 return (short) compareTo(other);
1853 * DOM nodes have a natural ordering: document order.
1855 public final int compareTo(Object other)
1857 if (other instanceof DomNode)
1859 DomNode n1 = this;
1860 DomNode n2 = (DomNode) other;
1861 if (n1.owner != n2.owner)
1863 return 0;
1865 int d1 = n1.depth, d2 = n2.depth;
1866 int delta = d1 - d2;
1867 while (d1 > d2)
1869 n1 = n1.parent;
1870 d1--;
1872 while (d2 > d1)
1874 n2 = n2.parent;
1875 d2--;
1877 int c = compareTo2(n1, n2);
1878 return (c != 0) ? c : delta;
1880 return 0;
1884 * Compare two nodes at the same depth.
1886 final int compareTo2(DomNode n1, DomNode n2)
1888 if (n1 == n2 || n1.depth == 0 || n2.depth == 0)
1890 return 0;
1892 int c = compareTo2(n1.parent, n2.parent);
1893 return (c != 0) ? c : n1.index - n2.index;
1896 public final String getTextContent()
1897 throws DOMException
1899 return getTextContent(true);
1902 final String getTextContent(boolean topLevel)
1903 throws DOMException
1905 switch (nodeType)
1907 case ELEMENT_NODE:
1908 case ENTITY_NODE:
1909 case ENTITY_REFERENCE_NODE:
1910 case DOCUMENT_FRAGMENT_NODE:
1911 StringBuffer buffer = new StringBuffer();
1912 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
1914 String textContent = ctx.getTextContent(false);
1915 if (textContent != null)
1917 buffer.append(textContent);
1920 return buffer.toString();
1921 case TEXT_NODE:
1922 case CDATA_SECTION_NODE:
1923 if (((Text) this).isElementContentWhitespace())
1925 return "";
1927 return getNodeValue();
1928 case ATTRIBUTE_NODE:
1929 return getNodeValue();
1930 case COMMENT_NODE:
1931 case PROCESSING_INSTRUCTION_NODE:
1932 return topLevel ? getNodeValue() : "";
1933 default:
1934 return null;
1938 public void setTextContent(String textContent)
1939 throws DOMException
1941 switch (nodeType)
1943 case ELEMENT_NODE:
1944 case ATTRIBUTE_NODE:
1945 case ENTITY_NODE:
1946 case ENTITY_REFERENCE_NODE:
1947 case DOCUMENT_FRAGMENT_NODE:
1948 for (DomNode ctx = first; ctx != null; )
1950 DomNode n = ctx.next;
1951 removeChild(ctx);
1952 ctx = n;
1954 if (textContent != null)
1956 Text text = owner.createTextNode(textContent);
1957 appendChild(text);
1959 break;
1960 case TEXT_NODE:
1961 case CDATA_SECTION_NODE:
1962 case COMMENT_NODE:
1963 case PROCESSING_INSTRUCTION_NODE:
1964 setNodeValue(textContent);
1965 break;
1969 public boolean isSameNode(Node other)
1971 return this == other;
1974 public String lookupPrefix(String namespaceURI)
1976 return (parent == null || parent == owner) ? null :
1977 parent.lookupPrefix(namespaceURI);
1980 public boolean isDefaultNamespace(String namespaceURI)
1982 return (parent == null || parent == owner) ? false :
1983 parent.isDefaultNamespace(namespaceURI);
1986 public String lookupNamespaceURI(String prefix)
1988 return (parent == null || parent == owner) ? null :
1989 parent.lookupNamespaceURI(prefix);
1992 public boolean isEqualNode(Node arg)
1994 if (this == arg)
1995 return true;
1996 if (arg == null)
1997 return false;
1998 if (nodeType != arg.getNodeType())
1999 return false;
2000 switch (nodeType)
2002 case ELEMENT_NODE:
2003 case ATTRIBUTE_NODE:
2004 if (!equal(getLocalName(), arg.getLocalName()) ||
2005 !equal(getNamespaceURI(), arg.getNamespaceURI()))
2006 return false;
2007 break;
2008 case PROCESSING_INSTRUCTION_NODE:
2009 if (!equal(getNodeName(), arg.getNodeName()) ||
2010 !equal(getNodeValue(), arg.getNodeValue()))
2011 return false;
2012 break;
2013 case COMMENT_NODE:
2014 case TEXT_NODE:
2015 case CDATA_SECTION_NODE:
2016 if (!equal(getNodeValue(), arg.getNodeValue()))
2017 return false;
2018 break;
2020 // Children
2021 Node argCtx = arg.getFirstChild();
2022 getFirstChild(); // because of DomAttr lazy children
2023 DomNode ctx = first;
2024 for (; ctx != null && argCtx != null; ctx = ctx.next)
2026 if (nodeType == DOCUMENT_NODE)
2028 // Ignore whitespace outside document element
2029 while (ctx != null && ctx.nodeType == TEXT_NODE)
2030 ctx = ctx.next;
2031 while (argCtx != null && ctx.getNodeType() == TEXT_NODE)
2032 argCtx = argCtx.getNextSibling();
2033 if (ctx == null && argCtx != null)
2034 return false;
2035 else if (argCtx == null && ctx != null)
2036 return false;
2038 if (!ctx.isEqualNode(argCtx))
2039 return false;
2040 argCtx = argCtx.getNextSibling();
2042 if (ctx != null || argCtx != null)
2043 return false;
2045 // TODO DocumentType
2046 return true;
2049 boolean equal(String arg1, String arg2)
2051 return ((arg1 == null && arg2 == null) ||
2052 (arg1 != null && arg1.equals(arg2)));
2055 public Object getFeature(String feature, String version)
2057 DOMImplementation impl = (nodeType == DOCUMENT_NODE) ?
2058 ((Document) this).getImplementation() : owner.getImplementation();
2059 if (impl.hasFeature(feature, version))
2061 return this;
2063 return null;
2066 public Object setUserData(String key, Object data, UserDataHandler handler)
2068 if (userData == null)
2070 userData = new HashMap();
2072 if (handler != null)
2074 if (userDataHandlers == null)
2076 userDataHandlers = new HashMap();
2078 userDataHandlers.put(key, handler);
2080 return userData.put(key, data);
2083 public Object getUserData(String key)
2085 if (userData == null)
2087 return null;
2089 return userData.get(key);
2092 public String toString()
2094 String nodeName = getNodeName();
2095 String nodeValue = getNodeValue();
2096 StringBuffer buf = new StringBuffer(getClass().getName());
2097 buf.append('[');
2098 if (nodeName != null)
2100 buf.append(nodeName);
2102 if (nodeValue != null)
2104 if (nodeName != null)
2106 buf.append('=');
2108 buf.append('\'');
2109 buf.append(encode(nodeValue));
2110 buf.append('\'');
2112 buf.append(']');
2113 return buf.toString();
2116 String encode(String value)
2118 StringBuffer buf = null;
2119 int len = value.length();
2120 for (int i = 0; i < len; i++)
2122 char c = value.charAt(i);
2123 if (c == '\n')
2125 if (buf == null)
2127 buf = new StringBuffer(value.substring(0, i));
2129 buf.append("\\n");
2131 else if (c == '\r')
2133 if (buf == null)
2135 buf = new StringBuffer(value.substring(0, i));
2137 buf.append("\\r");
2139 else if (buf != null)
2141 buf.append(c);
2144 return (buf != null) ? buf.toString() : value;
2147 String nodeTypeToString(short nodeType)
2149 switch (nodeType)
2151 case ELEMENT_NODE:
2152 return "ELEMENT_NODE";
2153 case ATTRIBUTE_NODE:
2154 return "ATTRIBUTE_NODE";
2155 case TEXT_NODE:
2156 return "TEXT_NODE";
2157 case CDATA_SECTION_NODE:
2158 return "CDATA_SECTION_NODE";
2159 case DOCUMENT_NODE:
2160 return "DOCUMENT_NODE";
2161 case DOCUMENT_TYPE_NODE:
2162 return "DOCUMENT_TYPE_NODE";
2163 case COMMENT_NODE:
2164 return "COMMENT_NODE";
2165 case PROCESSING_INSTRUCTION_NODE:
2166 return "PROCESSING_INSTRUCTION_NODE";
2167 case DOCUMENT_FRAGMENT_NODE:
2168 return "DOCUMENT_FRAGMENT_NODE";
2169 case ENTITY_NODE:
2170 return "ENTITY_NODE";
2171 case ENTITY_REFERENCE_NODE:
2172 return "ENTITY_REFERENCE_NODE";
2173 case NOTATION_NODE:
2174 return "NOTATION_NODE";
2175 default:
2176 return "UNKNOWN";
2180 public void list(java.io.PrintStream out, int indent)
2182 for (int i = 0; i < indent; i++)
2183 out.print(" ");
2184 out.println(toString());
2185 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
2186 ctx.list(out, indent + 1);