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 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
1118 DomNode newChild
= (DomNode
) ctx
.cloneNode(deep
);
1119 newChild
.setOwner(doc
);
1120 node
.appendChild(newChild
);
1124 if (nodeType
== ENTITY_REFERENCE_NODE
)
1126 node
.makeReadonly();
1128 notifyUserDataHandlers(UserDataHandler
.NODE_CLONED
, this, node
);
1132 void notifyUserDataHandlers(short op
, Node src
, Node dst
)
1134 if (userDataHandlers
!= null)
1136 for (Iterator i
= userDataHandlers
.entrySet().iterator(); i
.hasNext(); )
1138 Map
.Entry entry
= (Map
.Entry
) i
.next();
1139 String key
= (String
) entry
.getKey();
1140 UserDataHandler handler
= (UserDataHandler
) entry
.getValue();
1141 Object data
= userData
.get(key
);
1142 handler
.handle(op
, key
, data
, src
, dst
);
1148 * Clones this node; roughly equivalent to cloneNode(false).
1149 * Element subclasses must provide a new implementation which
1150 * invokes this method to handle the basics, and then arranges
1151 * to clone any element attributes directly. Attribute subclasses
1152 * must make similar arrangements, ensuring that existing ties to
1153 * elements are broken by cloning.
1155 public Object
clone()
1159 DomNode node
= (DomNode
) super.clone();
1167 node
.previous
= null;
1170 node
.readonly
= false;
1171 node
.listeners
= new HashSet();
1172 node
.nListeners
= 0;
1176 catch (CloneNotSupportedException x
)
1178 throw new Error("clone didn't work");
1182 // the elements-by-tagname stuff is needed for both
1183 // elements and documents ... this is in lieu of a
1184 // common base class between Node and NodeNS.
1188 * Creates a NodeList giving array-style access to elements with
1189 * the specified name. Access is fastest if indices change by
1190 * small values, and the DOM is not modified.
1192 public NodeList
getElementsByTagName(String tag
)
1194 return new ShadowList(null, tag
);
1199 * Creates a NodeList giving array-style access to elements with
1200 * the specified namespace and local name. Access is fastest if
1201 * indices change by small values, and the DOM is not modified.
1203 public NodeList
getElementsByTagNameNS(String namespace
, String local
)
1205 return new ShadowList(namespace
, local
);
1210 // This shadow class is GC-able even when the live list it shadows
1211 // can't be, because of event registration hookups. Its finalizer
1212 // makes that live list become GC-able.
1214 final class ShadowList
1218 private LiveNodeList liveList
;
1220 ShadowList(String ns
, String local
)
1222 liveList
= new LiveNodeList(ns
, local
);
1225 public void finalize()
1231 public Node
item(int index
)
1233 return liveList
.item(index
);
1236 public int getLength()
1238 return liveList
.getLength();
1242 final class LiveNodeList
1243 implements NodeList
, EventListener
, NodeFilter
1246 private final boolean matchAnyURI
;
1247 private final boolean matchAnyName
;
1248 private final String elementURI
;
1249 private final String elementName
;
1251 private DomIterator current
;
1252 private int lastIndex
;
1254 LiveNodeList(String uri
, String name
)
1258 matchAnyURI
= "*".equals(uri
);
1259 matchAnyName
= "*".equals(name
);
1261 DomNode
.this.addEventListener("DOMNodeInserted", this, true);
1262 DomNode
.this.addEventListener("DOMNodeRemoved", this, true);
1267 if (current
!= null)
1271 DomNode
.this.removeEventListener("DOMNodeInserted", this, true);
1272 DomNode
.this.removeEventListener("DOMNodeRemoved", this, true);
1275 public short acceptNode(Node element
)
1277 if (element
== DomNode
.this)
1282 // use namespace-aware matching ...
1283 if (elementURI
!= null)
1286 || elementURI
.equals(element
.getNamespaceURI())))
1291 || elementName
.equals(element
.getLocalName())))
1296 // ... or qName-based kind.
1301 || elementName
.equals(element
.getNodeName())))
1306 return FILTER_ACCEPT
;
1309 private DomIterator
createIterator()
1311 return new DomIterator(DomNode
.this,
1312 NodeFilter
.SHOW_ELEMENT
,
1314 true /* expand entity refs */
1318 public void handleEvent(Event e
)
1320 MutationEvent mutation
= (MutationEvent
) e
;
1321 Node related
= mutation
.getRelatedNode();
1323 // XXX if it's got children ... check all kids too, they
1324 // will invalidate our saved index
1326 if (related
.getNodeType() != Node
.ELEMENT_NODE
||
1327 related
.getNodeName() != elementName
||
1328 related
.getNamespaceURI() != elementURI
)
1333 if (current
!= null)
1338 public Node
item(int index
)
1340 if (current
== null)
1342 current
= createIterator();
1346 // last node or before? go backwards
1347 if (index
<= lastIndex
) {
1348 while (index
!= lastIndex
) {
1349 current
.previousNode ();
1352 Node ret
= current
.previousNode ();
1358 // somewhere after last node
1359 while (++lastIndex
!= index
)
1360 current
.nextNode ();
1362 Node ret
= current
.nextNode ();
1368 public int getLength()
1371 NodeIterator iter
= createIterator();
1373 while (iter
.nextNode() != null)
1384 // EventTarget support
1386 static final class ListenerRecord
1390 EventListener listener
;
1393 // XXX use JDK 1.2 java.lang.ref.WeakReference to listener,
1394 // and we can both get rid of "shadow" classes and remove
1395 // the need for applications to apply similar trix ... but
1396 // JDK 1.2 support isn't generally available yet
1398 ListenerRecord(String type
, EventListener listener
, boolean useCapture
)
1400 this.type
= type
.intern();
1401 this.listener
= listener
;
1402 this.useCapture
= useCapture
;
1405 public boolean equals(Object o
)
1407 ListenerRecord rec
= (ListenerRecord
)o
;
1408 return listener
== rec
.listener
1409 && useCapture
== rec
.useCapture
1410 && type
== rec
.type
;
1413 public int hashCode()
1415 return listener
.hashCode() ^ type
.hashCode();
1420 * <b>DOM L2 (Events)</b>
1421 * Returns an instance of the specified type of event object.
1422 * Understands about DOM Mutation, HTML, and UI events.
1424 * <p>If the name of the event type begins with "USER-", then an object
1425 * implementing the "Event" class will be returned; this provides a
1426 * limited facility for application-defined events to use the DOM event
1427 * infrastructure. Alternatively, use one of the standard DOM event
1428 * classes and initialize it using use such a "USER-" event type name;
1429 * or defin, instantiate, and initialize an application-specific subclass
1430 * of DomEvent and pass that to dispatchEvent().
1432 * @param eventType Identifies the particular DOM feature module
1433 * defining the type of event, such as "MutationEvents".
1434 * <em>The event "name" is a different kind of "type".</em>
1436 public Event
createEvent(String eventType
)
1438 eventType
= eventType
.toLowerCase();
1440 if ("mutationevents".equals(eventType
))
1442 return new DomEvent
.DomMutationEvent(null);
1445 if ("htmlevents".equals(eventType
)
1446 || "events".equals(eventType
)
1447 || "user-events".equals(eventType
))
1449 return new DomEvent(null);
1452 if ("uievents".equals(eventType
))
1454 return new DomEvent
.DomUIEvent(null);
1459 throw new DomDOMException(DOMException
.NOT_SUPPORTED_ERR
,
1460 eventType
, null, 0);
1464 * <b>DOM L2 (Events)</b>
1465 * Registers an event listener's interest in a class of events.
1467 public final void addEventListener(String type
,
1468 EventListener listener
,
1472 ListenerRecord record
;
1474 record
= new ListenerRecord(type
, listener
, useCapture
);
1475 listeners
.add(record
);
1476 nListeners
= listeners
.size();
1479 // XXX this exception should be discarded from DOM
1481 // this class can be instantiated, unlike the one in the spec
1482 static final class DomEventException
1483 extends EventException
1488 super(UNSPECIFIED_EVENT_TYPE_ERR
, "unspecified event type");
1494 * <b>DOM L2 (Events)</b>
1495 * Delivers an event to all relevant listeners, returning true if the
1496 * caller should perform their default action. Note that the event
1497 * must have been provided by the createEvent() method on this
1498 * class, else it can't be dispatched.
1502 * @exception NullPointerException When a null event is passed.
1503 * @exception ClassCastException When the event wasn't provided by
1504 * the createEvent method, or otherwise isn't a DomEvent.
1505 * @exception EventException If the event type wasn't specified
1507 public final boolean dispatchEvent(Event event
)
1508 throws EventException
1510 DomEvent e
= (DomEvent
) event
;
1511 DomNode
[] ancestors
= null;
1512 int ancestorMax
= 0;
1513 boolean haveDispatchDataLock
= false;
1517 throw new DomEventException();
1524 // Typical case: one nonrecursive dispatchEvent call at a time
1525 // for this class. If that's our case, we can avoid allocating
1526 // garbage, which is overall a big win. Even with advanced GCs
1527 // that deal well with short-lived garbage, and wayfast allocators,
1530 // Remember -- EVERY mutation goes though here at least once.
1532 // When populating a DOM tree, trying to send mutation events is
1533 // the primary cost; this dominates the critical path.
1539 boolean haveAncestorRegistrations
= false;
1540 ListenerRecord
[] notificationSet
;
1543 synchronized (lockNode
)
1545 if (!dispatchDataLock
)
1547 haveDispatchDataLock
= dispatchDataLock
= true;
1548 notificationSet
= DomNode
.notificationSet
;
1549 ancestors
= DomNode
.ancestors
;
1553 notificationSet
= new ListenerRecord
[NOTIFICATIONS_INIT
];
1554 ancestors
= new DomNode
[ANCESTORS_INIT
];
1556 ancestorLen
= ancestors
.length
;
1559 // XXX autogrow ancestors ... based on statistics
1561 // Climb to the top of this subtree and handle capture, letting
1562 // each node (from the top down) capture until one stops it or
1563 // until we get to this one.
1565 for (index
= 0, current
= parent
;
1566 current
!= null && index
< ancestorLen
;
1567 index
++, current
= current
.parent
)
1569 if (current
.nListeners
!= 0)
1571 haveAncestorRegistrations
= true;
1573 ancestors
[index
] = current
;
1575 if (current
!= null)
1577 throw new RuntimeException("dispatchEvent capture stack size");
1580 ancestorMax
= index
;
1583 if (haveAncestorRegistrations
)
1585 e
.eventPhase
= Event
.CAPTURING_PHASE
;
1586 while (!e
.stop
&& index
-- > 0)
1588 current
= ancestors
[index
];
1589 if (current
.nListeners
!= 0)
1591 notifyNode(e
, current
, true, notificationSet
);
1596 // Always deliver events to the target node (this)
1597 // unless stopPropagation was called. If we saw
1598 // no registrations yet (typical!), we never will.
1599 if (!e
.stop
&& nListeners
!= 0)
1601 e
.eventPhase
= Event
.AT_TARGET
;
1602 notifyNode (e
, this, false, notificationSet
);
1604 else if (!haveAncestorRegistrations
)
1609 // If the event bubbles and propagation wasn't halted,
1610 // walk back up the ancestor list. Stop bubbling when
1611 // any bubbled event handler stops it.
1613 if (!e
.stop
&& e
.bubbles
)
1615 e
.eventPhase
= Event
.BUBBLING_PHASE
;
1618 && index
< ancestorMax
1619 && (current
= ancestors
[index
]) != null;
1622 if (current
.nListeners
!= 0)
1624 notifyNode(e
, current
, false, notificationSet
);
1630 // Caller chooses whether to perform the default
1631 // action based on return from this method.
1637 if (haveDispatchDataLock
)
1639 // synchronize to force write ordering
1640 synchronized (lockNode
)
1642 // null out refs to ensure they'll be GC'd
1643 for (int i
= 0; i
< ancestorMax
; i
++)
1645 ancestors
[i
] = null;
1647 // notificationSet handled by notifyNode
1649 dispatchDataLock
= false;
1655 private void notifyNode(DomEvent e
,
1658 ListenerRecord
[] notificationSet
)
1663 iter
= current
.listeners
.iterator();
1665 // do any of this set of listeners get notified?
1666 while (iter
.hasNext())
1668 ListenerRecord rec
= (ListenerRecord
)iter
.next();
1670 if (rec
.useCapture
!= capture
)
1674 if (!e
.type
.equals (rec
.type
))
1678 if (count
>= notificationSet
.length
)
1680 // very simple growth algorithm
1681 int len
= Math
.max(notificationSet
.length
, 1);
1682 ListenerRecord
[] tmp
= new ListenerRecord
[len
* 2];
1683 System
.arraycopy(notificationSet
, 0, tmp
, 0,
1684 notificationSet
.length
);
1685 notificationSet
= tmp
;
1687 notificationSet
[count
++] = rec
;
1691 // Notify just those listeners
1692 e
.currentNode
= current
;
1693 for (int i
= 0; i
< count
; i
++)
1697 iter
= current
.listeners
.iterator();
1698 // Late in the DOM CR process (3rd or 4th CR?) the
1699 // removeEventListener spec became asymmetric with respect
1700 // to addEventListener ... effect is now immediate.
1701 while (iter
.hasNext())
1703 ListenerRecord rec
= (ListenerRecord
)iter
.next();
1705 if (rec
.equals(notificationSet
[i
]))
1707 notificationSet
[i
].listener
.handleEvent(e
);
1715 // ignore all exceptions
1717 notificationSet
[i
] = null; // free for GC
1722 * <b>DOM L2 (Events)</b>
1723 * Unregisters an event listener.
1725 public final void removeEventListener(String type
,
1726 EventListener listener
,
1729 listeners
.remove(new ListenerRecord(type
, listener
, useCapture
));
1730 nListeners
= listeners
.size();
1731 // no exceptions reported
1735 * <b>DOM L1 (relocated in DOM L2)</b>
1736 * In this node and all contained nodes (including attributes if
1737 * relevant) merge adjacent text nodes. This is done while ignoring
1738 * text which happens to use CDATA delimiters).
1740 public final void normalize()
1742 // Suspend readonly status
1743 boolean saved
= readonly
;
1745 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
1747 boolean saved2
= ctx
.readonly
;
1748 ctx
.readonly
= false;
1749 switch (ctx
.nodeType
)
1752 case CDATA_SECTION_NODE
:
1753 while (ctx
.next
!= null &&
1754 (ctx
.next
.nodeType
== TEXT_NODE
||
1755 ctx
.next
.nodeType
== CDATA_SECTION_NODE
))
1757 Text text
= (Text
) ctx
;
1758 text
.appendData(ctx
.next
.getNodeValue());
1759 removeChild(ctx
.next
);
1763 NamedNodeMap attrs
= ctx
.getAttributes();
1764 int len
= attrs
.getLength();
1765 for (int i
= 0; i
< len
; i
++)
1767 DomNode attr
= (DomNode
) attrs
.item(i
);
1768 boolean saved3
= attr
.readonly
;
1769 attr
.readonly
= false;
1771 attr
.readonly
= saved3
;
1775 case DOCUMENT_FRAGMENT_NODE
:
1776 case ATTRIBUTE_NODE
:
1777 case ENTITY_REFERENCE_NODE
:
1781 ctx
.readonly
= saved2
;
1787 * Returns true iff node types match, and either (a) both nodes have no
1788 * namespace and their getNodeName() values are the same, or (b) both
1789 * nodes have the same getNamespaceURI() and same getLocalName() values.
1791 * <p>Note that notion of a "Per-Element-Type" attribute name scope, as
1792 * found in a non-normative appendix of the XML Namespaces specification,
1793 * is not supported here. Your application must implement that notion,
1794 * typically by not bothering to check nameAndTypeEquals for attributes
1795 * without namespace URIs unless you already know their elements are
1796 * nameAndTypeEquals.
1798 public boolean nameAndTypeEquals(Node other
)
1804 // node types must match
1805 if (nodeType
!= other
.getNodeType())
1810 // if both have namespaces, do a "full" comparision
1811 // this is a "global" partition
1812 String ns1
= this.getNamespaceURI();
1813 String ns2
= other
.getNamespaceURI();
1815 if (ns1
!= null && ns2
!= null)
1817 return ns1
.equals(ns2
) &&
1818 equal(getLocalName(), other
.getLocalName());
1821 // if neither has a namespace, this is a "no-namespace" name.
1822 if (ns1
== null && ns2
== null)
1824 if (!getNodeName().equals(other
.getNodeName()))
1828 // can test the non-normative "per-element-type" scope here.
1829 // if this is an attribute node and both nodes have been bound
1830 // to elements (!!), then return the nameAndTypeEquals()
1831 // comparison of those elements.
1835 // otherwise they're unequal: one scoped, one not.
1839 // DOM Level 3 methods
1841 public String
getBaseURI()
1843 return (parent
!= null) ? parent
.getBaseURI() : null;
1846 public short compareDocumentPosition(Node other
)
1849 return (short) compareTo(other
);
1853 * DOM nodes have a natural ordering: document order.
1855 public final int compareTo(Object other
)
1857 if (other
instanceof DomNode
)
1860 DomNode n2
= (DomNode
) other
;
1861 if (n1
.owner
!= n2
.owner
)
1865 int d1
= n1
.depth
, d2
= n2
.depth
;
1866 int delta
= d1
- d2
;
1877 int c
= compareTo2(n1
, n2
);
1878 return (c
!= 0) ? c
: delta
;
1884 * Compare two nodes at the same depth.
1886 final int compareTo2(DomNode n1
, DomNode n2
)
1888 if (n1
== n2
|| n1
.depth
== 0 || n2
.depth
== 0)
1892 int c
= compareTo2(n1
.parent
, n2
.parent
);
1893 return (c
!= 0) ? c
: n1
.index
- n2
.index
;
1896 public final String
getTextContent()
1899 return getTextContent(true);
1902 final String
getTextContent(boolean topLevel
)
1909 case ENTITY_REFERENCE_NODE
:
1910 case DOCUMENT_FRAGMENT_NODE
:
1911 StringBuffer buffer
= new StringBuffer();
1912 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
1914 String textContent
= ctx
.getTextContent(false);
1915 if (textContent
!= null)
1917 buffer
.append(textContent
);
1920 return buffer
.toString();
1922 case CDATA_SECTION_NODE
:
1923 if (((Text
) this).isElementContentWhitespace())
1927 return getNodeValue();
1928 case ATTRIBUTE_NODE
:
1929 return getNodeValue();
1931 case PROCESSING_INSTRUCTION_NODE
:
1932 return topLevel ?
getNodeValue() : "";
1938 public void setTextContent(String textContent
)
1944 case ATTRIBUTE_NODE
:
1946 case ENTITY_REFERENCE_NODE
:
1947 case DOCUMENT_FRAGMENT_NODE
:
1948 for (DomNode ctx
= first
; ctx
!= null; )
1950 DomNode n
= ctx
.next
;
1954 if (textContent
!= null)
1956 Text text
= owner
.createTextNode(textContent
);
1961 case CDATA_SECTION_NODE
:
1963 case PROCESSING_INSTRUCTION_NODE
:
1964 setNodeValue(textContent
);
1969 public boolean isSameNode(Node other
)
1971 return this == other
;
1974 public String
lookupPrefix(String namespaceURI
)
1976 return (parent
== null || parent
== owner
) ?
null :
1977 parent
.lookupPrefix(namespaceURI
);
1980 public boolean isDefaultNamespace(String namespaceURI
)
1982 return (parent
== null || parent
== owner
) ?
false :
1983 parent
.isDefaultNamespace(namespaceURI
);
1986 public String
lookupNamespaceURI(String prefix
)
1988 return (parent
== null || parent
== owner
) ?
null :
1989 parent
.lookupNamespaceURI(prefix
);
1992 public boolean isEqualNode(Node arg
)
1998 if (nodeType
!= arg
.getNodeType())
2003 case ATTRIBUTE_NODE
:
2004 if (!equal(getLocalName(), arg
.getLocalName()) ||
2005 !equal(getNamespaceURI(), arg
.getNamespaceURI()))
2008 case PROCESSING_INSTRUCTION_NODE
:
2009 if (!equal(getNodeName(), arg
.getNodeName()) ||
2010 !equal(getNodeValue(), arg
.getNodeValue()))
2015 case CDATA_SECTION_NODE
:
2016 if (!equal(getNodeValue(), arg
.getNodeValue()))
2021 Node argCtx
= arg
.getFirstChild();
2022 getFirstChild(); // because of DomAttr lazy children
2023 DomNode ctx
= first
;
2024 for (; ctx
!= null && argCtx
!= null; ctx
= ctx
.next
)
2026 if (nodeType
== DOCUMENT_NODE
)
2028 // Ignore whitespace outside document element
2029 while (ctx
!= null && ctx
.nodeType
== TEXT_NODE
)
2031 while (argCtx
!= null && ctx
.getNodeType() == TEXT_NODE
)
2032 argCtx
= argCtx
.getNextSibling();
2033 if (ctx
== null && argCtx
!= null)
2035 else if (argCtx
== null && ctx
!= null)
2038 if (!ctx
.isEqualNode(argCtx
))
2040 argCtx
= argCtx
.getNextSibling();
2042 if (ctx
!= null || argCtx
!= null)
2045 // TODO DocumentType
2049 boolean equal(String arg1
, String arg2
)
2051 return ((arg1
== null && arg2
== null) ||
2052 (arg1
!= null && arg1
.equals(arg2
)));
2055 public Object
getFeature(String feature
, String version
)
2057 DOMImplementation impl
= (nodeType
== DOCUMENT_NODE
) ?
2058 ((Document
) this).getImplementation() : owner
.getImplementation();
2059 if (impl
.hasFeature(feature
, version
))
2066 public Object
setUserData(String key
, Object data
, UserDataHandler handler
)
2068 if (userData
== null)
2070 userData
= new HashMap();
2072 if (handler
!= null)
2074 if (userDataHandlers
== null)
2076 userDataHandlers
= new HashMap();
2078 userDataHandlers
.put(key
, handler
);
2080 return userData
.put(key
, data
);
2083 public Object
getUserData(String key
)
2085 if (userData
== null)
2089 return userData
.get(key
);
2092 public String
toString()
2094 String nodeName
= getNodeName();
2095 String nodeValue
= getNodeValue();
2096 StringBuffer buf
= new StringBuffer(getClass().getName());
2098 if (nodeName
!= null)
2100 buf
.append(nodeName
);
2102 if (nodeValue
!= null)
2104 if (nodeName
!= null)
2109 buf
.append(encode(nodeValue
));
2113 return buf
.toString();
2116 String
encode(String value
)
2118 StringBuffer buf
= null;
2119 int len
= value
.length();
2120 for (int i
= 0; i
< len
; i
++)
2122 char c
= value
.charAt(i
);
2127 buf
= new StringBuffer(value
.substring(0, i
));
2135 buf
= new StringBuffer(value
.substring(0, i
));
2139 else if (buf
!= null)
2144 return (buf
!= null) ? buf
.toString() : value
;
2147 String
nodeTypeToString(short nodeType
)
2152 return "ELEMENT_NODE";
2153 case ATTRIBUTE_NODE
:
2154 return "ATTRIBUTE_NODE";
2157 case CDATA_SECTION_NODE
:
2158 return "CDATA_SECTION_NODE";
2160 return "DOCUMENT_NODE";
2161 case DOCUMENT_TYPE_NODE
:
2162 return "DOCUMENT_TYPE_NODE";
2164 return "COMMENT_NODE";
2165 case PROCESSING_INSTRUCTION_NODE
:
2166 return "PROCESSING_INSTRUCTION_NODE";
2167 case DOCUMENT_FRAGMENT_NODE
:
2168 return "DOCUMENT_FRAGMENT_NODE";
2170 return "ENTITY_NODE";
2171 case ENTITY_REFERENCE_NODE
:
2172 return "ENTITY_REFERENCE_NODE";
2174 return "NOTATION_NODE";
2180 public void list(java
.io
.PrintStream out
, int indent
)
2182 for (int i
= 0; i
< indent
; i
++)
2184 out
.println(toString());
2185 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
2186 ctx
.list(out
, indent
+ 1);