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;
43 import javax.xml.XMLConstants;
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;
61 import org.w3c.dom.traversal.TreeWalker;
64 * <p> "Node", "EventTarget", and "DocumentEvent" implementation.
65 * This provides most of the core DOM functionality; only more
66 * specialized features are provided by subclasses. Those subclasses may
67 * have some particular constraints they must implement, by overriding
68 * methods defined here. Such constraints are noted here in the method
71 * <p> Note that you can create events with type names prefixed with "USER-",
72 * and pass them through this DOM. This lets you use the DOM event scheme
73 * for application specific purposes, although you must use a predefined event
74 * structure (such as MutationEvent) to pass data along with those events.
75 * Test for existence of this feature with the "USER-Events" DOM feature
78 * <p> Other kinds of events you can send include the "html" events,
79 * like "load", "unload", "abort", "error", and "blur"; and the mutation
80 * events. If this DOM has been compiled with mutation event support
81 * enabled, it will send mutation events when you change parts of the
82 * tree; otherwise you may create and send such events yourself, but
83 * they won't be generated by the DOM itself. </p>
85 * <p> Note that there is a namespace-aware name comparison method,
86 * <em>nameAndTypeEquals</em>, which compares the names (and types) of
87 * two nodes in conformance with the "Namespaces in XML" specification.
88 * While mostly intended for use with elements and attributes, this should
89 * also be helpful for ProcessingInstruction nodes and some others which
90 * do not have namespace URIs.
92 * @author David Brownell
93 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
95 public abstract class DomNode
96 implements Node, NodeList, EventTarget, DocumentEvent, Cloneable, Comparable
100 //final static String xmlNamespace = "http://www.w3.org/XML/1998/namespace";
101 //final static String xmlnsURI = "http://www.w3.org/2000/xmlns/";
104 // NKIDS_* affects arrays of children (which grow)
105 // (currently) fixed size:
106 // ANCESTORS_* is for event capture/bubbling, # ancestors
107 // NOTIFICATIONS_* is for per-node event delivery, # events
108 private static final int NKIDS_DELTA = 8;
109 private static final int ANCESTORS_INIT = 20;
110 private static final int NOTIFICATIONS_INIT = 10;
112 // tunable: enable mutation events or not? Enabling it costs about
113 // 10-15% in DOM construction time, last time it was measured.
115 // package private !!!
116 static final boolean reportMutations = true;
118 // locking protocol changeable only within this class
119 private static final Object lockNode = new Object();
121 // NON-FINAL class data
123 // Optimize event dispatch by not allocating memory each time
124 private static boolean dispatchDataLock;
125 private static DomNode[] ancestors = new DomNode[ANCESTORS_INIT];
126 private static ListenerRecord[] notificationSet
127 = new ListenerRecord[NOTIFICATIONS_INIT];
129 // Ditto for the (most common) event object itself!
130 private static boolean eventDataLock;
131 private static DomEvent.DomMutationEvent mutationEvent
132 = new DomEvent.DomMutationEvent(null);
139 DomNode parent; // parent node;
140 DomNode previous; // previous sibling node
141 DomNode next; // next sibling node
142 DomNode first; // first child node
143 DomNode last; // last child node
144 int index; // index of this node in its parent's children
145 int depth; // depth of the node in the document
146 int length; // number of children
147 final short nodeType;
149 // Bleech ... "package private" so a builder can populate entity refs.
150 // writable during construction. DOM spec is nasty.
153 // event registrations
154 private ListenerRecord[] listeners;
155 private int nListeners;
157 // DOM Level 3 userData dictionary.
158 private HashMap userData;
159 private HashMap userDataHandlers;
162 // Some of the methods here are declared 'final' because
163 // knowledge about their implementation is built into this
164 // class -- for both integrity and performance.
168 * Reduces space utilization for this node.
170 public void compact()
172 if (listeners != null && listeners.length != nListeners)
180 ListenerRecord[] l = new ListenerRecord[nListeners];
181 System.arraycopy(listeners, 0, l, 0, nListeners);
188 * Constructs a node and associates it with its owner. Only
189 * Document and DocumentType nodes may be created with no owner,
190 * and DocumentType nodes get an owner as soon as they are
191 * associated with a document.
193 protected DomNode(short nodeType, DomDocument owner)
195 this.nodeType = nodeType;
199 // DOM calls never go down this path
200 if (nodeType != DOCUMENT_NODE && nodeType != DOCUMENT_TYPE_NODE)
202 throw new IllegalArgumentException ("no owner!");
211 * Returns null; Element subclasses must override this method.
213 public NamedNodeMap getAttributes()
220 * Returns true iff this is an element node with attributes.
222 public boolean hasAttributes()
229 * Returns a list, possibly empty, of the children of this node.
230 * In this implementation, to conserve memory, nodes are the same
231 * as their list of children. This can have ramifications for
232 * subclasses, which may need to provide their own getLength method
233 * for reasons unrelated to the NodeList method of the same name.
235 public NodeList getChildNodes()
242 * Returns the first child of this node, or null if there are none.
244 public Node getFirstChild()
251 * Returns the last child of this node, or null if there are none.
253 public Node getLastChild()
260 * Returns true if this node has children.
262 public boolean hasChildNodes()
269 * Exposes the internal "readonly" flag. In DOM, children of
270 * entities and entity references are readonly, as are the
271 * objects associated with DocumentType objets.
273 public final boolean isReadonly()
279 * Sets the internal "readonly" flag so this subtree can't be changed.
280 * Subclasses need to override this method for any associated content
281 * that's not a child node, such as an element's attributes or the
282 * (few) declarations associated with a DocumentType.
284 public void makeReadonly()
287 for (DomNode child = first; child != null; child = child.next)
289 child.makeReadonly();
294 * Used to adopt a node to a new document.
296 void setOwner(DomDocument doc)
299 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
305 // just checks the node for inclusion -- may be called many
306 // times (docfrag) before anything is allowed to change
307 private void checkMisc(DomNode child)
309 if (readonly && !owner.building)
311 throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR,
314 for (DomNode ctx = this; ctx != null; ctx = ctx.parent)
318 throw new DomEx(DomEx.HIERARCHY_REQUEST_ERR,
319 "can't make ancestor into a child", this, 0);
323 DomDocument owner = (nodeType == DOCUMENT_NODE) ? (DomDocument) this :
325 DomDocument childOwner = child.owner;
326 short childNodeType = child.nodeType;
328 if (childOwner != owner)
330 // new in DOM L2, this case -- patch it up later, in reparent()
331 if (!(childNodeType == DOCUMENT_TYPE_NODE && childOwner == null))
333 throw new DomEx(DomEx.WRONG_DOCUMENT_ERR,
338 // enforce various structural constraints
342 switch (childNodeType)
345 case PROCESSING_INSTRUCTION_NODE:
347 case DOCUMENT_TYPE_NODE:
353 switch (childNodeType)
356 case ENTITY_REFERENCE_NODE:
361 case DOCUMENT_FRAGMENT_NODE:
362 case ENTITY_REFERENCE_NODE:
365 switch (childNodeType)
370 case PROCESSING_INSTRUCTION_NODE:
371 case CDATA_SECTION_NODE:
372 case ENTITY_REFERENCE_NODE:
377 if (owner.checkingWellformedness)
379 throw new DomEx(DomEx.HIERARCHY_REQUEST_ERR,
380 "can't append " + nodeTypeToString(childNodeType) +
381 " to node of type " + 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 DomEx(DomEx.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 DomEx(DomEx.NOT_FOUND_ERR, null, ref, 0);
637 throw new DomEx(DomEx.HIERARCHY_REQUEST_ERR,
638 "can't insert node before itself", ref, 0);
641 if (child.parent != null)
643 child.parent.removeChild(child);
647 child.setDepth(depth + 1);
649 if (ref.previous != null)
651 ref.previous.next = child;
653 child.previous = ref.previous;
654 ref.previous = child;
660 for (DomNode ctx = child; ctx != null; ctx = ctx.next)
667 insertionEvent(null, child);
673 catch (ClassCastException e)
675 throw new DomEx(DomEx.WRONG_DOCUMENT_ERR,
682 * Replaces the specified node in this node's list of children.
683 * Document subclasses must override this to test the restrictions
684 * that there be only one element and document type child.
686 * <p> Causes DOMNodeRemoved and DOMNodeInserted mutation event to be
687 * reported. Will cause another DOMNodeRemoved event to be reported if
688 * the newChild parameter already has a parent. These events may be
689 * delivered in any order, except that the event reporting removal
690 * from such an existing parent will always be delivered before the
691 * event reporting its re-insertion as a child of some other node.
692 * The order in which children are removed and inserted is implementation
695 * <p> If your application needs to depend on the in which those removal
696 * and insertion events are delivered, don't use this API. Instead,
697 * invoke the removeChild and insertBefore methods directly, to guarantee
698 * a specific delivery order. Similarly, don't use document fragments,
699 * Otherwise your application code may not work on a DOM which implements
700 * this method differently.
702 * <p> If this DOM has been compiled without mutation event support,
703 * these events will not be reported.
705 public Node replaceChild(Node newChild, Node refChild)
709 DomNode child = (DomNode) newChild;
710 DomNode ref = (DomNode) refChild;
712 DomEvent.DomMutationEvent event = getMutationEvent();
713 boolean doFree = (event != null);
715 if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
717 // Append all nodes in the fragment to this node
718 for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
722 if (ref == null || ref.parent != this)
724 throw new DomEx(DomEx.NOT_FOUND_ERR, null, ref, 0);
729 removalEvent(event, ref);
732 length += child.length;
734 if (child.length == 0)
737 if (ref.previous != null)
739 ref.previous.next = ref.next;
741 if (ref.next != null)
743 ref.next.previous = ref.previous;
757 for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
762 ctx.setDepth(ref.depth);
763 if (ctx == child.first)
765 ctx.previous = ref.previous;
767 if (ctx == child.last)
785 if (ref == null || ref.parent != this)
787 throw new DomEx(DomEx.NOT_FOUND_ERR, null, ref, 0);
792 removalEvent(event, ref);
795 if (child.parent != null)
797 child.parent.removeChild(child);
800 child.index = ref.index;
801 child.setDepth(ref.depth);
802 if (ref.previous != null)
804 ref.previous.next = child;
806 child.previous = ref.previous;
807 if (ref.next != null)
809 ref.next.previous = child;
811 child.next = ref.next;
823 insertionEvent(event, child);
838 catch (ClassCastException e)
840 throw new DomEx(DomEx.WRONG_DOCUMENT_ERR,
847 * Removes the specified child from this node's list of children,
848 * or else reports an exception.
850 * <p> Causes a DOMNodeRemoved mutation event to be reported.
852 * <p> If this DOM has been compiled without mutation event support,
853 * these events will not be reported.
855 public Node removeChild(Node refChild)
859 DomNode ref = (DomNode) refChild;
861 if (ref == null || ref.parent != this)
863 throw new DomEx(DomEx.NOT_FOUND_ERR, null, ref, 0);
865 if (readonly && !owner.building)
867 throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR,
871 for (DomNode child = first; child != null; child = child.next)
877 removalEvent(null, child);
881 if (ref.previous != null)
883 ref.previous.next = ref.next;
885 if (ref.next != null)
887 ref.next.previous = ref.previous;
899 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
912 throw new DomEx(DomEx.NOT_FOUND_ERR,
913 "that's no child of mine", refChild, 0);
915 catch (ClassCastException e)
917 throw new DomEx(DomEx.WRONG_DOCUMENT_ERR,
923 * <b>DOM L1 (NodeList)</b>
924 * Returns the item with the specified index in this NodeList,
927 public Node item(int index)
929 DomNode child = first;
931 while (child != null && count < index)
940 * <b>DOM L1 (NodeList)</b>
941 * Returns the number of elements in this NodeList.
942 * (Note that many interfaces have a "Length" property, not just
943 * NodeList, and if a node subtype must implement one of those,
944 * it will also need to override getChildNodes.)
946 public int getLength()
952 * Minimize extra space consumed by this node to hold children and event
955 public void trimToSize()
957 if (listeners != null && listeners.length != nListeners)
959 ListenerRecord[] newKids = new ListenerRecord[length];
960 System.arraycopy(listeners, 0, newKids, 0, nListeners);
967 * Returns the previous sibling, if one is known.
969 public Node getNextSibling()
976 * Returns the previous sibling, if one is known.
978 public Node getPreviousSibling()
985 * Returns the parent node, if one is known.
987 public Node getParentNode()
994 * Consults the DOM implementation to determine if the requested
995 * feature is supported. DocumentType subclasses must override
996 * this method, and associate themselves directly with the
997 * DOMImplementation node used. (This method relies on being able
998 * to access the DOMImplementation from the owner document, but
999 * DocumentType nodes can be created without an owner.)
1001 public boolean isSupported(String feature, String version)
1003 Document doc = owner;
1004 DOMImplementation impl = null;
1006 if (doc == null && nodeType == DOCUMENT_NODE)
1008 doc = (Document) this;
1013 // possible for DocumentType
1014 throw new IllegalStateException ("unbound ownerDocument");
1017 impl = doc.getImplementation();
1018 return impl.hasFeature(feature, version);
1022 * <b>DOM L1 (modified in L2)</b>
1023 * Returns the owner document. This is only null for Document nodes,
1024 * and (new in L2) for DocumentType nodes which have not yet been
1025 * associated with the rest of their document.
1027 final public Document getOwnerDocument()
1034 * Does nothing; this must be overridden (along with the
1035 * getNodeValue method) for nodes with a non-null defined value.
1037 public void setNodeValue(String value)
1043 * Returns null; this must be overridden for nodes types with
1044 * a defined value, along with the setNodeValue method.
1046 public String getNodeValue()
1051 /** This forces GCJ compatibility.
1052 * Without this method GCJ is unable to compile to byte code.
1054 public final short getNodeType()
1059 /** This forces GCJ compatibility.
1060 * Without this method GCJ seems unable to natively compile GNUJAXP.
1062 public abstract String getNodeName();
1066 * Does nothing; this must be overridden (along with the
1067 * getPrefix method) for element and attribute nodes.
1069 public void setPrefix(String prefix)
1075 * Returns null; this must be overridden for element and
1078 public String getPrefix()
1085 * Returns null; this must be overridden for element and
1088 public String getNamespaceURI()
1095 * Returns the node name; this must be overridden for element and
1098 public String getLocalName()
1105 * Returns a clone of this node which optionally includes cloned
1106 * versions of child nodes. Clones are always mutable, except for
1107 * entity reference nodes.
1109 public Node cloneNode(boolean deep)
1111 DomNode node = (DomNode) clone();
1115 DomDocument doc = (nodeType == DOCUMENT_NODE) ?
1116 (DomDocument) node : node.owner;
1117 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
1119 DomNode newChild = (DomNode) ctx.cloneNode(deep);
1120 newChild.setOwner(doc);
1121 node.appendChild(newChild);
1125 if (nodeType == ENTITY_REFERENCE_NODE)
1127 node.makeReadonly();
1129 notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node);
1133 void notifyUserDataHandlers(short op, Node src, Node dst)
1135 if (userDataHandlers != null)
1137 for (Iterator i = userDataHandlers.entrySet().iterator(); i.hasNext(); )
1139 Map.Entry entry = (Map.Entry) i.next();
1140 String key = (String) entry.getKey();
1141 UserDataHandler handler = (UserDataHandler) entry.getValue();
1142 Object data = userData.get(key);
1143 handler.handle(op, key, data, src, dst);
1149 * Clones this node; roughly equivalent to cloneNode(false).
1150 * Element subclasses must provide a new implementation which
1151 * invokes this method to handle the basics, and then arranges
1152 * to clone any element attributes directly. Attribute subclasses
1153 * must make similar arrangements, ensuring that existing ties to
1154 * elements are broken by cloning.
1156 public Object clone()
1160 DomNode node = (DomNode) super.clone();
1168 node.previous = null;
1171 node.readonly = false;
1172 node.listeners = null;
1173 node.nListeners = 0;
1177 catch (CloneNotSupportedException x)
1179 throw new Error("clone didn't work");
1183 // the elements-by-tagname stuff is needed for both
1184 // elements and documents ... this is in lieu of a
1185 // common base class between Node and NodeNS.
1189 * Creates a NodeList giving array-style access to elements with
1190 * the specified name. Access is fastest if indices change by
1191 * small values, and the DOM is not modified.
1193 public NodeList getElementsByTagName(String tag)
1195 return new ShadowList(null, tag);
1200 * Creates a NodeList giving array-style access to elements with
1201 * the specified namespace and local name. Access is fastest if
1202 * indices change by small values, and the DOM is not modified.
1204 public NodeList getElementsByTagNameNS(String namespace, String local)
1206 return new ShadowList(namespace, local);
1211 // This shadow class is GC-able even when the live list it shadows
1212 // can't be, because of event registration hookups. Its finalizer
1213 // makes that live list become GC-able.
1215 final class ShadowList
1219 private LiveNodeList liveList;
1221 ShadowList(String ns, String local)
1223 liveList = new LiveNodeList(ns, local);
1226 public void finalize()
1232 public Node item(int index)
1234 return liveList.item(index);
1237 public int getLength()
1239 return liveList.getLength();
1243 final class LiveNodeList
1244 implements NodeList, EventListener, NodeFilter
1247 private final boolean matchAnyURI;
1248 private final boolean matchAnyName;
1249 private final String elementURI;
1250 private final String elementName;
1252 private DomIterator current;
1253 private int lastIndex;
1255 LiveNodeList(String uri, String name)
1259 matchAnyURI = "*".equals(uri);
1260 matchAnyName = "*".equals(name);
1262 DomNode.this.addEventListener("DOMNodeInserted", this, true);
1263 DomNode.this.addEventListener("DOMNodeRemoved", this, true);
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)
1336 public Node item(int index)
1338 if (current == null)
1340 current = createIterator();
1344 // last node or before? go backwards
1345 if (index <= lastIndex) {
1346 while (index != lastIndex) {
1347 current.previousNode ();
1350 Node ret = current.previousNode ();
1355 // somewhere after last node
1356 while (++lastIndex != index)
1357 current.nextNode ();
1358 Node ret = current.nextNode ();
1363 public int getLength()
1366 NodeIterator iter = createIterator();
1368 while (iter.nextNode() != null)
1379 // EventTarget support
1381 static final class ListenerRecord
1385 EventListener listener;
1388 // XXX use JDK 1.2 java.lang.ref.WeakReference to listener,
1389 // and we can both get rid of "shadow" classes and remove
1390 // the need for applications to apply similar trix ... but
1391 // JDK 1.2 support isn't generally available yet
1393 ListenerRecord(String type, EventListener listener, boolean useCapture)
1395 this.type = type.intern();
1396 this.listener = listener;
1397 this.useCapture = useCapture;
1400 boolean equals(ListenerRecord rec)
1402 return listener == rec.listener
1403 && useCapture == rec.useCapture
1404 && type == rec.type;
1410 * <b>DOM L2 (Events)</b>
1411 * Returns an instance of the specified type of event object.
1412 * Understands about DOM Mutation, HTML, and UI events.
1414 * <p>If the name of the event type begins with "USER-", then an object
1415 * implementing the "Event" class will be returned; this provides a
1416 * limited facility for application-defined events to use the DOM event
1417 * infrastructure. Alternatively, use one of the standard DOM event
1418 * classes and initialize it using use such a "USER-" event type name;
1419 * or defin, instantiate, and initialize an application-specific subclass
1420 * of DomEvent and pass that to dispatchEvent().
1422 * @param eventType Identifies the particular DOM feature module
1423 * defining the type of event, such as "MutationEvents".
1424 * <em>The event "name" is a different kind of "type".</em>
1426 public Event createEvent(String eventType)
1428 eventType = eventType.toLowerCase();
1430 if ("mutationevents".equals(eventType))
1432 return new DomEvent.DomMutationEvent(null);
1435 if ("htmlevents".equals(eventType)
1436 || "events".equals(eventType)
1437 || "user-events".equals(eventType))
1439 return new DomEvent(null);
1442 if ("uievents".equals(eventType))
1444 return new DomEvent.DomUIEvent(null);
1449 throw new DomEx(DomEx.NOT_SUPPORTED_ERR,
1450 eventType, null, 0);
1454 * <b>DOM L2 (Events)</b>
1455 * Registers an event listener's interest in a class of events.
1457 public final void addEventListener(String type,
1458 EventListener listener,
1461 if (listeners == null)
1463 listeners = new ListenerRecord[1];
1465 else if (nListeners == listeners.length)
1467 ListenerRecord[] newListeners =
1468 new ListenerRecord[listeners.length + NKIDS_DELTA];
1469 System.arraycopy(listeners, 0, newListeners, 0, nListeners);
1470 listeners = newListeners;
1474 ListenerRecord record;
1476 record = new ListenerRecord(type, listener, useCapture);
1477 for (int i = 0; i < nListeners; i++)
1479 if (record.equals(listeners[i]))
1484 listeners [nListeners++] = record;
1487 // XXX this exception should be discarded from DOM
1489 // this class can be instantiated, unlike the one in the spec
1490 static final class DomEventException
1491 extends EventException
1496 super(UNSPECIFIED_EVENT_TYPE_ERR, "unspecified event type");
1502 * <b>DOM L2 (Events)</b>
1503 * Delivers an event to all relevant listeners, returning true if the
1504 * caller should perform their default action. Note that the event
1505 * must have been provided by the createEvent() method on this
1506 * class, else it can't be dispatched.
1510 * @exception NullPointerException When a null event is passed.
1511 * @exception ClassCastException When the event wasn't provided by
1512 * the createEvent method, or otherwise isn't a DomEvent.
1513 * @exception EventException If the event type wasn't specified
1515 public final boolean dispatchEvent(Event event)
1516 throws EventException
1518 DomEvent e = (DomEvent) event;
1519 DomNode[] ancestors = null;
1520 int ancestorMax = 0;
1521 boolean haveDispatchDataLock = false;
1525 throw new DomEventException();
1532 // Typical case: one nonrecursive dispatchEvent call at a time
1533 // for this class. If that's our case, we can avoid allocating
1534 // garbage, which is overall a big win. Even with advanced GCs
1535 // that deal well with short-lived garbage, and wayfast allocators,
1538 // Remember -- EVERY mutation goes though here at least once.
1540 // When populating a DOM tree, trying to send mutation events is
1541 // the primary cost; this dominates the critical path.
1547 boolean haveAncestorRegistrations = false;
1548 ListenerRecord[] notificationSet;
1551 synchronized (lockNode)
1553 if (!dispatchDataLock)
1555 haveDispatchDataLock = dispatchDataLock = true;
1556 notificationSet = DomNode.notificationSet;
1557 ancestors = DomNode.ancestors;
1561 notificationSet = new ListenerRecord[NOTIFICATIONS_INIT];
1562 ancestors = new DomNode[ANCESTORS_INIT];
1564 ancestorLen = ancestors.length;
1567 // XXX autogrow ancestors ... based on statistics
1569 // Climb to the top of this subtree and handle capture, letting
1570 // each node (from the top down) capture until one stops it or
1571 // until we get to this one.
1573 for (index = 0, current = parent;
1574 current != null && index < ancestorLen;
1575 index++, current = current.parent)
1577 if (current.nListeners != 0)
1579 haveAncestorRegistrations = true;
1581 ancestors [index] = current;
1583 if (current != null)
1585 throw new RuntimeException("dispatchEvent capture stack size");
1588 ancestorMax = index;
1591 if (haveAncestorRegistrations)
1593 e.eventPhase = Event.CAPTURING_PHASE;
1594 while (!e.stop && index-- > 0)
1596 current = ancestors [index];
1597 if (current.nListeners != 0)
1599 notifyNode(e, current, true, notificationSet);
1604 // Always deliver events to the target node (this)
1605 // unless stopPropagation was called. If we saw
1606 // no registrations yet (typical!), we never will.
1607 if (!e.stop && nListeners != 0)
1609 e.eventPhase = Event.AT_TARGET;
1610 notifyNode (e, this, false, notificationSet);
1612 else if (!haveAncestorRegistrations)
1617 // If the event bubbles and propagation wasn't halted,
1618 // walk back up the ancestor list. Stop bubbling when
1619 // any bubbled event handler stops it.
1621 if (!e.stop && e.bubbles)
1623 e.eventPhase = Event.BUBBLING_PHASE;
1626 && index < ancestorMax
1627 && (current = ancestors[index]) != null;
1630 if (current.nListeners != 0)
1632 notifyNode(e, current, false, notificationSet);
1638 // Caller chooses whether to perform the default
1639 // action based on return from this method.
1645 if (haveDispatchDataLock)
1647 // synchronize to force write ordering
1648 synchronized (lockNode)
1650 // null out refs to ensure they'll be GC'd
1651 for (int i = 0; i < ancestorMax; i++)
1653 ancestors [i] = null;
1655 // notificationSet handled by notifyNode
1657 dispatchDataLock = false;
1663 private void notifyNode(DomEvent e,
1666 ListenerRecord[] notificationSet)
1670 // do any of this set of listeners get notified?
1671 for (int i = 0; i < current.nListeners; i++)
1673 ListenerRecord rec = current.listeners[i];
1675 if (rec.useCapture != capture)
1679 if (!e.type.equals (rec.type))
1683 if (count < notificationSet.length)
1685 notificationSet[count++] = rec;
1688 // XXX fire up some cheap growth algorithm
1689 throw new RuntimeException("Event notification set size exceeded");
1692 // Notify just those listeners
1693 e.currentNode = current;
1694 for (int i = 0; i < count; i++)
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 for (int j = 0; j < current.nListeners; j++)
1703 if (current.listeners[j].equals(notificationSet[i]))
1705 notificationSet[i].listener.handleEvent(e);
1713 // ignore all exceptions
1715 notificationSet[i] = null; // free for GC
1720 * <b>DOM L2 (Events)</b>
1721 * Unregisters an event listener.
1723 public final void removeEventListener(String type,
1724 EventListener listener,
1727 for (int i = 0; i < nListeners; i++)
1729 if (listeners[i].listener != listener)
1733 if (listeners[i].useCapture != useCapture)
1737 if (!listeners[i].type.equals(type))
1742 if (nListeners == 1)
1749 for (int j = i + 1; j < nListeners; j++)
1751 listeners[i++] = listeners[j++];
1753 listeners[--nListeners] = null;
1757 // no exceptions reported
1761 * <b>DOM L1 (relocated in DOM L2)</b>
1762 * In this node and all contained nodes (including attributes if
1763 * relevant) merge adjacent text nodes. This is done while ignoring
1764 * text which happens to use CDATA delimiters).
1766 public final void normalize()
1768 // Suspend readonly status
1769 boolean saved = readonly;
1771 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
1773 switch (ctx.nodeType)
1776 while (ctx.next != null && ctx.next.nodeType == TEXT_NODE)
1778 Text text = (Text) ctx;
1779 text.appendData(ctx.next.getNodeValue());
1780 removeChild(ctx.next);
1784 NamedNodeMap attrs = ctx.getAttributes();
1785 int len = attrs.getLength();
1786 for (int i = 0; i < len; i++)
1788 attrs.item(i).normalize();
1792 case DOCUMENT_FRAGMENT_NODE:
1793 case ATTRIBUTE_NODE:
1794 case ENTITY_REFERENCE_NODE:
1803 * Returns true iff node types match, and either (a) both nodes have no
1804 * namespace and their getNodeName() values are the same, or (b) both
1805 * nodes have the same getNamespaceURI() and same getLocalName() values.
1807 * <p>Note that notion of a "Per-Element-Type" attribute name scope, as
1808 * found in a non-normative appendix of the XML Namespaces specification,
1809 * is not supported here. Your application must implement that notion,
1810 * typically by not bothering to check nameAndTypeEquals for attributes
1811 * without namespace URIs unless you already know their elements are
1812 * nameAndTypeEquals.
1814 public boolean nameAndTypeEquals(Node other)
1820 // node types must match
1821 if (nodeType != other.getNodeType())
1826 // if both have namespaces, do a "full" comparision
1827 // this is a "global" partition
1828 String ns1 = this.getNamespaceURI();
1829 String ns2 = other.getNamespaceURI();
1831 if (ns1 != null && ns2 != null)
1833 return ns1.equals(ns2) &&
1834 getLocalName().equals(other.getLocalName());
1837 // if neither has a namespace, this is a "no-namespace" name.
1838 if (ns1 == null && ns2 == null)
1840 if (!getNodeName().equals(other.getNodeName()))
1844 // can test the non-normative "per-element-type" scope here.
1845 // if this is an attribute node and both nodes have been bound
1846 // to elements (!!), then return the nameAndTypeEquals()
1847 // comparison of those elements.
1851 // otherwise they're unequal: one scoped, one not.
1855 // DOM Level 3 methods
1857 public String getBaseURI()
1859 return (parent != null) ? parent.getBaseURI() : null;
1862 public short compareDocumentPosition(Node other)
1865 return (short) compareTo(other);
1869 * DOM nodes have a natural ordering: document order.
1871 public final int compareTo(Object other)
1873 if (other instanceof DomNode)
1876 DomNode n2 = (DomNode) other;
1877 if (n1.owner != n2.owner)
1881 int d1 = n1.depth, d2 = n2.depth;
1882 int delta = d1 - d2;
1893 int c = compareTo2(n1, n2);
1894 return (c != 0) ? c : delta;
1900 * Compare two nodes at the same depth.
1902 final int compareTo2(DomNode n1, DomNode n2)
1904 if (n1 == n2 || n1.depth == 0 || n2.depth == 0)
1908 int c = compareTo2(n1.parent, n2.parent);
1909 return (c != 0) ? c : n1.index - n2.index;
1912 public final String getTextContent()
1915 return getTextContent(true);
1918 final String getTextContent(boolean topLevel)
1925 case ENTITY_REFERENCE_NODE:
1926 case DOCUMENT_FRAGMENT_NODE:
1927 StringBuffer buffer = new StringBuffer();
1928 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
1930 String textContent = ctx.getTextContent(false);
1931 if (textContent != null)
1933 buffer.append(textContent);
1936 return buffer.toString();
1938 case CDATA_SECTION_NODE:
1939 if (((Text) this).isElementContentWhitespace())
1943 return getNodeValue();
1944 case ATTRIBUTE_NODE:
1945 return getNodeValue();
1947 case PROCESSING_INSTRUCTION_NODE:
1948 return topLevel ? getNodeValue() : "";
1954 public void setTextContent(String textContent)
1960 case ATTRIBUTE_NODE:
1962 case ENTITY_REFERENCE_NODE:
1963 case DOCUMENT_FRAGMENT_NODE:
1964 for (DomNode ctx = first; ctx != null; )
1966 DomNode n = ctx.next;
1970 if (textContent != null)
1972 Text text = owner.createTextNode(textContent);
1977 case CDATA_SECTION_NODE:
1979 case PROCESSING_INSTRUCTION_NODE:
1980 setNodeValue(textContent);
1985 public boolean isSameNode(Node other)
1987 return this == other;
1990 public String lookupPrefix(String namespaceURI)
1992 return (parent == null || parent == owner) ? null :
1993 parent.lookupPrefix(namespaceURI);
1996 public boolean isDefaultNamespace(String namespaceURI)
1998 return (parent == null || parent == owner) ? false :
1999 parent.isDefaultNamespace(namespaceURI);
2002 public String lookupNamespaceURI(String prefix)
2004 return (parent == null || parent == owner) ? null :
2005 parent.lookupNamespaceURI(prefix);
2008 public boolean isEqualNode(Node arg)
2018 if (nodeType != arg.getNodeType() ||
2019 !equal(getNodeName(), arg.getNodeName()) ||
2020 !equal(getLocalName(), arg.getLocalName()) ||
2021 !equal(getNamespaceURI(), arg.getNamespaceURI()) ||
2022 !equal(getPrefix(), arg.getPrefix()) ||
2023 !equal(getNodeValue(), arg.getNodeValue()))
2028 Node argCtx = arg.getFirstChild();
2029 getFirstChild(); // because of DomAttr lazy children
2030 for (DomNode ctx = first; ctx != null; ctx = ctx.next)
2032 if (!ctx.isEqualNode(argCtx))
2036 argCtx = argCtx.getNextSibling();
2043 // TODO Attr NamedNodeMap
2044 // TODO DocumentType
2048 boolean equal(String arg1, String arg2)
2050 return ((arg1 == null && arg2 == null) ||
2051 (arg1 != null && arg1.equals(arg2)));
2054 public Object getFeature(String feature, String version)
2056 DOMImplementation impl = (nodeType == DOCUMENT_NODE) ?
2057 ((Document) this).getImplementation() : owner.getImplementation();
2058 if (impl.hasFeature(feature, version))
2065 public Object setUserData(String key, Object data, UserDataHandler handler)
2067 if (userData == null)
2069 userData = new HashMap();
2071 if (handler != null)
2073 if (userDataHandlers == null)
2075 userDataHandlers = new HashMap();
2077 userDataHandlers.put(key, handler);
2079 return userData.put(key, data);
2082 public Object getUserData(String key)
2084 if (userData == null)
2088 return userData.get(key);
2091 public String toString()
2093 String nodeName = getNodeName();
2094 String nodeValue = getNodeValue();
2095 StringBuffer buf = new StringBuffer(getClass().getName());
2097 if (nodeName != null)
2099 buf.append(nodeName);
2101 if (nodeValue != null)
2103 if (nodeName != null)
2108 buf.append(encode(nodeValue));
2112 return buf.toString();
2115 String encode(String value)
2117 StringBuffer buf = null;
2118 int len = value.length();
2119 for (int i = 0; i < len; i++)
2121 char c = value.charAt(i);
2126 buf = new StringBuffer(value.substring(0, i));
2134 buf = new StringBuffer(value.substring(0, i));
2138 else if (buf != null)
2143 return (buf != null) ? buf.toString() : value;
2146 String nodeTypeToString(short nodeType)
2151 return "ELEMENT_NODE";
2152 case ATTRIBUTE_NODE:
2153 return "ATTRIBUTE_NODE";
2156 case CDATA_SECTION_NODE:
2157 return "CDATA_SECTION_NODE";
2159 return "DOCUMENT_NODE";
2160 case DOCUMENT_TYPE_NODE:
2161 return "DOCUMENT_TYPE_NODE";
2163 return "COMMENT_NODE";
2164 case PROCESSING_INSTRUCTION_NODE:
2165 return "PROCESSING_INSTRUCTION_NODE";
2166 case DOCUMENT_FRAGMENT_NODE:
2167 return "DOCUMENT_FRAGMENT_NODE";
2169 return "ENTITY_NODE";
2170 case ENTITY_REFERENCE_NODE:
2171 return "ENTITY_REFERENCE_NODE";
2173 return "NOTATION_NODE";