Imported GNU Classpath 0.90
[official-gcc.git] / libjava / classpath / gnu / xml / dom / DomNode.java
blobf0915eb5e93e254c234b327dbc9a1e746c9ef892
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 boolean building = doc.building;
1117 doc.building = true; // Permit certain structural rules
1118 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
1120 DomNode newChild = (DomNode) ctx.cloneNode(deep);
1121 newChild.setOwner(doc);
1122 node.appendChild(newChild);
1124 doc.building = building;
1126 if (nodeType == ENTITY_REFERENCE_NODE)
1128 node.makeReadonly();
1130 notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node);
1131 return node;
1134 void notifyUserDataHandlers(short op, Node src, Node dst)
1136 if (userDataHandlers != null)
1138 for (Iterator i = userDataHandlers.entrySet().iterator(); i.hasNext(); )
1140 Map.Entry entry = (Map.Entry) i.next();
1141 String key = (String) entry.getKey();
1142 UserDataHandler handler = (UserDataHandler) entry.getValue();
1143 Object data = userData.get(key);
1144 handler.handle(op, key, data, src, dst);
1150 * Clones this node; roughly equivalent to cloneNode(false).
1151 * Element subclasses must provide a new implementation which
1152 * invokes this method to handle the basics, and then arranges
1153 * to clone any element attributes directly. Attribute subclasses
1154 * must make similar arrangements, ensuring that existing ties to
1155 * elements are broken by cloning.
1157 public Object clone()
1161 DomNode node = (DomNode) super.clone();
1163 node.parent = null;
1164 node.depth = 0;
1165 node.index = 0;
1166 node.length = 0;
1167 node.first = null;
1168 node.last = null;
1169 node.previous = null;
1170 node.next = null;
1172 node.readonly = false;
1173 node.listeners = new HashSet();
1174 node.nListeners = 0;
1175 return node;
1178 catch (CloneNotSupportedException x)
1180 throw new Error("clone didn't work");
1184 // the elements-by-tagname stuff is needed for both
1185 // elements and documents ... this is in lieu of a
1186 // common base class between Node and NodeNS.
1189 * <b>DOM L1</b>
1190 * Creates a NodeList giving array-style access to elements with
1191 * the specified name. Access is fastest if indices change by
1192 * small values, and the DOM is not modified.
1194 public NodeList getElementsByTagName(String tag)
1196 return new ShadowList(null, tag);
1200 * <b>DOM L2</b>
1201 * Creates a NodeList giving array-style access to elements with
1202 * the specified namespace and local name. Access is fastest if
1203 * indices change by small values, and the DOM is not modified.
1205 public NodeList getElementsByTagNameNS(String namespace, String local)
1207 return new ShadowList(namespace, local);
1212 // This shadow class is GC-able even when the live list it shadows
1213 // can't be, because of event registration hookups. Its finalizer
1214 // makes that live list become GC-able.
1216 final class ShadowList
1217 implements NodeList
1220 private LiveNodeList liveList;
1222 ShadowList(String ns, String local)
1224 liveList = new LiveNodeList(ns, local);
1227 public void finalize()
1229 liveList.detach();
1230 liveList = null;
1233 public Node item(int index)
1235 return liveList.item(index);
1238 public int getLength()
1240 return liveList.getLength();
1244 final class LiveNodeList
1245 implements NodeList, EventListener, NodeFilter
1248 private final boolean matchAnyURI;
1249 private final boolean matchAnyName;
1250 private final String elementURI;
1251 private final String elementName;
1253 private DomIterator current;
1254 private int lastIndex;
1256 LiveNodeList(String uri, String name)
1258 elementURI = uri;
1259 elementName = name;
1260 matchAnyURI = "*".equals(uri);
1261 matchAnyName = "*".equals(name);
1263 DomNode.this.addEventListener("DOMNodeInserted", this, true);
1264 DomNode.this.addEventListener("DOMNodeRemoved", this, true);
1267 void detach()
1269 if (current != null)
1270 current.detach();
1271 current = null;
1273 DomNode.this.removeEventListener("DOMNodeInserted", this, true);
1274 DomNode.this.removeEventListener("DOMNodeRemoved", this, true);
1277 public short acceptNode(Node element)
1279 if (element == DomNode.this)
1281 return FILTER_SKIP;
1284 // use namespace-aware matching ...
1285 if (elementURI != null)
1287 if (!(matchAnyURI
1288 || elementURI.equals(element.getNamespaceURI())))
1290 return FILTER_SKIP;
1292 if (!(matchAnyName
1293 || elementName.equals(element.getLocalName())))
1295 return FILTER_SKIP;
1298 // ... or qName-based kind.
1300 else
1302 if (!(matchAnyName
1303 || elementName.equals(element.getNodeName())))
1305 return FILTER_SKIP;
1308 return FILTER_ACCEPT;
1311 private DomIterator createIterator()
1313 return new DomIterator(DomNode.this,
1314 NodeFilter.SHOW_ELEMENT,
1315 this, /* filter */
1316 true /* expand entity refs */
1320 public void handleEvent(Event e)
1322 MutationEvent mutation = (MutationEvent) e;
1323 Node related = mutation.getRelatedNode();
1325 // XXX if it's got children ... check all kids too, they
1326 // will invalidate our saved index
1328 if (related.getNodeType() != Node.ELEMENT_NODE ||
1329 related.getNodeName() != elementName ||
1330 related.getNamespaceURI() != elementURI)
1332 return;
1335 if (current != null)
1336 current.detach();
1337 current = null;
1340 public Node item(int index)
1342 if (current == null)
1344 current = createIterator();
1345 lastIndex = -1;
1348 // last node or before? go backwards
1349 if (index <= lastIndex) {
1350 while (index != lastIndex) {
1351 current.previousNode ();
1352 lastIndex--;
1354 Node ret = current.previousNode ();
1355 current.detach();
1356 current = null;
1357 return ret;
1360 // somewhere after last node
1361 while (++lastIndex != index)
1362 current.nextNode ();
1364 Node ret = current.nextNode ();
1365 current.detach();
1366 current = null;
1367 return ret;
1370 public int getLength()
1372 int retval = 0;
1373 NodeIterator iter = createIterator();
1375 while (iter.nextNode() != null)
1377 retval++;
1379 iter.detach();
1380 return retval;
1386 // EventTarget support
1388 static final class ListenerRecord
1391 String type;
1392 EventListener listener;
1393 boolean useCapture;
1395 // XXX use JDK 1.2 java.lang.ref.WeakReference to listener,
1396 // and we can both get rid of "shadow" classes and remove
1397 // the need for applications to apply similar trix ... but
1398 // JDK 1.2 support isn't generally available yet
1400 ListenerRecord(String type, EventListener listener, boolean useCapture)
1402 this.type = type.intern();
1403 this.listener = listener;
1404 this.useCapture = useCapture;
1407 public boolean equals(Object o)
1409 ListenerRecord rec = (ListenerRecord)o;
1410 return listener == rec.listener
1411 && useCapture == rec.useCapture
1412 && type == rec.type;
1415 public int hashCode()
1417 return listener.hashCode() ^ type.hashCode();
1422 * <b>DOM L2 (Events)</b>
1423 * Returns an instance of the specified type of event object.
1424 * Understands about DOM Mutation, HTML, and UI events.
1426 * <p>If the name of the event type begins with "USER-", then an object
1427 * implementing the "Event" class will be returned; this provides a
1428 * limited facility for application-defined events to use the DOM event
1429 * infrastructure. Alternatively, use one of the standard DOM event
1430 * classes and initialize it using use such a "USER-" event type name;
1431 * or defin, instantiate, and initialize an application-specific subclass
1432 * of DomEvent and pass that to dispatchEvent().
1434 * @param eventType Identifies the particular DOM feature module
1435 * defining the type of event, such as "MutationEvents".
1436 * <em>The event "name" is a different kind of "type".</em>
1438 public Event createEvent(String eventType)
1440 eventType = eventType.toLowerCase();
1442 if ("mutationevents".equals(eventType))
1444 return new DomEvent.DomMutationEvent(null);
1447 if ("htmlevents".equals(eventType)
1448 || "events".equals(eventType)
1449 || "user-events".equals(eventType))
1451 return new DomEvent(null);
1454 if ("uievents".equals(eventType))
1456 return new DomEvent.DomUIEvent(null);
1459 // mouse events
1461 throw new DomDOMException(DOMException.NOT_SUPPORTED_ERR,
1462 eventType, null, 0);
1466 * <b>DOM L2 (Events)</b>
1467 * Registers an event listener's interest in a class of events.
1469 public final void addEventListener(String type,
1470 EventListener listener,
1471 boolean useCapture)
1473 // prune duplicates
1474 ListenerRecord record;
1476 record = new ListenerRecord(type, listener, useCapture);
1477 listeners.add(record);
1478 nListeners = listeners.size();
1481 // XXX this exception should be discarded from DOM
1483 // this class can be instantiated, unlike the one in the spec
1484 static final class DomEventException
1485 extends EventException
1488 DomEventException()
1490 super(UNSPECIFIED_EVENT_TYPE_ERR, "unspecified event type");
1496 * <b>DOM L2 (Events)</b>
1497 * Delivers an event to all relevant listeners, returning true if the
1498 * caller should perform their default action. Note that the event
1499 * must have been provided by the createEvent() method on this
1500 * class, else it can't be dispatched.
1502 * @see #createEvent
1504 * @exception NullPointerException When a null event is passed.
1505 * @exception ClassCastException When the event wasn't provided by
1506 * the createEvent method, or otherwise isn't a DomEvent.
1507 * @exception EventException If the event type wasn't specified
1509 public final boolean dispatchEvent(Event event)
1510 throws EventException
1512 DomEvent e = (DomEvent) event;
1513 DomNode[] ancestors = null;
1514 int ancestorMax = 0;
1515 boolean haveDispatchDataLock = false;
1517 if (e.type == null)
1519 throw new DomEventException();
1522 e.doDefault = true;
1523 e.target = this;
1526 // Typical case: one nonrecursive dispatchEvent call at a time
1527 // for this class. If that's our case, we can avoid allocating
1528 // garbage, which is overall a big win. Even with advanced GCs
1529 // that deal well with short-lived garbage, and wayfast allocators,
1530 // it still helps.
1532 // Remember -- EVERY mutation goes though here at least once.
1534 // When populating a DOM tree, trying to send mutation events is
1535 // the primary cost; this dominates the critical path.
1539 DomNode current;
1540 int index;
1541 boolean haveAncestorRegistrations = false;
1542 ListenerRecord[] notificationSet;
1543 int ancestorLen;
1545 synchronized (lockNode)
1547 if (!dispatchDataLock)
1549 haveDispatchDataLock = dispatchDataLock = true;
1550 notificationSet = DomNode.notificationSet;
1551 ancestors = DomNode.ancestors;
1553 else
1555 notificationSet = new ListenerRecord[NOTIFICATIONS_INIT];
1556 ancestors = new DomNode[ANCESTORS_INIT];
1558 ancestorLen = ancestors.length;
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.
1564 current = parent;
1565 if (current.depth >= ANCESTORS_INIT)
1567 DomNode[] newants = new DomNode[current.depth + 1];
1568 System.arraycopy(ancestors, 0, newants, 0, ancestors.length);
1569 ancestors = newants;
1570 ancestorLen = ancestors.length;
1572 for (index = 0; index < ancestorLen; index++)
1574 if (current == null || current.depth == 0)
1575 break;
1577 if (current.nListeners != 0)
1579 haveAncestorRegistrations = true;
1581 ancestors [index] = current;
1582 current = current.parent;
1584 if (current.depth > 0)
1586 throw new RuntimeException("dispatchEvent capture stack size");
1589 ancestorMax = index;
1590 e.stop = false;
1592 if (haveAncestorRegistrations)
1594 e.eventPhase = Event.CAPTURING_PHASE;
1595 while (!e.stop && index-- > 0)
1597 current = ancestors [index];
1598 if (current.nListeners != 0)
1600 notifyNode(e, current, true, notificationSet);
1605 // Always deliver events to the target node (this)
1606 // unless stopPropagation was called. If we saw
1607 // no registrations yet (typical!), we never will.
1608 if (!e.stop && nListeners != 0)
1610 e.eventPhase = Event.AT_TARGET;
1611 notifyNode (e, this, false, notificationSet);
1613 else if (!haveAncestorRegistrations)
1615 e.stop = true;
1618 // If the event bubbles and propagation wasn't halted,
1619 // walk back up the ancestor list. Stop bubbling when
1620 // any bubbled event handler stops it.
1622 if (!e.stop && e.bubbles)
1624 e.eventPhase = Event.BUBBLING_PHASE;
1625 for (index = 0;
1626 !e.stop
1627 && index < ancestorMax
1628 && (current = ancestors[index]) != null;
1629 index++)
1631 if (current.nListeners != 0)
1633 notifyNode(e, current, false, notificationSet);
1637 e.eventPhase = 0;
1639 // Caller chooses whether to perform the default
1640 // action based on return from this method.
1641 return e.doDefault;
1644 finally
1646 if (haveDispatchDataLock)
1648 // synchronize to force write ordering
1649 synchronized (lockNode)
1651 // null out refs to ensure they'll be GC'd
1652 for (int i = 0; i < ancestorMax; i++)
1654 ancestors [i] = null;
1656 // notificationSet handled by notifyNode
1658 dispatchDataLock = false;
1664 private void notifyNode(DomEvent e,
1665 DomNode current,
1666 boolean capture,
1667 ListenerRecord[] notificationSet)
1669 int count = 0;
1670 Iterator iter;
1672 iter = current.listeners.iterator();
1674 // do any of this set of listeners get notified?
1675 while (iter.hasNext())
1677 ListenerRecord rec = (ListenerRecord)iter.next();
1679 if (rec.useCapture != capture)
1681 continue;
1683 if (!e.type.equals (rec.type))
1685 continue;
1687 if (count >= notificationSet.length)
1689 // very simple growth algorithm
1690 int len = Math.max(notificationSet.length, 1);
1691 ListenerRecord[] tmp = new ListenerRecord[len * 2];
1692 System.arraycopy(notificationSet, 0, tmp, 0,
1693 notificationSet.length);
1694 notificationSet = tmp;
1696 notificationSet[count++] = rec;
1698 iter = null;
1700 // Notify just those listeners
1701 e.currentNode = current;
1702 for (int i = 0; i < count; i++)
1706 iter = current.listeners.iterator();
1707 // Late in the DOM CR process (3rd or 4th CR?) the
1708 // removeEventListener spec became asymmetric with respect
1709 // to addEventListener ... effect is now immediate.
1710 while (iter.hasNext())
1712 ListenerRecord rec = (ListenerRecord)iter.next();
1714 if (rec.equals(notificationSet[i]))
1716 notificationSet[i].listener.handleEvent(e);
1717 break;
1720 iter = null;
1722 catch (Exception x)
1724 // ignore all exceptions
1726 notificationSet[i] = null; // free for GC
1731 * <b>DOM L2 (Events)</b>
1732 * Unregisters an event listener.
1734 public final void removeEventListener(String type,
1735 EventListener listener,
1736 boolean useCapture)
1738 listeners.remove(new ListenerRecord(type, listener, useCapture));
1739 nListeners = listeners.size();
1740 // no exceptions reported
1744 * <b>DOM L1 (relocated in DOM L2)</b>
1745 * In this node and all contained nodes (including attributes if
1746 * relevant) merge adjacent text nodes. This is done while ignoring
1747 * text which happens to use CDATA delimiters).
1749 public final void normalize()
1751 // Suspend readonly status
1752 boolean saved = readonly;
1753 readonly = false;
1754 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
1756 boolean saved2 = ctx.readonly;
1757 ctx.readonly = false;
1758 switch (ctx.nodeType)
1760 case TEXT_NODE:
1761 case CDATA_SECTION_NODE:
1762 while (ctx.next != null &&
1763 (ctx.next.nodeType == TEXT_NODE ||
1764 ctx.next.nodeType == CDATA_SECTION_NODE))
1766 Text text = (Text) ctx;
1767 text.appendData(ctx.next.getNodeValue());
1768 removeChild(ctx.next);
1770 break;
1771 case ELEMENT_NODE:
1772 NamedNodeMap attrs = ctx.getAttributes();
1773 int len = attrs.getLength();
1774 for (int i = 0; i < len; i++)
1776 DomNode attr = (DomNode) attrs.item(i);
1777 boolean saved3 = attr.readonly;
1778 attr.readonly = false;
1779 attr.normalize();
1780 attr.readonly = saved3;
1782 // Fall through
1783 case DOCUMENT_NODE:
1784 case DOCUMENT_FRAGMENT_NODE:
1785 case ATTRIBUTE_NODE:
1786 case ENTITY_REFERENCE_NODE:
1787 ctx.normalize();
1788 break;
1790 ctx.readonly = saved2;
1792 readonly = saved;
1796 * Returns true iff node types match, and either (a) both nodes have no
1797 * namespace and their getNodeName() values are the same, or (b) both
1798 * nodes have the same getNamespaceURI() and same getLocalName() values.
1800 * <p>Note that notion of a "Per-Element-Type" attribute name scope, as
1801 * found in a non-normative appendix of the XML Namespaces specification,
1802 * is not supported here. Your application must implement that notion,
1803 * typically by not bothering to check nameAndTypeEquals for attributes
1804 * without namespace URIs unless you already know their elements are
1805 * nameAndTypeEquals.
1807 public boolean nameAndTypeEquals(Node other)
1809 if (other == this)
1811 return true;
1813 // node types must match
1814 if (nodeType != other.getNodeType())
1816 return false;
1819 // if both have namespaces, do a "full" comparision
1820 // this is a "global" partition
1821 String ns1 = this.getNamespaceURI();
1822 String ns2 = other.getNamespaceURI();
1824 if (ns1 != null && ns2 != null)
1826 return ns1.equals(ns2) &&
1827 equal(getLocalName(), other.getLocalName());
1830 // if neither has a namespace, this is a "no-namespace" name.
1831 if (ns1 == null && ns2 == null)
1833 if (!getNodeName().equals(other.getNodeName()))
1835 return false;
1837 // can test the non-normative "per-element-type" scope here.
1838 // if this is an attribute node and both nodes have been bound
1839 // to elements (!!), then return the nameAndTypeEquals()
1840 // comparison of those elements.
1841 return true;
1844 // otherwise they're unequal: one scoped, one not.
1845 return false;
1848 // DOM Level 3 methods
1850 public String getBaseURI()
1852 return (parent != null) ? parent.getBaseURI() : null;
1855 public short compareDocumentPosition(Node other)
1856 throws DOMException
1858 return (short) compareTo(other);
1862 * DOM nodes have a natural ordering: document order.
1864 public final int compareTo(Object other)
1866 if (other instanceof DomNode)
1868 DomNode n1 = this;
1869 DomNode n2 = (DomNode) other;
1870 if (n1.owner != n2.owner)
1872 return 0;
1874 int d1 = n1.depth, d2 = n2.depth;
1875 int delta = d1 - d2;
1876 while (d1 > d2)
1878 n1 = n1.parent;
1879 d1--;
1881 while (d2 > d1)
1883 n2 = n2.parent;
1884 d2--;
1886 int c = compareTo2(n1, n2);
1887 return (c != 0) ? c : delta;
1889 return 0;
1893 * Compare two nodes at the same depth.
1895 final int compareTo2(DomNode n1, DomNode n2)
1897 if (n1 == n2 || n1.depth == 0 || n2.depth == 0)
1899 return 0;
1901 int c = compareTo2(n1.parent, n2.parent);
1902 return (c != 0) ? c : n1.index - n2.index;
1905 public final String getTextContent()
1906 throws DOMException
1908 return getTextContent(true);
1911 final String getTextContent(boolean topLevel)
1912 throws DOMException
1914 switch (nodeType)
1916 case ELEMENT_NODE:
1917 case ENTITY_NODE:
1918 case ENTITY_REFERENCE_NODE:
1919 case DOCUMENT_FRAGMENT_NODE:
1920 StringBuffer buffer = new StringBuffer();
1921 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
1923 String textContent = ctx.getTextContent(false);
1924 if (textContent != null)
1926 buffer.append(textContent);
1929 return buffer.toString();
1930 case TEXT_NODE:
1931 case CDATA_SECTION_NODE:
1932 if (((Text) this).isElementContentWhitespace())
1934 return "";
1936 return getNodeValue();
1937 case ATTRIBUTE_NODE:
1938 return getNodeValue();
1939 case COMMENT_NODE:
1940 case PROCESSING_INSTRUCTION_NODE:
1941 return topLevel ? getNodeValue() : "";
1942 default:
1943 return null;
1947 public void setTextContent(String textContent)
1948 throws DOMException
1950 switch (nodeType)
1952 case ELEMENT_NODE:
1953 case ATTRIBUTE_NODE:
1954 case ENTITY_NODE:
1955 case ENTITY_REFERENCE_NODE:
1956 case DOCUMENT_FRAGMENT_NODE:
1957 for (DomNode ctx = first; ctx != null; )
1959 DomNode n = ctx.next;
1960 removeChild(ctx);
1961 ctx = n;
1963 if (textContent != null)
1965 Text text = owner.createTextNode(textContent);
1966 appendChild(text);
1968 break;
1969 case TEXT_NODE:
1970 case CDATA_SECTION_NODE:
1971 case COMMENT_NODE:
1972 case PROCESSING_INSTRUCTION_NODE:
1973 setNodeValue(textContent);
1974 break;
1978 public boolean isSameNode(Node other)
1980 return this == other;
1983 public String lookupPrefix(String namespaceURI)
1985 return (parent == null || parent == owner) ? null :
1986 parent.lookupPrefix(namespaceURI);
1989 public boolean isDefaultNamespace(String namespaceURI)
1991 return (parent == null || parent == owner) ? false :
1992 parent.isDefaultNamespace(namespaceURI);
1995 public String lookupNamespaceURI(String prefix)
1997 return (parent == null || parent == owner) ? null :
1998 parent.lookupNamespaceURI(prefix);
2001 public boolean isEqualNode(Node arg)
2003 if (this == arg)
2004 return true;
2005 if (arg == null)
2006 return false;
2007 if (nodeType != arg.getNodeType())
2008 return false;
2009 switch (nodeType)
2011 case ELEMENT_NODE:
2012 case ATTRIBUTE_NODE:
2013 if (!equal(getLocalName(), arg.getLocalName()) ||
2014 !equal(getNamespaceURI(), arg.getNamespaceURI()))
2015 return false;
2016 break;
2017 case PROCESSING_INSTRUCTION_NODE:
2018 if (!equal(getNodeName(), arg.getNodeName()) ||
2019 !equal(getNodeValue(), arg.getNodeValue()))
2020 return false;
2021 break;
2022 case COMMENT_NODE:
2023 case TEXT_NODE:
2024 case CDATA_SECTION_NODE:
2025 if (!equal(getNodeValue(), arg.getNodeValue()))
2026 return false;
2027 break;
2029 // Children
2030 Node argCtx = arg.getFirstChild();
2031 getFirstChild(); // because of DomAttr lazy children
2032 DomNode ctx = first;
2033 for (; ctx != null && argCtx != null; ctx = ctx.next)
2035 if (nodeType == DOCUMENT_NODE)
2037 // Ignore whitespace outside document element
2038 while (ctx != null && ctx.nodeType == TEXT_NODE)
2039 ctx = ctx.next;
2040 while (argCtx != null && ctx.getNodeType() == TEXT_NODE)
2041 argCtx = argCtx.getNextSibling();
2042 if (ctx == null && argCtx != null)
2043 return false;
2044 else if (argCtx == null && ctx != null)
2045 return false;
2047 if (!ctx.isEqualNode(argCtx))
2048 return false;
2049 argCtx = argCtx.getNextSibling();
2051 if (ctx != null || argCtx != null)
2052 return false;
2054 // TODO DocumentType
2055 return true;
2058 boolean equal(String arg1, String arg2)
2060 return ((arg1 == null && arg2 == null) ||
2061 (arg1 != null && arg1.equals(arg2)));
2064 public Object getFeature(String feature, String version)
2066 DOMImplementation impl = (nodeType == DOCUMENT_NODE) ?
2067 ((Document) this).getImplementation() : owner.getImplementation();
2068 if (impl.hasFeature(feature, version))
2070 return this;
2072 return null;
2075 public Object setUserData(String key, Object data, UserDataHandler handler)
2077 if (userData == null)
2079 userData = new HashMap();
2081 if (handler != null)
2083 if (userDataHandlers == null)
2085 userDataHandlers = new HashMap();
2087 userDataHandlers.put(key, handler);
2089 return userData.put(key, data);
2092 public Object getUserData(String key)
2094 if (userData == null)
2096 return null;
2098 return userData.get(key);
2101 public String toString()
2103 String nodeName = getNodeName();
2104 String nodeValue = getNodeValue();
2105 StringBuffer buf = new StringBuffer(getClass().getName());
2106 buf.append('[');
2107 if (nodeName != null)
2109 buf.append(nodeName);
2111 if (nodeValue != null)
2113 if (nodeName != null)
2115 buf.append('=');
2117 buf.append('\'');
2118 buf.append(encode(nodeValue));
2119 buf.append('\'');
2121 buf.append(']');
2122 return buf.toString();
2125 String encode(String value)
2127 StringBuffer buf = null;
2128 int len = value.length();
2129 for (int i = 0; i < len; i++)
2131 char c = value.charAt(i);
2132 if (c == '\n')
2134 if (buf == null)
2136 buf = new StringBuffer(value.substring(0, i));
2138 buf.append("\\n");
2140 else if (c == '\r')
2142 if (buf == null)
2144 buf = new StringBuffer(value.substring(0, i));
2146 buf.append("\\r");
2148 else if (buf != null)
2150 buf.append(c);
2153 return (buf != null) ? buf.toString() : value;
2156 String nodeTypeToString(short nodeType)
2158 switch (nodeType)
2160 case ELEMENT_NODE:
2161 return "ELEMENT_NODE";
2162 case ATTRIBUTE_NODE:
2163 return "ATTRIBUTE_NODE";
2164 case TEXT_NODE:
2165 return "TEXT_NODE";
2166 case CDATA_SECTION_NODE:
2167 return "CDATA_SECTION_NODE";
2168 case DOCUMENT_NODE:
2169 return "DOCUMENT_NODE";
2170 case DOCUMENT_TYPE_NODE:
2171 return "DOCUMENT_TYPE_NODE";
2172 case COMMENT_NODE:
2173 return "COMMENT_NODE";
2174 case PROCESSING_INSTRUCTION_NODE:
2175 return "PROCESSING_INSTRUCTION_NODE";
2176 case DOCUMENT_FRAGMENT_NODE:
2177 return "DOCUMENT_FRAGMENT_NODE";
2178 case ENTITY_NODE:
2179 return "ENTITY_NODE";
2180 case ENTITY_REFERENCE_NODE:
2181 return "ENTITY_REFERENCE_NODE";
2182 case NOTATION_NODE:
2183 return "NOTATION_NODE";
2184 default:
2185 return "UNKNOWN";
2189 public void list(java.io.PrintStream out, int indent)
2191 for (int i = 0; i < indent; i++)
2192 out.print(" ");
2193 out.println(toString());
2194 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
2195 ctx.list(out, indent + 1);