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)
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
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
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. */
40 import java
.util
.HashMap
;
41 import java
.util
.HashSet
;
42 import java
.util
.Iterator
;
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
;
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
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
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
99 //final static String xmlNamespace = "http://www.w3.org/XML/1998/namespace";
100 //final static String xmlnsURI = "http://www.w3.org/2000/xmlns/";
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);
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.
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
;
185 // DOM calls never go down this path
186 if (nodeType
!= DOCUMENT_NODE
&& nodeType
!= DOCUMENT_TYPE_NODE
)
188 throw new IllegalArgumentException ("no owner!");
192 this.listeners
= new HashSet();
198 * Returns null; Element subclasses must override this method.
200 public NamedNodeMap
getAttributes()
207 * Returns true iff this is an element node with attributes.
209 public boolean hasAttributes()
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()
229 * Returns the first child of this node, or null if there are none.
231 public Node
getFirstChild()
238 * Returns the last child of this node, or null if there are none.
240 public Node
getLastChild()
247 * Returns true if this node has children.
249 public boolean hasChildNodes()
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()
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()
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
)
286 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
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
,
301 for (DomNode ctx
= this; ctx
!= null; ctx
= ctx
.parent
)
305 throw new DomDOMException(DOMException
.HIERARCHY_REQUEST_ERR
,
306 "can't make ancestor into a child",
311 DomDocument owner
= (nodeType
== DOCUMENT_NODE
) ?
(DomDocument
) this :
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
,
326 // enforce various structural constraints
330 switch (childNodeType
)
333 case PROCESSING_INSTRUCTION_NODE
:
335 case DOCUMENT_TYPE_NODE
:
341 switch (childNodeType
)
344 case ENTITY_REFERENCE_NODE
:
349 case DOCUMENT_FRAGMENT_NODE
:
350 case ENTITY_REFERENCE_NODE
:
353 switch (childNodeType
)
358 case PROCESSING_INSTRUCTION_NODE
:
359 case CDATA_SECTION_NODE
:
360 case ENTITY_REFERENCE_NODE
:
364 case DOCUMENT_TYPE_NODE
:
367 switch (childNodeType
)
370 case PROCESSING_INSTRUCTION_NODE
:
375 if (owner
.checkingWellformedness
)
377 throw new DomDOMException(DOMException
.HIERARCHY_REQUEST_ERR
,
379 nodeTypeToString(childNodeType
) +
380 " to node of type " +
381 nodeTypeToString(nodeType
),
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
392 private void insertionEvent(DomEvent
.DomMutationEvent event
,
395 if (owner
== null || owner
.building
)
399 boolean doFree
= false;
403 event
= getMutationEvent();
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.
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
,
434 if (owner
== null || owner
.building
)
438 boolean doFree
= false;
442 event
= getMutationEvent();
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.
462 event
.relatedNode
= null;
463 event
.currentNode
= null;
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
)
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
)
506 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
508 ctx
.setDepth(depth
+ 1);
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
)
541 for (DomNode ctx
= child
.first
; ctx
!= null; )
543 DomNode ctxNext
= ctx
.next
;
551 if (child
.parent
!= null)
553 child
.parent
.removeChild(child
);
556 child
.index
= length
++;
557 child
.setDepth(depth
+ 1);
562 child
.previous
= null;
567 child
.previous
= last
;
573 insertionEvent(null, child
);
579 catch (ClassCastException e
)
581 throw new DomDOMException(DOMException
.WRONG_DOCUMENT_ERR
,
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
)
621 for (DomNode ctx
= child
.first
; ctx
!= null; )
623 DomNode ctxNext
= ctx
.next
;
624 insertBefore(ctx
, ref
);
631 if (ref
== null || ref
.parent
!= this)
633 throw new DomDOMException(DOMException
.NOT_FOUND_ERR
,
638 throw new DomDOMException(DOMException
.HIERARCHY_REQUEST_ERR
,
639 "can't insert node before itself",
643 if (child
.parent
!= null)
645 child
.parent
.removeChild(child
);
649 child
.setDepth(depth
+ 1);
651 if (ref
.previous
!= null)
653 ref
.previous
.next
= child
;
655 child
.previous
= ref
.previous
;
656 ref
.previous
= child
;
662 for (DomNode ctx
= child
; ctx
!= null; ctx
= ctx
.next
)
669 insertionEvent(null, child
);
675 catch (ClassCastException e
)
677 throw new DomDOMException(DOMException
.WRONG_DOCUMENT_ERR
,
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
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
)
724 if (ref
== null || ref
.parent
!= this)
726 throw new DomDOMException(DOMException
.NOT_FOUND_ERR
,
732 removalEvent(event
, ref
);
735 length
+= child
.length
;
737 if (child
.length
== 0)
740 if (ref
.previous
!= null)
742 ref
.previous
.next
= ref
.next
;
744 if (ref
.next
!= null)
746 ref
.next
.previous
= ref
.previous
;
760 for (DomNode ctx
= child
.first
; ctx
!= null; ctx
= ctx
.next
)
765 ctx
.setDepth(ref
.depth
);
766 if (ctx
== child
.first
)
768 ctx
.previous
= ref
.previous
;
770 if (ctx
== child
.last
)
788 if (ref
== null || ref
.parent
!= this)
790 throw new DomDOMException(DOMException
.NOT_FOUND_ERR
,
796 removalEvent(event
, ref
);
799 if (child
.parent
!= null)
801 child
.parent
.removeChild(child
);
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
;
827 insertionEvent(event
, child
);
842 catch (ClassCastException e
)
844 throw new DomDOMException(DOMException
.WRONG_DOCUMENT_ERR
,
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
,
870 if (readonly
&& !owner
.building
)
872 throw new DomDOMException(DOMException
.NO_MODIFICATION_ALLOWED_ERR
,
876 for (DomNode child
= first
; child
!= null; child
= child
.next
)
882 removalEvent(null, child
);
886 if (ref
.previous
!= null)
888 ref
.previous
.next
= ref
.next
;
890 if (ref
.next
!= null)
892 ref
.next
.previous
= ref
.previous
;
904 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
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
,
928 * <b>DOM L1 (NodeList)</b>
929 * Returns the item with the specified index in this NodeList,
932 public Node
item(int index
)
934 DomNode child
= first
;
936 while (child
!= null && count
< index
)
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()
957 * Minimize extra space consumed by this node to hold children and event
960 public void trimToSize()
966 * Returns the previous sibling, if one is known.
968 public Node
getNextSibling()
975 * Returns the previous sibling, if one is known.
977 public Node
getPreviousSibling()
984 * Returns the parent node, if one is known.
986 public Node
getParentNode()
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;
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()
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
)
1042 * Returns null; this must be overridden for nodes types with
1043 * a defined value, along with the setNodeValue method.
1045 public String
getNodeValue()
1050 /** This forces GCJ compatibility.
1051 * Without this method GCJ is unable to compile to byte code.
1053 public final short getNodeType()
1058 /** This forces GCJ compatibility.
1059 * Without this method GCJ seems unable to natively compile GNUJAXP.
1061 public abstract String
getNodeName();
1065 * Does nothing; this must be overridden (along with the
1066 * getPrefix method) for element and attribute nodes.
1068 public void setPrefix(String prefix
)
1074 * Returns null; this must be overridden for element and
1077 public String
getPrefix()
1084 * Returns null; this must be overridden for element and
1087 public String
getNamespaceURI()
1094 * Returns the node name; this must be overridden for element and
1097 public String
getLocalName()
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();
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
);
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();
1169 node
.previous
= null;
1172 node
.readonly
= false;
1173 node
.listeners
= new HashSet();
1174 node
.nListeners
= 0;
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.
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
);
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
1220 private LiveNodeList liveList
;
1222 ShadowList(String ns
, String local
)
1224 liveList
= new LiveNodeList(ns
, local
);
1227 public void finalize()
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
)
1260 matchAnyURI
= "*".equals(uri
);
1261 matchAnyName
= "*".equals(name
);
1263 DomNode
.this.addEventListener("DOMNodeInserted", this, true);
1264 DomNode
.this.addEventListener("DOMNodeRemoved", this, true);
1269 if (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)
1284 // use namespace-aware matching ...
1285 if (elementURI
!= null)
1288 || elementURI
.equals(element
.getNamespaceURI())))
1293 || elementName
.equals(element
.getLocalName())))
1298 // ... or qName-based kind.
1303 || elementName
.equals(element
.getNodeName())))
1308 return FILTER_ACCEPT
;
1311 private DomIterator
createIterator()
1313 return new DomIterator(DomNode
.this,
1314 NodeFilter
.SHOW_ELEMENT
,
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
)
1335 if (current
!= null)
1340 public Node
item(int index
)
1342 if (current
== null)
1344 current
= createIterator();
1348 // last node or before? go backwards
1349 if (index
<= lastIndex
) {
1350 while (index
!= lastIndex
) {
1351 current
.previousNode ();
1354 Node ret
= current
.previousNode ();
1360 // somewhere after last node
1361 while (++lastIndex
!= index
)
1362 current
.nextNode ();
1364 Node ret
= current
.nextNode ();
1370 public int getLength()
1373 NodeIterator iter
= createIterator();
1375 while (iter
.nextNode() != null)
1386 // EventTarget support
1388 static final class ListenerRecord
1392 EventListener listener
;
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);
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
,
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
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.
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;
1519 throw new DomEventException();
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,
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.
1541 boolean haveAncestorRegistrations
= false;
1542 ListenerRecord
[] notificationSet
;
1545 synchronized (lockNode
)
1547 if (!dispatchDataLock
)
1549 haveDispatchDataLock
= dispatchDataLock
= true;
1550 notificationSet
= DomNode
.notificationSet
;
1551 ancestors
= DomNode
.ancestors
;
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.
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)
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
;
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
)
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
;
1627 && index
< ancestorMax
1628 && (current
= ancestors
[index
]) != null;
1631 if (current
.nListeners
!= 0)
1633 notifyNode(e
, current
, false, notificationSet
);
1639 // Caller chooses whether to perform the default
1640 // action based on return from this method.
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
,
1667 ListenerRecord
[] notificationSet
)
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
)
1683 if (!e
.type
.equals (rec
.type
))
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
;
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
);
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
,
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
;
1754 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
1756 boolean saved2
= ctx
.readonly
;
1757 ctx
.readonly
= false;
1758 switch (ctx
.nodeType
)
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
);
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;
1780 attr
.readonly
= saved3
;
1784 case DOCUMENT_FRAGMENT_NODE
:
1785 case ATTRIBUTE_NODE
:
1786 case ENTITY_REFERENCE_NODE
:
1790 ctx
.readonly
= saved2
;
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
)
1813 // node types must match
1814 if (nodeType
!= other
.getNodeType())
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()))
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.
1844 // otherwise they're unequal: one scoped, one not.
1848 // DOM Level 3 methods
1850 public String
getBaseURI()
1852 return (parent
!= null) ? parent
.getBaseURI() : null;
1855 public short compareDocumentPosition(Node other
)
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
)
1869 DomNode n2
= (DomNode
) other
;
1870 if (n1
.owner
!= n2
.owner
)
1874 int d1
= n1
.depth
, d2
= n2
.depth
;
1875 int delta
= d1
- d2
;
1886 int c
= compareTo2(n1
, n2
);
1887 return (c
!= 0) ? c
: delta
;
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)
1901 int c
= compareTo2(n1
.parent
, n2
.parent
);
1902 return (c
!= 0) ? c
: n1
.index
- n2
.index
;
1905 public final String
getTextContent()
1908 return getTextContent(true);
1911 final String
getTextContent(boolean topLevel
)
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();
1931 case CDATA_SECTION_NODE
:
1932 if (((Text
) this).isElementContentWhitespace())
1936 return getNodeValue();
1937 case ATTRIBUTE_NODE
:
1938 return getNodeValue();
1940 case PROCESSING_INSTRUCTION_NODE
:
1941 return topLevel ?
getNodeValue() : "";
1947 public void setTextContent(String textContent
)
1953 case ATTRIBUTE_NODE
:
1955 case ENTITY_REFERENCE_NODE
:
1956 case DOCUMENT_FRAGMENT_NODE
:
1957 for (DomNode ctx
= first
; ctx
!= null; )
1959 DomNode n
= ctx
.next
;
1963 if (textContent
!= null)
1965 Text text
= owner
.createTextNode(textContent
);
1970 case CDATA_SECTION_NODE
:
1972 case PROCESSING_INSTRUCTION_NODE
:
1973 setNodeValue(textContent
);
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
)
2007 if (nodeType
!= arg
.getNodeType())
2012 case ATTRIBUTE_NODE
:
2013 if (!equal(getLocalName(), arg
.getLocalName()) ||
2014 !equal(getNamespaceURI(), arg
.getNamespaceURI()))
2017 case PROCESSING_INSTRUCTION_NODE
:
2018 if (!equal(getNodeName(), arg
.getNodeName()) ||
2019 !equal(getNodeValue(), arg
.getNodeValue()))
2024 case CDATA_SECTION_NODE
:
2025 if (!equal(getNodeValue(), arg
.getNodeValue()))
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
)
2040 while (argCtx
!= null && ctx
.getNodeType() == TEXT_NODE
)
2041 argCtx
= argCtx
.getNextSibling();
2042 if (ctx
== null && argCtx
!= null)
2044 else if (argCtx
== null && ctx
!= null)
2047 if (!ctx
.isEqualNode(argCtx
))
2049 argCtx
= argCtx
.getNextSibling();
2051 if (ctx
!= null || argCtx
!= null)
2054 // TODO DocumentType
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
))
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)
2098 return userData
.get(key
);
2101 public String
toString()
2103 String nodeName
= getNodeName();
2104 String nodeValue
= getNodeValue();
2105 StringBuffer buf
= new StringBuffer(getClass().getName());
2107 if (nodeName
!= null)
2109 buf
.append(nodeName
);
2111 if (nodeValue
!= null)
2113 if (nodeName
!= null)
2118 buf
.append(encode(nodeValue
));
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
);
2136 buf
= new StringBuffer(value
.substring(0, i
));
2144 buf
= new StringBuffer(value
.substring(0, i
));
2148 else if (buf
!= null)
2153 return (buf
!= null) ? buf
.toString() : value
;
2156 String
nodeTypeToString(short nodeType
)
2161 return "ELEMENT_NODE";
2162 case ATTRIBUTE_NODE
:
2163 return "ATTRIBUTE_NODE";
2166 case CDATA_SECTION_NODE
:
2167 return "CDATA_SECTION_NODE";
2169 return "DOCUMENT_NODE";
2170 case DOCUMENT_TYPE_NODE
:
2171 return "DOCUMENT_TYPE_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";
2179 return "ENTITY_NODE";
2180 case ENTITY_REFERENCE_NODE
:
2181 return "ENTITY_REFERENCE_NODE";
2183 return "NOTATION_NODE";
2189 public void list(java
.io
.PrintStream out
, int indent
)
2191 for (int i
= 0; i
< indent
; i
++)
2193 out
.println(toString());
2194 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
2195 ctx
.list(out
, indent
+ 1);