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., 59 Temple Place, Suite 330, 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
.Iterator
;
44 import org
.w3c
.dom
.Document
;
45 import org
.w3c
.dom
.DOMException
;
46 import org
.w3c
.dom
.DOMImplementation
;
47 import org
.w3c
.dom
.NamedNodeMap
;
48 import org
.w3c
.dom
.Node
;
49 import org
.w3c
.dom
.NodeList
;
50 import org
.w3c
.dom
.Text
;
51 import org
.w3c
.dom
.UserDataHandler
;
52 import org
.w3c
.dom
.events
.DocumentEvent
;
53 import org
.w3c
.dom
.events
.Event
;
54 import org
.w3c
.dom
.events
.EventException
;
55 import org
.w3c
.dom
.events
.EventListener
;
56 import org
.w3c
.dom
.events
.EventTarget
;
57 import org
.w3c
.dom
.events
.MutationEvent
;
58 import org
.w3c
.dom
.traversal
.NodeFilter
;
59 import org
.w3c
.dom
.traversal
.NodeIterator
;
62 * <p> "Node", "EventTarget", and "DocumentEvent" implementation.
63 * This provides most of the core DOM functionality; only more
64 * specialized features are provided by subclasses. Those subclasses may
65 * have some particular constraints they must implement, by overriding
66 * methods defined here. Such constraints are noted here in the method
69 * <p> Note that you can create events with type names prefixed with "USER-",
70 * and pass them through this DOM. This lets you use the DOM event scheme
71 * for application specific purposes, although you must use a predefined event
72 * structure (such as MutationEvent) to pass data along with those events.
73 * Test for existence of this feature with the "USER-Events" DOM feature
76 * <p> Other kinds of events you can send include the "html" events,
77 * like "load", "unload", "abort", "error", and "blur"; and the mutation
78 * events. If this DOM has been compiled with mutation event support
79 * enabled, it will send mutation events when you change parts of the
80 * tree; otherwise you may create and send such events yourself, but
81 * they won't be generated by the DOM itself. </p>
83 * <p> Note that there is a namespace-aware name comparison method,
84 * <em>nameAndTypeEquals</em>, which compares the names (and types) of
85 * two nodes in conformance with the "Namespaces in XML" specification.
86 * While mostly intended for use with elements and attributes, this should
87 * also be helpful for ProcessingInstruction nodes and some others which
88 * do not have namespace URIs.
90 * @author David Brownell
91 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
93 public abstract class DomNode
94 implements Node
, NodeList
, EventTarget
, DocumentEvent
, Cloneable
, Comparable
98 //final static String xmlNamespace = "http://www.w3.org/XML/1998/namespace";
99 //final static String xmlnsURI = "http://www.w3.org/2000/xmlns/";
102 // NKIDS_* affects arrays of children (which grow)
103 // (currently) fixed size:
104 // ANCESTORS_* is for event capture/bubbling, # ancestors
105 // NOTIFICATIONS_* is for per-node event delivery, # events
106 private static final int NKIDS_DELTA
= 8;
107 private static final int ANCESTORS_INIT
= 20;
108 private static final int NOTIFICATIONS_INIT
= 10;
110 // tunable: enable mutation events or not? Enabling it costs about
111 // 10-15% in DOM construction time, last time it was measured.
113 // package private !!!
114 static final boolean reportMutations
= true;
116 // locking protocol changeable only within this class
117 private static final Object lockNode
= new Object();
119 // NON-FINAL class data
121 // Optimize event dispatch by not allocating memory each time
122 private static boolean dispatchDataLock
;
123 private static DomNode
[] ancestors
= new DomNode
[ANCESTORS_INIT
];
124 private static ListenerRecord
[] notificationSet
125 = new ListenerRecord
[NOTIFICATIONS_INIT
];
127 // Ditto for the (most common) event object itself!
128 private static boolean eventDataLock
;
129 private static DomEvent
.DomMutationEvent mutationEvent
130 = new DomEvent
.DomMutationEvent(null);
137 DomNode parent
; // parent node;
138 DomNode previous
; // previous sibling node
139 DomNode next
; // next sibling node
140 DomNode first
; // first child node
141 DomNode last
; // last child node
142 int index
; // index of this node in its parent's children
143 int depth
; // depth of the node in the document
144 int length
; // number of children
145 final short nodeType
;
147 // Bleech ... "package private" so a builder can populate entity refs.
148 // writable during construction. DOM spec is nasty.
151 // event registrations
152 private ListenerRecord
[] listeners
;
153 private int nListeners
;
155 // DOM Level 3 userData dictionary.
156 private HashMap userData
;
157 private HashMap userDataHandlers
;
160 // Some of the methods here are declared 'final' because
161 // knowledge about their implementation is built into this
162 // class -- for both integrity and performance.
166 * Reduces space utilization for this node.
168 public void compact()
170 if (listeners
!= null && listeners
.length
!= nListeners
)
178 ListenerRecord
[] l
= new ListenerRecord
[nListeners
];
179 System
.arraycopy(listeners
, 0, l
, 0, nListeners
);
186 * Constructs a node and associates it with its owner. Only
187 * Document and DocumentType nodes may be created with no owner,
188 * and DocumentType nodes get an owner as soon as they are
189 * associated with a document.
191 protected DomNode(short nodeType
, DomDocument owner
)
193 this.nodeType
= nodeType
;
197 // DOM calls never go down this path
198 if (nodeType
!= DOCUMENT_NODE
&& nodeType
!= DOCUMENT_TYPE_NODE
)
200 throw new IllegalArgumentException ("no owner!");
209 * Returns null; Element subclasses must override this method.
211 public NamedNodeMap
getAttributes()
218 * Returns true iff this is an element node with attributes.
220 public boolean hasAttributes()
227 * Returns a list, possibly empty, of the children of this node.
228 * In this implementation, to conserve memory, nodes are the same
229 * as their list of children. This can have ramifications for
230 * subclasses, which may need to provide their own getLength method
231 * for reasons unrelated to the NodeList method of the same name.
233 public NodeList
getChildNodes()
240 * Returns the first child of this node, or null if there are none.
242 public Node
getFirstChild()
249 * Returns the last child of this node, or null if there are none.
251 public Node
getLastChild()
258 * Returns true if this node has children.
260 public boolean hasChildNodes()
267 * Exposes the internal "readonly" flag. In DOM, children of
268 * entities and entity references are readonly, as are the
269 * objects associated with DocumentType objets.
271 public final boolean isReadonly()
277 * Sets the internal "readonly" flag so this subtree can't be changed.
278 * Subclasses need to override this method for any associated content
279 * that's not a child node, such as an element's attributes or the
280 * (few) declarations associated with a DocumentType.
282 public void makeReadonly()
285 for (DomNode child
= first
; child
!= null; child
= child
.next
)
287 child
.makeReadonly();
292 * Used to adopt a node to a new document.
294 void setOwner(DomDocument doc
)
297 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
303 // just checks the node for inclusion -- may be called many
304 // times (docfrag) before anything is allowed to change
305 private void checkMisc(DomNode child
)
307 if (readonly
&& !owner
.building
)
309 throw new DomDOMException(DOMException
.NO_MODIFICATION_ALLOWED_ERR
,
312 for (DomNode ctx
= this; ctx
!= null; ctx
= ctx
.parent
)
316 throw new DomDOMException(DOMException
.HIERARCHY_REQUEST_ERR
,
317 "can't make ancestor into a child",
322 DomDocument owner
= (nodeType
== DOCUMENT_NODE
) ?
(DomDocument
) this :
324 DomDocument childOwner
= child
.owner
;
325 short childNodeType
= child
.nodeType
;
327 if (childOwner
!= owner
)
329 // new in DOM L2, this case -- patch it up later, in reparent()
330 if (!(childNodeType
== DOCUMENT_TYPE_NODE
&& childOwner
== null))
332 throw new DomDOMException(DOMException
.WRONG_DOCUMENT_ERR
,
337 // enforce various structural constraints
341 switch (childNodeType
)
344 case PROCESSING_INSTRUCTION_NODE
:
346 case DOCUMENT_TYPE_NODE
:
352 switch (childNodeType
)
355 case ENTITY_REFERENCE_NODE
:
360 case DOCUMENT_FRAGMENT_NODE
:
361 case ENTITY_REFERENCE_NODE
:
364 switch (childNodeType
)
369 case PROCESSING_INSTRUCTION_NODE
:
370 case CDATA_SECTION_NODE
:
371 case ENTITY_REFERENCE_NODE
:
376 if (owner
.checkingWellformedness
)
378 throw new DomDOMException(DOMException
.HIERARCHY_REQUEST_ERR
,
380 nodeTypeToString(childNodeType
) +
381 " to node of type " +
382 nodeTypeToString(nodeType
),
387 // Here's hoping a good optimizer will detect the case when the
388 // next several methods are never called, and won't allocate
389 // object code space of any kind. (Case: not reporting any
390 // mutation events. We can also remove some static variables
393 private void insertionEvent(DomEvent
.DomMutationEvent event
,
396 if (owner
== null || owner
.building
)
400 boolean doFree
= false;
404 event
= getMutationEvent();
412 event
= new DomEvent
.DomMutationEvent(null);
414 event
.initMutationEvent("DOMNodeInserted",
415 true /* bubbles */, false /* nocancel */,
416 this /* related */, null, null, null, (short) 0);
417 target
.dispatchEvent(event
);
419 // XXX should really visit every descendant of 'target'
420 // and sent a DOMNodeInsertedIntoDocument event to it...
421 // bleech, there's no way to keep that acceptably fast.
426 event
.relatedNode
= null;
427 event
.currentNode
= null;
428 eventDataLock
= false;
429 } // else we created work for the GC
432 private void removalEvent(DomEvent
.DomMutationEvent event
,
435 if (owner
== null || owner
.building
)
439 boolean doFree
= false;
443 event
= getMutationEvent();
451 event
= new DomEvent
.DomMutationEvent(null);
453 event
.initMutationEvent("DOMNodeRemoved",
454 true /* bubbles */, false /* nocancel */,
455 this /* related */, null, null, null, (short) 0);
456 target
.dispatchEvent(event
);
458 // XXX should really visit every descendant of 'target'
459 // and sent a DOMNodeRemovedFromDocument event to it...
460 // bleech, there's no way to keep that acceptably fast.
463 event
.relatedNode
= null;
464 event
.currentNode
= null;
467 eventDataLock
= false;
469 // else we created more work for the GC
473 // Avoid creating lots of memory management work, by using a simple
474 // allocation strategy for the mutation event objects that get used
475 // at least once per tree modification. We can't use stack allocation,
476 // so we do the next simplest thing -- more or less, static allocation.
477 // Concurrent notifications should be rare, anyway.
479 // Returns the preallocated object, which needs to be carefully freed,
480 // or null to indicate the caller needs to allocate their own.
482 static private DomEvent
.DomMutationEvent
getMutationEvent()
484 synchronized (lockNode
)
490 eventDataLock
= true;
491 return mutationEvent
;
495 // NOTE: this is manually inlined in the insertion
496 // and removal event methods above; change in sync.
497 static private void freeMutationEvent()
499 // clear fields to enable GC
500 mutationEvent
.clear();
501 eventDataLock
= false;
504 void setDepth(int depth
)
507 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
509 ctx
.setDepth(depth
+ 1);
515 * Appends the specified node to this node's list of children.
516 * Document subclasses must override this to enforce the restrictions
517 * that there be only one element and document type child.
519 * <p> Causes a DOMNodeInserted mutation event to be reported.
520 * Will first cause a DOMNodeRemoved event to be reported if the
521 * parameter already has a parent. If the new child is a document
522 * fragment node, both events will be reported for each child of
523 * the fragment; the order in which children are removed and
524 * inserted is implementation-specific.
526 * <p> If this DOM has been compiled without mutation event support,
527 * these events will not be reported.
529 public Node
appendChild(Node newChild
)
533 DomNode child
= (DomNode
) newChild
;
535 if (child
.nodeType
== DOCUMENT_FRAGMENT_NODE
)
537 // Append all nodes in the fragment to this node
538 for (DomNode ctx
= child
.first
; ctx
!= null; ctx
= ctx
.next
)
542 for (DomNode ctx
= child
.first
; ctx
!= null; )
544 DomNode ctxNext
= ctx
.next
;
552 if (child
.parent
!= null)
554 child
.parent
.removeChild(child
);
557 child
.index
= length
++;
558 child
.setDepth(depth
+ 1);
563 child
.previous
= null;
568 child
.previous
= last
;
574 insertionEvent(null, child
);
580 catch (ClassCastException e
)
582 throw new DomDOMException(DOMException
.WRONG_DOCUMENT_ERR
,
589 * Inserts the specified node in this node's list of children.
590 * Document subclasses must override this to enforce the restrictions
591 * that there be only one element and document type child.
593 * <p> Causes a DOMNodeInserted mutation event to be reported. Will
594 * first cause a DOMNodeRemoved event to be reported if the newChild
595 * parameter already has a parent. If the new child is a document
596 * fragment node, both events will be reported for each child of
597 * the fragment; the order in which children are removed and inserted
598 * is implementation-specific.
600 * <p> If this DOM has been compiled without mutation event support,
601 * these events will not be reported.
603 public Node
insertBefore(Node newChild
, Node refChild
)
605 if (refChild
== null)
607 return appendChild(newChild
);
612 DomNode child
= (DomNode
) newChild
;
613 DomNode ref
= (DomNode
) refChild
;
615 if (child
.nodeType
== DOCUMENT_FRAGMENT_NODE
)
617 // Append all nodes in the fragment to this node
618 for (DomNode ctx
= child
.first
; ctx
!= null; ctx
= ctx
.next
)
622 for (DomNode ctx
= child
.first
; ctx
!= null; )
624 DomNode ctxNext
= ctx
.next
;
625 insertBefore(ctx
, ref
);
632 if (ref
== null || ref
.parent
!= this)
634 throw new DomDOMException(DOMException
.NOT_FOUND_ERR
,
639 throw new DomDOMException(DOMException
.HIERARCHY_REQUEST_ERR
,
640 "can't insert node before itself",
644 if (child
.parent
!= null)
646 child
.parent
.removeChild(child
);
650 child
.setDepth(depth
+ 1);
652 if (ref
.previous
!= null)
654 ref
.previous
.next
= child
;
656 child
.previous
= ref
.previous
;
657 ref
.previous
= child
;
663 for (DomNode ctx
= child
; ctx
!= null; ctx
= ctx
.next
)
670 insertionEvent(null, child
);
676 catch (ClassCastException e
)
678 throw new DomDOMException(DOMException
.WRONG_DOCUMENT_ERR
,
685 * Replaces the specified node in this node's list of children.
686 * Document subclasses must override this to test the restrictions
687 * that there be only one element and document type child.
689 * <p> Causes DOMNodeRemoved and DOMNodeInserted mutation event to be
690 * reported. Will cause another DOMNodeRemoved event to be reported if
691 * the newChild parameter already has a parent. These events may be
692 * delivered in any order, except that the event reporting removal
693 * from such an existing parent will always be delivered before the
694 * event reporting its re-insertion as a child of some other node.
695 * The order in which children are removed and inserted is implementation
698 * <p> If your application needs to depend on the in which those removal
699 * and insertion events are delivered, don't use this API. Instead,
700 * invoke the removeChild and insertBefore methods directly, to guarantee
701 * a specific delivery order. Similarly, don't use document fragments,
702 * Otherwise your application code may not work on a DOM which implements
703 * this method differently.
705 * <p> If this DOM has been compiled without mutation event support,
706 * these events will not be reported.
708 public Node
replaceChild(Node newChild
, Node refChild
)
712 DomNode child
= (DomNode
) newChild
;
713 DomNode ref
= (DomNode
) refChild
;
715 DomEvent
.DomMutationEvent event
= getMutationEvent();
716 boolean doFree
= (event
!= null);
718 if (child
.nodeType
== DOCUMENT_FRAGMENT_NODE
)
720 // Append all nodes in the fragment to this node
721 for (DomNode ctx
= child
.first
; ctx
!= null; ctx
= ctx
.next
)
725 if (ref
== null || ref
.parent
!= this)
727 throw new DomDOMException(DOMException
.NOT_FOUND_ERR
,
733 removalEvent(event
, ref
);
736 length
+= child
.length
;
738 if (child
.length
== 0)
741 if (ref
.previous
!= null)
743 ref
.previous
.next
= ref
.next
;
745 if (ref
.next
!= null)
747 ref
.next
.previous
= ref
.previous
;
761 for (DomNode ctx
= child
.first
; ctx
!= null; ctx
= ctx
.next
)
766 ctx
.setDepth(ref
.depth
);
767 if (ctx
== child
.first
)
769 ctx
.previous
= ref
.previous
;
771 if (ctx
== child
.last
)
789 if (ref
== null || ref
.parent
!= this)
791 throw new DomDOMException(DOMException
.NOT_FOUND_ERR
,
797 removalEvent(event
, ref
);
800 if (child
.parent
!= null)
802 child
.parent
.removeChild(child
);
805 child
.index
= ref
.index
;
806 child
.setDepth(ref
.depth
);
807 if (ref
.previous
!= null)
809 ref
.previous
.next
= child
;
811 child
.previous
= ref
.previous
;
812 if (ref
.next
!= null)
814 ref
.next
.previous
= child
;
816 child
.next
= ref
.next
;
828 insertionEvent(event
, child
);
843 catch (ClassCastException e
)
845 throw new DomDOMException(DOMException
.WRONG_DOCUMENT_ERR
,
852 * Removes the specified child from this node's list of children,
853 * or else reports an exception.
855 * <p> Causes a DOMNodeRemoved mutation event to be reported.
857 * <p> If this DOM has been compiled without mutation event support,
858 * these events will not be reported.
860 public Node
removeChild(Node refChild
)
864 DomNode ref
= (DomNode
) refChild
;
866 if (ref
== null || ref
.parent
!= this)
868 throw new DomDOMException(DOMException
.NOT_FOUND_ERR
,
871 if (readonly
&& !owner
.building
)
873 throw new DomDOMException(DOMException
.NO_MODIFICATION_ALLOWED_ERR
,
877 for (DomNode child
= first
; child
!= null; child
= child
.next
)
883 removalEvent(null, child
);
887 if (ref
.previous
!= null)
889 ref
.previous
.next
= ref
.next
;
891 if (ref
.next
!= null)
893 ref
.next
.previous
= ref
.previous
;
905 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
918 throw new DomDOMException(DOMException
.NOT_FOUND_ERR
,
919 "that's no child of mine", refChild
, 0);
921 catch (ClassCastException e
)
923 throw new DomDOMException(DOMException
.WRONG_DOCUMENT_ERR
,
929 * <b>DOM L1 (NodeList)</b>
930 * Returns the item with the specified index in this NodeList,
933 public Node
item(int index
)
935 DomNode child
= first
;
937 while (child
!= null && count
< index
)
946 * <b>DOM L1 (NodeList)</b>
947 * Returns the number of elements in this NodeList.
948 * (Note that many interfaces have a "Length" property, not just
949 * NodeList, and if a node subtype must implement one of those,
950 * it will also need to override getChildNodes.)
952 public int getLength()
958 * Minimize extra space consumed by this node to hold children and event
961 public void trimToSize()
963 if (listeners
!= null && listeners
.length
!= nListeners
)
965 ListenerRecord
[] newKids
= new ListenerRecord
[length
];
966 System
.arraycopy(listeners
, 0, newKids
, 0, nListeners
);
973 * Returns the previous sibling, if one is known.
975 public Node
getNextSibling()
982 * Returns the previous sibling, if one is known.
984 public Node
getPreviousSibling()
991 * Returns the parent node, if one is known.
993 public Node
getParentNode()
1000 * Consults the DOM implementation to determine if the requested
1001 * feature is supported. DocumentType subclasses must override
1002 * this method, and associate themselves directly with the
1003 * DOMImplementation node used. (This method relies on being able
1004 * to access the DOMImplementation from the owner document, but
1005 * DocumentType nodes can be created without an owner.)
1007 public boolean isSupported(String feature
, String version
)
1009 Document doc
= owner
;
1010 DOMImplementation impl
= null;
1012 if (doc
== null && nodeType
== DOCUMENT_NODE
)
1014 doc
= (Document
) this;
1019 // possible for DocumentType
1020 throw new IllegalStateException ("unbound ownerDocument");
1023 impl
= doc
.getImplementation();
1024 return impl
.hasFeature(feature
, version
);
1028 * <b>DOM L1 (modified in L2)</b>
1029 * Returns the owner document. This is only null for Document nodes,
1030 * and (new in L2) for DocumentType nodes which have not yet been
1031 * associated with the rest of their document.
1033 final public Document
getOwnerDocument()
1040 * Does nothing; this must be overridden (along with the
1041 * getNodeValue method) for nodes with a non-null defined value.
1043 public void setNodeValue(String value
)
1049 * Returns null; this must be overridden for nodes types with
1050 * a defined value, along with the setNodeValue method.
1052 public String
getNodeValue()
1057 /** This forces GCJ compatibility.
1058 * Without this method GCJ is unable to compile to byte code.
1060 public final short getNodeType()
1065 /** This forces GCJ compatibility.
1066 * Without this method GCJ seems unable to natively compile GNUJAXP.
1068 public abstract String
getNodeName();
1072 * Does nothing; this must be overridden (along with the
1073 * getPrefix method) for element and attribute nodes.
1075 public void setPrefix(String prefix
)
1081 * Returns null; this must be overridden for element and
1084 public String
getPrefix()
1091 * Returns null; this must be overridden for element and
1094 public String
getNamespaceURI()
1101 * Returns the node name; this must be overridden for element and
1104 public String
getLocalName()
1111 * Returns a clone of this node which optionally includes cloned
1112 * versions of child nodes. Clones are always mutable, except for
1113 * entity reference nodes.
1115 public Node
cloneNode(boolean deep
)
1117 DomNode node
= (DomNode
) clone();
1121 DomDocument doc
= (nodeType
== DOCUMENT_NODE
) ?
1122 (DomDocument
) node
: node
.owner
;
1123 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
1125 DomNode newChild
= (DomNode
) ctx
.cloneNode(deep
);
1126 newChild
.setOwner(doc
);
1127 node
.appendChild(newChild
);
1131 if (nodeType
== ENTITY_REFERENCE_NODE
)
1133 node
.makeReadonly();
1135 notifyUserDataHandlers(UserDataHandler
.NODE_CLONED
, this, node
);
1139 void notifyUserDataHandlers(short op
, Node src
, Node dst
)
1141 if (userDataHandlers
!= null)
1143 for (Iterator i
= userDataHandlers
.entrySet().iterator(); i
.hasNext(); )
1145 Map
.Entry entry
= (Map
.Entry
) i
.next();
1146 String key
= (String
) entry
.getKey();
1147 UserDataHandler handler
= (UserDataHandler
) entry
.getValue();
1148 Object data
= userData
.get(key
);
1149 handler
.handle(op
, key
, data
, src
, dst
);
1155 * Clones this node; roughly equivalent to cloneNode(false).
1156 * Element subclasses must provide a new implementation which
1157 * invokes this method to handle the basics, and then arranges
1158 * to clone any element attributes directly. Attribute subclasses
1159 * must make similar arrangements, ensuring that existing ties to
1160 * elements are broken by cloning.
1162 public Object
clone()
1166 DomNode node
= (DomNode
) super.clone();
1174 node
.previous
= null;
1177 node
.readonly
= false;
1178 node
.listeners
= null;
1179 node
.nListeners
= 0;
1183 catch (CloneNotSupportedException x
)
1185 throw new Error("clone didn't work");
1189 // the elements-by-tagname stuff is needed for both
1190 // elements and documents ... this is in lieu of a
1191 // common base class between Node and NodeNS.
1195 * Creates a NodeList giving array-style access to elements with
1196 * the specified name. Access is fastest if indices change by
1197 * small values, and the DOM is not modified.
1199 public NodeList
getElementsByTagName(String tag
)
1201 return new ShadowList(null, tag
);
1206 * Creates a NodeList giving array-style access to elements with
1207 * the specified namespace and local name. Access is fastest if
1208 * indices change by small values, and the DOM is not modified.
1210 public NodeList
getElementsByTagNameNS(String namespace
, String local
)
1212 return new ShadowList(namespace
, local
);
1217 // This shadow class is GC-able even when the live list it shadows
1218 // can't be, because of event registration hookups. Its finalizer
1219 // makes that live list become GC-able.
1221 final class ShadowList
1225 private LiveNodeList liveList
;
1227 ShadowList(String ns
, String local
)
1229 liveList
= new LiveNodeList(ns
, local
);
1232 public void finalize()
1238 public Node
item(int index
)
1240 return liveList
.item(index
);
1243 public int getLength()
1245 return liveList
.getLength();
1249 final class LiveNodeList
1250 implements NodeList
, EventListener
, NodeFilter
1253 private final boolean matchAnyURI
;
1254 private final boolean matchAnyName
;
1255 private final String elementURI
;
1256 private final String elementName
;
1258 private DomIterator current
;
1259 private int lastIndex
;
1261 LiveNodeList(String uri
, String name
)
1265 matchAnyURI
= "*".equals(uri
);
1266 matchAnyName
= "*".equals(name
);
1268 DomNode
.this.addEventListener("DOMNodeInserted", this, true);
1269 DomNode
.this.addEventListener("DOMNodeRemoved", this, true);
1277 DomNode
.this.removeEventListener("DOMNodeInserted", this, true);
1278 DomNode
.this.removeEventListener("DOMNodeRemoved", this, true);
1281 public short acceptNode(Node element
)
1283 if (element
== DomNode
.this)
1288 // use namespace-aware matching ...
1289 if (elementURI
!= null)
1292 || elementURI
.equals(element
.getNamespaceURI())))
1297 || elementName
.equals(element
.getLocalName())))
1302 // ... or qName-based kind.
1307 || elementName
.equals(element
.getNodeName())))
1312 return FILTER_ACCEPT
;
1315 private DomIterator
createIterator()
1317 return new DomIterator(DomNode
.this,
1318 NodeFilter
.SHOW_ELEMENT
,
1320 true /* expand entity refs */
1324 public void handleEvent(Event e
)
1326 MutationEvent mutation
= (MutationEvent
) e
;
1327 Node related
= mutation
.getRelatedNode();
1329 // XXX if it's got children ... check all kids too, they
1330 // will invalidate our saved index
1332 if (related
.getNodeType() != Node
.ELEMENT_NODE
||
1333 related
.getNodeName() != elementName
||
1334 related
.getNamespaceURI() != elementURI
)
1342 public Node
item(int index
)
1344 if (current
== null)
1346 current
= createIterator();
1350 // last node or before? go backwards
1351 if (index
<= lastIndex
) {
1352 while (index
!= lastIndex
) {
1353 current
.previousNode ();
1356 Node ret
= current
.previousNode ();
1361 // somewhere after last node
1362 while (++lastIndex
!= index
)
1363 current
.nextNode ();
1364 Node ret
= current
.nextNode ();
1369 public int getLength()
1372 NodeIterator iter
= createIterator();
1374 while (iter
.nextNode() != null)
1385 // EventTarget support
1387 static final class ListenerRecord
1391 EventListener listener
;
1394 // XXX use JDK 1.2 java.lang.ref.WeakReference to listener,
1395 // and we can both get rid of "shadow" classes and remove
1396 // the need for applications to apply similar trix ... but
1397 // JDK 1.2 support isn't generally available yet
1399 ListenerRecord(String type
, EventListener listener
, boolean useCapture
)
1401 this.type
= type
.intern();
1402 this.listener
= listener
;
1403 this.useCapture
= useCapture
;
1406 boolean equals(ListenerRecord rec
)
1408 return listener
== rec
.listener
1409 && useCapture
== rec
.useCapture
1410 && type
== rec
.type
;
1416 * <b>DOM L2 (Events)</b>
1417 * Returns an instance of the specified type of event object.
1418 * Understands about DOM Mutation, HTML, and UI events.
1420 * <p>If the name of the event type begins with "USER-", then an object
1421 * implementing the "Event" class will be returned; this provides a
1422 * limited facility for application-defined events to use the DOM event
1423 * infrastructure. Alternatively, use one of the standard DOM event
1424 * classes and initialize it using use such a "USER-" event type name;
1425 * or defin, instantiate, and initialize an application-specific subclass
1426 * of DomEvent and pass that to dispatchEvent().
1428 * @param eventType Identifies the particular DOM feature module
1429 * defining the type of event, such as "MutationEvents".
1430 * <em>The event "name" is a different kind of "type".</em>
1432 public Event
createEvent(String eventType
)
1434 eventType
= eventType
.toLowerCase();
1436 if ("mutationevents".equals(eventType
))
1438 return new DomEvent
.DomMutationEvent(null);
1441 if ("htmlevents".equals(eventType
)
1442 || "events".equals(eventType
)
1443 || "user-events".equals(eventType
))
1445 return new DomEvent(null);
1448 if ("uievents".equals(eventType
))
1450 return new DomEvent
.DomUIEvent(null);
1455 throw new DomDOMException(DOMException
.NOT_SUPPORTED_ERR
,
1456 eventType
, null, 0);
1460 * <b>DOM L2 (Events)</b>
1461 * Registers an event listener's interest in a class of events.
1463 public final void addEventListener(String type
,
1464 EventListener listener
,
1467 if (listeners
== null)
1469 listeners
= new ListenerRecord
[1];
1471 else if (nListeners
== listeners
.length
)
1473 ListenerRecord
[] newListeners
=
1474 new ListenerRecord
[listeners
.length
+ NKIDS_DELTA
];
1475 System
.arraycopy(listeners
, 0, newListeners
, 0, nListeners
);
1476 listeners
= newListeners
;
1480 ListenerRecord record
;
1482 record
= new ListenerRecord(type
, listener
, useCapture
);
1483 for (int i
= 0; i
< nListeners
; i
++)
1485 if (record
.equals(listeners
[i
]))
1490 listeners
[nListeners
++] = record
;
1493 // XXX this exception should be discarded from DOM
1495 // this class can be instantiated, unlike the one in the spec
1496 static final class DomEventException
1497 extends EventException
1502 super(UNSPECIFIED_EVENT_TYPE_ERR
, "unspecified event type");
1508 * <b>DOM L2 (Events)</b>
1509 * Delivers an event to all relevant listeners, returning true if the
1510 * caller should perform their default action. Note that the event
1511 * must have been provided by the createEvent() method on this
1512 * class, else it can't be dispatched.
1516 * @exception NullPointerException When a null event is passed.
1517 * @exception ClassCastException When the event wasn't provided by
1518 * the createEvent method, or otherwise isn't a DomEvent.
1519 * @exception EventException If the event type wasn't specified
1521 public final boolean dispatchEvent(Event event
)
1522 throws EventException
1524 DomEvent e
= (DomEvent
) event
;
1525 DomNode
[] ancestors
= null;
1526 int ancestorMax
= 0;
1527 boolean haveDispatchDataLock
= false;
1531 throw new DomEventException();
1538 // Typical case: one nonrecursive dispatchEvent call at a time
1539 // for this class. If that's our case, we can avoid allocating
1540 // garbage, which is overall a big win. Even with advanced GCs
1541 // that deal well with short-lived garbage, and wayfast allocators,
1544 // Remember -- EVERY mutation goes though here at least once.
1546 // When populating a DOM tree, trying to send mutation events is
1547 // the primary cost; this dominates the critical path.
1553 boolean haveAncestorRegistrations
= false;
1554 ListenerRecord
[] notificationSet
;
1557 synchronized (lockNode
)
1559 if (!dispatchDataLock
)
1561 haveDispatchDataLock
= dispatchDataLock
= true;
1562 notificationSet
= DomNode
.notificationSet
;
1563 ancestors
= DomNode
.ancestors
;
1567 notificationSet
= new ListenerRecord
[NOTIFICATIONS_INIT
];
1568 ancestors
= new DomNode
[ANCESTORS_INIT
];
1570 ancestorLen
= ancestors
.length
;
1573 // XXX autogrow ancestors ... based on statistics
1575 // Climb to the top of this subtree and handle capture, letting
1576 // each node (from the top down) capture until one stops it or
1577 // until we get to this one.
1579 for (index
= 0, current
= parent
;
1580 current
!= null && index
< ancestorLen
;
1581 index
++, current
= current
.parent
)
1583 if (current
.nListeners
!= 0)
1585 haveAncestorRegistrations
= true;
1587 ancestors
[index
] = current
;
1589 if (current
!= null)
1591 throw new RuntimeException("dispatchEvent capture stack size");
1594 ancestorMax
= index
;
1597 if (haveAncestorRegistrations
)
1599 e
.eventPhase
= Event
.CAPTURING_PHASE
;
1600 while (!e
.stop
&& index
-- > 0)
1602 current
= ancestors
[index
];
1603 if (current
.nListeners
!= 0)
1605 notifyNode(e
, current
, true, notificationSet
);
1610 // Always deliver events to the target node (this)
1611 // unless stopPropagation was called. If we saw
1612 // no registrations yet (typical!), we never will.
1613 if (!e
.stop
&& nListeners
!= 0)
1615 e
.eventPhase
= Event
.AT_TARGET
;
1616 notifyNode (e
, this, false, notificationSet
);
1618 else if (!haveAncestorRegistrations
)
1623 // If the event bubbles and propagation wasn't halted,
1624 // walk back up the ancestor list. Stop bubbling when
1625 // any bubbled event handler stops it.
1627 if (!e
.stop
&& e
.bubbles
)
1629 e
.eventPhase
= Event
.BUBBLING_PHASE
;
1632 && index
< ancestorMax
1633 && (current
= ancestors
[index
]) != null;
1636 if (current
.nListeners
!= 0)
1638 notifyNode(e
, current
, false, notificationSet
);
1644 // Caller chooses whether to perform the default
1645 // action based on return from this method.
1651 if (haveDispatchDataLock
)
1653 // synchronize to force write ordering
1654 synchronized (lockNode
)
1656 // null out refs to ensure they'll be GC'd
1657 for (int i
= 0; i
< ancestorMax
; i
++)
1659 ancestors
[i
] = null;
1661 // notificationSet handled by notifyNode
1663 dispatchDataLock
= false;
1669 private void notifyNode(DomEvent e
,
1672 ListenerRecord
[] notificationSet
)
1676 // do any of this set of listeners get notified?
1677 for (int i
= 0; i
< current
.nListeners
; i
++)
1679 ListenerRecord rec
= current
.listeners
[i
];
1681 if (rec
.useCapture
!= capture
)
1685 if (!e
.type
.equals (rec
.type
))
1689 if (count
< notificationSet
.length
)
1691 notificationSet
[count
++] = rec
;
1694 // XXX fire up some cheap growth algorithm
1695 throw new RuntimeException("Event notification set size exceeded");
1698 // Notify just those listeners
1699 e
.currentNode
= current
;
1700 for (int i
= 0; i
< count
; i
++)
1704 // Late in the DOM CR process (3rd or 4th CR?) the
1705 // removeEventListener spec became asymmetric with respect
1706 // to addEventListener ... effect is now immediate.
1707 for (int j
= 0; j
< current
.nListeners
; j
++)
1709 if (current
.listeners
[j
].equals(notificationSet
[i
]))
1711 notificationSet
[i
].listener
.handleEvent(e
);
1719 // ignore all exceptions
1721 notificationSet
[i
] = null; // free for GC
1726 * <b>DOM L2 (Events)</b>
1727 * Unregisters an event listener.
1729 public final void removeEventListener(String type
,
1730 EventListener listener
,
1733 for (int i
= 0; i
< nListeners
; i
++)
1735 if (listeners
[i
].listener
!= listener
)
1739 if (listeners
[i
].useCapture
!= useCapture
)
1743 if (!listeners
[i
].type
.equals(type
))
1748 if (nListeners
== 1)
1755 for (int j
= i
+ 1; j
< nListeners
; j
++)
1757 listeners
[i
++] = listeners
[j
++];
1759 listeners
[--nListeners
] = null;
1763 // no exceptions reported
1767 * <b>DOM L1 (relocated in DOM L2)</b>
1768 * In this node and all contained nodes (including attributes if
1769 * relevant) merge adjacent text nodes. This is done while ignoring
1770 * text which happens to use CDATA delimiters).
1772 public final void normalize()
1774 // Suspend readonly status
1775 boolean saved
= readonly
;
1777 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
1779 switch (ctx
.nodeType
)
1782 while (ctx
.next
!= null && ctx
.next
.nodeType
== TEXT_NODE
)
1784 Text text
= (Text
) ctx
;
1785 text
.appendData(ctx
.next
.getNodeValue());
1786 removeChild(ctx
.next
);
1790 NamedNodeMap attrs
= ctx
.getAttributes();
1791 int len
= attrs
.getLength();
1792 for (int i
= 0; i
< len
; i
++)
1794 attrs
.item(i
).normalize();
1798 case DOCUMENT_FRAGMENT_NODE
:
1799 case ATTRIBUTE_NODE
:
1800 case ENTITY_REFERENCE_NODE
:
1809 * Returns true iff node types match, and either (a) both nodes have no
1810 * namespace and their getNodeName() values are the same, or (b) both
1811 * nodes have the same getNamespaceURI() and same getLocalName() values.
1813 * <p>Note that notion of a "Per-Element-Type" attribute name scope, as
1814 * found in a non-normative appendix of the XML Namespaces specification,
1815 * is not supported here. Your application must implement that notion,
1816 * typically by not bothering to check nameAndTypeEquals for attributes
1817 * without namespace URIs unless you already know their elements are
1818 * nameAndTypeEquals.
1820 public boolean nameAndTypeEquals(Node other
)
1826 // node types must match
1827 if (nodeType
!= other
.getNodeType())
1832 // if both have namespaces, do a "full" comparision
1833 // this is a "global" partition
1834 String ns1
= this.getNamespaceURI();
1835 String ns2
= other
.getNamespaceURI();
1837 if (ns1
!= null && ns2
!= null)
1839 return ns1
.equals(ns2
) &&
1840 getLocalName().equals(other
.getLocalName());
1843 // if neither has a namespace, this is a "no-namespace" name.
1844 if (ns1
== null && ns2
== null)
1846 if (!getNodeName().equals(other
.getNodeName()))
1850 // can test the non-normative "per-element-type" scope here.
1851 // if this is an attribute node and both nodes have been bound
1852 // to elements (!!), then return the nameAndTypeEquals()
1853 // comparison of those elements.
1857 // otherwise they're unequal: one scoped, one not.
1861 // DOM Level 3 methods
1863 public String
getBaseURI()
1865 return (parent
!= null) ? parent
.getBaseURI() : null;
1868 public short compareDocumentPosition(Node other
)
1871 return (short) compareTo(other
);
1875 * DOM nodes have a natural ordering: document order.
1877 public final int compareTo(Object other
)
1879 if (other
instanceof DomNode
)
1882 DomNode n2
= (DomNode
) other
;
1883 if (n1
.owner
!= n2
.owner
)
1887 int d1
= n1
.depth
, d2
= n2
.depth
;
1888 int delta
= d1
- d2
;
1899 int c
= compareTo2(n1
, n2
);
1900 return (c
!= 0) ? c
: delta
;
1906 * Compare two nodes at the same depth.
1908 final int compareTo2(DomNode n1
, DomNode n2
)
1910 if (n1
== n2
|| n1
.depth
== 0 || n2
.depth
== 0)
1914 int c
= compareTo2(n1
.parent
, n2
.parent
);
1915 return (c
!= 0) ? c
: n1
.index
- n2
.index
;
1918 public final String
getTextContent()
1921 return getTextContent(true);
1924 final String
getTextContent(boolean topLevel
)
1931 case ENTITY_REFERENCE_NODE
:
1932 case DOCUMENT_FRAGMENT_NODE
:
1933 StringBuffer buffer
= new StringBuffer();
1934 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
1936 String textContent
= ctx
.getTextContent(false);
1937 if (textContent
!= null)
1939 buffer
.append(textContent
);
1942 return buffer
.toString();
1944 case CDATA_SECTION_NODE
:
1945 if (((Text
) this).isElementContentWhitespace())
1949 return getNodeValue();
1950 case ATTRIBUTE_NODE
:
1951 return getNodeValue();
1953 case PROCESSING_INSTRUCTION_NODE
:
1954 return topLevel ?
getNodeValue() : "";
1960 public void setTextContent(String textContent
)
1966 case ATTRIBUTE_NODE
:
1968 case ENTITY_REFERENCE_NODE
:
1969 case DOCUMENT_FRAGMENT_NODE
:
1970 for (DomNode ctx
= first
; ctx
!= null; )
1972 DomNode n
= ctx
.next
;
1976 if (textContent
!= null)
1978 Text text
= owner
.createTextNode(textContent
);
1983 case CDATA_SECTION_NODE
:
1985 case PROCESSING_INSTRUCTION_NODE
:
1986 setNodeValue(textContent
);
1991 public boolean isSameNode(Node other
)
1993 return this == other
;
1996 public String
lookupPrefix(String namespaceURI
)
1998 return (parent
== null || parent
== owner
) ?
null :
1999 parent
.lookupPrefix(namespaceURI
);
2002 public boolean isDefaultNamespace(String namespaceURI
)
2004 return (parent
== null || parent
== owner
) ?
false :
2005 parent
.isDefaultNamespace(namespaceURI
);
2008 public String
lookupNamespaceURI(String prefix
)
2010 return (parent
== null || parent
== owner
) ?
null :
2011 parent
.lookupNamespaceURI(prefix
);
2014 public boolean isEqualNode(Node arg
)
2024 if (nodeType
!= arg
.getNodeType() ||
2025 !equal(getNodeName(), arg
.getNodeName()) ||
2026 !equal(getLocalName(), arg
.getLocalName()) ||
2027 !equal(getNamespaceURI(), arg
.getNamespaceURI()) ||
2028 !equal(getPrefix(), arg
.getPrefix()) ||
2029 !equal(getNodeValue(), arg
.getNodeValue()))
2034 Node argCtx
= arg
.getFirstChild();
2035 getFirstChild(); // because of DomAttr lazy children
2036 for (DomNode ctx
= first
; ctx
!= null; ctx
= ctx
.next
)
2038 if (!ctx
.isEqualNode(argCtx
))
2042 argCtx
= argCtx
.getNextSibling();
2049 // TODO Attr NamedNodeMap
2050 // TODO DocumentType
2054 boolean equal(String arg1
, String arg2
)
2056 return ((arg1
== null && arg2
== null) ||
2057 (arg1
!= null && arg1
.equals(arg2
)));
2060 public Object
getFeature(String feature
, String version
)
2062 DOMImplementation impl
= (nodeType
== DOCUMENT_NODE
) ?
2063 ((Document
) this).getImplementation() : owner
.getImplementation();
2064 if (impl
.hasFeature(feature
, version
))
2071 public Object
setUserData(String key
, Object data
, UserDataHandler handler
)
2073 if (userData
== null)
2075 userData
= new HashMap();
2077 if (handler
!= null)
2079 if (userDataHandlers
== null)
2081 userDataHandlers
= new HashMap();
2083 userDataHandlers
.put(key
, handler
);
2085 return userData
.put(key
, data
);
2088 public Object
getUserData(String key
)
2090 if (userData
== null)
2094 return userData
.get(key
);
2097 public String
toString()
2099 String nodeName
= getNodeName();
2100 String nodeValue
= getNodeValue();
2101 StringBuffer buf
= new StringBuffer(getClass().getName());
2103 if (nodeName
!= null)
2105 buf
.append(nodeName
);
2107 if (nodeValue
!= null)
2109 if (nodeName
!= null)
2114 buf
.append(encode(nodeValue
));
2118 return buf
.toString();
2121 String
encode(String value
)
2123 StringBuffer buf
= null;
2124 int len
= value
.length();
2125 for (int i
= 0; i
< len
; i
++)
2127 char c
= value
.charAt(i
);
2132 buf
= new StringBuffer(value
.substring(0, i
));
2140 buf
= new StringBuffer(value
.substring(0, i
));
2144 else if (buf
!= null)
2149 return (buf
!= null) ? buf
.toString() : value
;
2152 String
nodeTypeToString(short nodeType
)
2157 return "ELEMENT_NODE";
2158 case ATTRIBUTE_NODE
:
2159 return "ATTRIBUTE_NODE";
2162 case CDATA_SECTION_NODE
:
2163 return "CDATA_SECTION_NODE";
2165 return "DOCUMENT_NODE";
2166 case DOCUMENT_TYPE_NODE
:
2167 return "DOCUMENT_TYPE_NODE";
2169 return "COMMENT_NODE";
2170 case PROCESSING_INSTRUCTION_NODE
:
2171 return "PROCESSING_INSTRUCTION_NODE";
2172 case DOCUMENT_FRAGMENT_NODE
:
2173 return "DOCUMENT_FRAGMENT_NODE";
2175 return "ENTITY_NODE";
2176 case ENTITY_REFERENCE_NODE
:
2177 return "ENTITY_REFERENCE_NODE";
2179 return "NOTATION_NODE";