diff options
Diffstat (limited to 'external/jaxp/source/gnu/xml/dom/DomNode.java')
-rw-r--r-- | external/jaxp/source/gnu/xml/dom/DomNode.java | 1567 |
1 files changed, 0 insertions, 1567 deletions
diff --git a/external/jaxp/source/gnu/xml/dom/DomNode.java b/external/jaxp/source/gnu/xml/dom/DomNode.java deleted file mode 100644 index dd8e2d764..000000000 --- a/external/jaxp/source/gnu/xml/dom/DomNode.java +++ /dev/null @@ -1,1567 +0,0 @@ -/* - * Copyright (C) 1999-2001 David Brownell - * - * This file is part of GNU JAXP, a library. - * - * GNU JAXP is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * GNU JAXP is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * As a special exception, if you link this library with other files to - * produce an executable, this library does not by itself cause the - * resulting executable to be covered by the GNU General Public License. - * This exception does not however invalidate any other reasons why the - * executable file might be covered by the GNU General Public License. - */ - -package gnu.xml.dom; - -import org.w3c.dom.*; -import org.w3c.dom.events.*; -import org.w3c.dom.traversal.*; - - -/** - * <p> "Node", "EventTarget", and "DocumentEvent" implementation. - * This provides most of the core DOM functionality; only more - * specialized features are provided by subclasses. Those subclasses may - * have some particular constraints they must implement, by overriding - * methods defined here. Such constraints are noted here in the method - * documentation. </p> - * - * <p> Note that you can create events with type names prefixed with "USER-", - * and pass them through this DOM. This lets you use the DOM event scheme - * for application specific purposes, although you must use a predefined event - * structure (such as MutationEvent) to pass data along with those events. - * Test for existence of this feature with the "USER-Events" DOM feature - * name.</p> - * - * <p> Other kinds of events you can send include the "html" events, - * like "load", "unload", "abort", "error", and "blur"; and the mutation - * events. If this DOM has been compiled with mutation event support - * enabled, it will send mutation events when you change parts of the - * tree; otherwise you may create and send such events yourself, but - * they won't be generated by the DOM itself. </p> - * - * <p> Note that there is a namespace-aware name comparison method, - * <em>nameAndTypeEquals</em>, which compares the names (and types) of - * two nodes in conformance with the "Namespaces in XML" specification. - * While mostly intended for use with elements and attributes, this should - * also be helpful for ProcessingInstruction nodes and some others which - * do not have namespace URIs. - * - * @author David Brownell - */ -public abstract class DomNode - implements Node, NodeList, EventTarget, DocumentEvent, Cloneable -{ - // - // CLASS DATA - // - - // tunable - // NKIDS_* affects arrays of children (which grow) - // (currently) fixed size: - // ANCESTORS_* is for event capture/bubbling, # ancestors - // NOTIFICATIONS_* is for per-node event delivery, # events - private static final int NKIDS_INIT = 5; - private static final int NKIDS_DELTA = 8; - private static final int ANCESTORS_INIT = 20; - private static final int NOTIFICATIONS_INIT = 10; - - // tunable: enable mutation events or not? Enabling it costs about - // 10-15% in DOM construction time, last time it was measured. - - // package private !!! - static final boolean reportMutations = true; - - // locking protocol changeable only within this class - private static final Object lockNode = new Object (); - - // optimize space to share what we can - private static final DomNode noKids [] = new DomNode [0]; - - - // NON-FINAL class data - - // Optimize event dispatch by not allocating memory each time - private static boolean dispatchDataLock; - private static DomNode ancestors [] - = new DomNode [ANCESTORS_INIT]; - private static ListenerRecord notificationSet [] - = new ListenerRecord [NOTIFICATIONS_INIT]; - - // Ditto for the (most common) event object itself! - private static boolean eventDataLock; - private static DomEvent.DomMutationEvent mutationEvent - = new DomEvent.DomMutationEvent (null); - - // - // PER-INSTANCE DATA - // - - private Document owner; - private DomNode parent; - - // Bleech ... "package private" so a builder can populate entity refs. - // writable during construction. DOM spec is nasty. - boolean readonly; - - // children - private DomNode children []; - private int length; - - // event registrations - private ListenerRecord listeners []; - private int nListeners; - - // Optimize access to siblings by caching indices. - private transient int parentIndex; - - // - // Some of the methods here are declared 'final' because - // knowledge about their implementation is built into this - // class -- for both integrity and performance. - // - - // package private - void nyi () - { - throw new DomEx (DomEx.NOT_SUPPORTED_ERR, - "feature not yet implemented", this, 0); - } - - /** - * Reduces space utilization for this node. - */ - public void compact () - { - if (children != null && children != noKids) { - if (length == 0) - children = noKids; - // allow a bit of fuzz (max NKIDS_DELTA). - // the JVM won't always use less memory for smaller arrays... - else if ((children.length - length) > 1) { - DomNode newKids [] = new DomNode [length]; - System.arraycopy (children, 0, newKids, 0, length); - children = newKids; - } - } - if (listeners != null && listeners.length != nListeners) { - if (nListeners == 0) - listeners = null; - else { - ListenerRecord l [] = new ListenerRecord [nListeners]; - System.arraycopy (listeners, 0, l, 0, nListeners); - listeners = l; - } - } - } - - /** - * Constructs a node and associates it with its owner. Only - * Document and DocumentType nodes may be created with no owner, - * and DocumentType nodes get an owner as soon as they are - * associated with a document. - */ - protected DomNode (Document owner) - { - short type = getNodeType (); - - if (owner == null) { - // DOM calls never go down this path - if (type != DOCUMENT_NODE && type != DOCUMENT_TYPE_NODE) - throw new IllegalArgumentException ("no owner!"); - } - this.owner = owner; - - switch (type) { - case DOCUMENT_NODE: - case DOCUMENT_FRAGMENT_NODE: - case ENTITY_REFERENCE_NODE: - case ELEMENT_NODE: - children = new DomNode [NKIDS_INIT]; - break; - // no sane app wants the attributes-with-children model - case ATTRIBUTE_NODE: - children = new DomNode [1]; - break; - // we don't currently build children with entities - case ENTITY_NODE: - children = noKids; - - // no other kinds of nodes may have children; so for - // such nodes, length stays zero, children stays null - } - } - - - /** - * <b>DOM L1</b> - * Returns null; Element subclasses must override this method. - */ - public NamedNodeMap getAttributes () - { return null; } - - /** - * <b>DOM L2></b> - * Returns true iff this is an element node with attributes. - */ - public boolean hasAttributes () - { return false; } - - /** - * <b>DOM L1</b> - * Returns a list, possibly empty, of the children of this node. - * In this implementation, to conserve memory, nodes are the same - * as their list of children. This can have ramifications for - * subclasses, which may need to provide their own getLength method - * for reasons unrelated to the NodeList method of the same name. - */ - public NodeList getChildNodes () - { return this; } - - - /** - * <b>DOM L1</b> - * Returns the first child of this node, or null if there are none. - */ - final public Node getFirstChild () - { return item (0); } - - - /** - * <b>DOM L1</b> - * Returns the last child of this node, or null if there are none. - */ - final public Node getLastChild () - { return item (length - 1); } - - - /** - * <b>DOM L1</b> - * Returns true if this node has children. - */ - final public boolean hasChildNodes () - { return length > 0; } - - - /** - * Exposes the internal "readonly" flag. In DOM, children of - * entities and entity references are readonly, as are the - * objects associated with DocumentType objets. - */ - final public boolean isReadonly () - { - return readonly; - } - - - /** - * Sets the internal "readonly" flag so this subtree can't be changed. - * Subclasses need to override this method for any associated content - * that's not a child node, such as an element's attributes or the - * (few) declarations associated with a DocumentType. - */ - public void makeReadonly () - { - readonly = true; - - for (int i = 0; i < length; i++) - children [i].makeReadonly (); - } - - - // we need to have at least N more kids - private void ensureEnough (int n) - { - if ((children.length - length) > n) - return; - - // don't grow in micro-chunks - if (n < NKIDS_DELTA) - n = NKIDS_DELTA; - n += children.length; - - DomNode newKids [] = new DomNode [n]; - - for (int i = 0; i < length; i++) - newKids [i] = children [i]; - children = newKids; - } - - // just checks the node for inclusion -- may be called many - // times (docfrag) before anything is allowed to change - private void checkMisc (DomNode child) - { - if (readonly) - throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR, - null, this, 0); - if (children == null) - throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR, - null, this, 0); - if (parent != null && child.length > 0) { - for (Node temp = parent; - temp != null; - temp = temp.getParentNode ()) { - if (child == parent) - throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR, - "can't make ancestor into a child", this, 0); - } - } - - Node myOwner = owner; - Node newOwner = child.owner; - short nodeType = getNodeType (); - short newType = child.getNodeType (); - - if (nodeType == DOCUMENT_NODE) - myOwner = this; - - if (newOwner != myOwner) { - // new in DOM L2, this case -- patch it up later, in reparent() - if (!(newType == DOCUMENT_TYPE_NODE && newOwner == null)) - throw new DomEx (DomEx.WRONG_DOCUMENT_ERR, - null, child, 0); - } - - // Test code. - // System.out.println("DOMNode node type = " + nodeType - // + " new type: " + newType); - - // if (newType == 3) - // System.out.println("child content = " + child.getNodeValue()); - - // enforce various structural constraints - switch (nodeType) { - case DOCUMENT_NODE: - if (newType == ELEMENT_NODE - || newType == PROCESSING_INSTRUCTION_NODE - || newType == COMMENT_NODE - || newType == DOCUMENT_TYPE_NODE) - return; - break; - - case ATTRIBUTE_NODE: - if (newType == TEXT_NODE || newType == ENTITY_REFERENCE_NODE) - return; - break; - - case DOCUMENT_FRAGMENT_NODE: - case ENTITY_REFERENCE_NODE: - case ELEMENT_NODE: - case ENTITY_NODE: - if (newType == ELEMENT_NODE - || newType == TEXT_NODE - || newType == COMMENT_NODE - || newType == PROCESSING_INSTRUCTION_NODE - || newType == CDATA_SECTION_NODE - || newType == ENTITY_REFERENCE_NODE) - return; - } - throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR, - "this node can't have that type of child", this, 0); - } - - // - // NOTE: after this method, the new child knows its parent, - // but the parent doesn't know the child. Don't let that - // intermediate state be seen by the application. - // - // XXX prefer to pass in a mutation event object, making removeChild reuse - // it appropriately. That'll shorten critical paths, and remove the - // guarantee that the three-message replaceChild case will hit the heap. - // - private void reparent (DomNode newChild) - { - short childType = newChild.getNodeType (); - - if (getNodeType () == DOCUMENT_NODE - && childType == DOCUMENT_TYPE_NODE) { - DomDoctype doctype = (DomDoctype) newChild; - - if (doctype.getImplementation () - != ((Document)this).getImplementation ()) - throw new DomEx (DomEx.WRONG_DOCUMENT_ERR, - "implementation mismatch", newChild, 0); - newChild.owner = (Document) this; - } - - // get rid of old parent - Node oldParent = newChild.parent; - - if (oldParent != null) - oldParent.removeChild (newChild); - - if (childType != ATTRIBUTE_NODE) - newChild.parent = this; - } - - - // Here's hoping a good optimizer will detect the case when the - // next several methods are never called, and won't allocate - // object code space of any kind. (Case: not reporting any - // mutation events. We can also remove some static variables - // listed above.) - - - private void insertionEvent ( - DomEvent.DomMutationEvent event, - DomNode target - ) { - boolean doFree = false; - - if (event == null) { - event = getMutationEvent (); - if (event != null) - doFree = true; - else - event = new DomEvent.DomMutationEvent (null); - } - event.initMutationEvent ("DOMNodeInserted", - true /* bubbles */, false /* nocancel */, - this /* related */, null, null, null, (short) 0); - target.dispatchEvent (event); - - // XXX should really visit every descendant of 'target' - // and sent a DOMNodeInsertedIntoDocument event to it... - // bleech, there's no way to keep that acceptably fast. - - if (doFree) { - event.target = null; - event.relatedNode = null; - event.currentNode = null; - eventDataLock = false; - } // else we created work for the GC - } - - - private void removalEvent ( - DomEvent.DomMutationEvent event, - DomNode target - ) { - boolean doFree = false; - - if (event == null) { - event = getMutationEvent (); - if (event != null) - doFree = true; - else - event = new DomEvent.DomMutationEvent (null); - } - event.initMutationEvent ("DOMNodeRemoved", - true /* bubbles */, false /* nocancel */, - this /* related */, null, null, null, (short) 0); - target.dispatchEvent (event); - - // XXX should really visit every descendant of 'target' - // and sent a DOMNodeRemovedFromDocument event to it... - // bleech, there's no way to keep that acceptably fast. - - event.target = null; - event.relatedNode = null; - event.currentNode = null; - if (doFree) - eventDataLock = false; - // else we created more work for the GC - } - - // - // Avoid creating lots of memory management work, by using a simple - // allocation strategy for the mutation event objects that get used - // at least once per tree modification. We can't use stack allocation, - // so we do the next simplest thing -- more or less, static allocation. - // Concurrent notifications should be rare, anyway. - // - // Returns the preallocated object, which needs to be carefully freed, - // or null to indicate the caller needs to allocate their own. - // - static private DomEvent.DomMutationEvent getMutationEvent () - { - synchronized (lockNode) { - if (eventDataLock) - return null; - eventDataLock = true; - return mutationEvent; - } - } - - // NOTE: this is manually inlined in the insertion - // and removal event methods above; change in sync. - static private void freeMutationEvent () - { - // clear fields to enable GC - mutationEvent.clear (); - eventDataLock = false; - } - - - /** - * <b>DOM L1</b> - * Appends the specified node to this node's list of children. - * Document subclasses must override this to enforce the restrictions - * that there be only one element and document type child. - * - * <p> Causes a DOMNodeInserted mutation event to be reported. - * Will first cause a DOMNodeRemoved event to be reported if the - * parameter already has a parent. If the new child is a document - * fragment node, both events will be reported for each child of - * the fragment; the order in which children are removed and - * inserted is implementation-specific. - * - * <p> If this DOM has been compiled without mutation event support, - * these events will not be reported. - */ - public Node appendChild (Node newChild) - { - try { - DomNode child = (DomNode) newChild; - - if (newChild.getNodeType () != DOCUMENT_FRAGMENT_NODE) { - checkMisc (child); - if (!(length < children.length)) - ensureEnough (1); - reparent (child); - children [length++] = child; - if (reportMutations) - insertionEvent (null, child); - } else { - // See if we can append all the nodes in the fragment - for (int i = 0; i < child.length; i++) - checkMisc (child.children [i]); - - // yep -- do so! - ensureEnough (child.length); - for (int i = 0; i <= child.length; i++) - appendChild (child.children [0]); - } - return child; - - } catch (ClassCastException e) { - throw new DomEx (DomEx.WRONG_DOCUMENT_ERR, - null, newChild, 0); - } - } - - - /** - * <b>DOM L1</b> - * Inserts the specified node in this node's list of children. - * Document subclasses must override this to enforce the restrictions - * that there be only one element and document type child. - * - * <p> Causes a DOMNodeInserted mutation event to be reported. Will - * first cause a DOMNodeRemoved event to be reported if the newChild - * parameter already has a parent. If the new child is a document - * fragment node, both events will be reported for each child of - * the fragment; the order in which children are removed and inserted - * is implementation-specific. - * - * <p> If this DOM has been compiled without mutation event support, - * these events will not be reported. - */ - public Node insertBefore (Node newChild, Node refChild) - { - if (refChild == null) - return appendChild (newChild); - - try { - DomNode child = (DomNode) newChild; - - if (newChild.getNodeType () != DOCUMENT_FRAGMENT_NODE) { - checkMisc (child); - for (int i = 0; i < length; i++) { - if (children [i] != refChild) - continue; - - ensureEnough (1); - reparent (child); - if (children [i] != refChild) - i--; - for (int j = ++length; j > i; j--) - children [j] = children [j - 1]; - children [i] = child; - if (reportMutations) - insertionEvent (null, child); - - return newChild; - } - throw new DomEx (DomEx.NOT_FOUND_ERR, - "that's no child of mine", refChild, 0); - - } else { - // See if we can insert all the nodes in the fragment - for (int i = 0; i < child.length; i++) - checkMisc (child.children [i]); - - // yep -- do so! - ensureEnough (child.length); - for (int i = 0; i <= child.length; i++) - insertBefore (child.children [0], refChild); - return newChild; - } - } catch (ClassCastException e) { - throw new DomEx (DomEx.WRONG_DOCUMENT_ERR, - null, newChild, 0); - } - } - - - /** - * <b>DOM L1</b> - * Replaces the specified node in this node's list of children. - * Document subclasses must override this to test the restrictions - * that there be only one element and document type child. - * - * <p> Causes DOMNodeRemoved and DOMNodeInserted mutation event to be - * reported. Will cause another DOMNodeRemoved event to be reported if - * the newChild parameter already has a parent. These events may be - * delivered in any order, except that the event reporting removal - * from such an existing parent will always be delivered before the - * event reporting its re-insertion as a child of some other node. - * The order in which children are removed and inserted is implementation - * specific. - * - * <p> If your application needs to depend on the in which those removal - * and insertion events are delivered, don't use this API. Instead, - * invoke the removeChild and insertBefore methods directly, to guarantee - * a specific delivery order. Similarly, don't use document fragments, - * Otherwise your application code may not work on a DOM which implements - * this method differently. - * - * <p> If this DOM has been compiled without mutation event support, - * these events will not be reported. - */ - public Node replaceChild (Node newChild, Node refChild) - { - try { - DomNode child = (DomNode) newChild; - - if (newChild.getNodeType () != DOCUMENT_FRAGMENT_NODE) { - checkMisc (child); - for (int i = 0; i < length; i++) { - if (children [i] != refChild) - continue; - - DomNode rmchild = (DomNode) refChild; - DomEvent.DomMutationEvent event; - boolean doFree; - - event = getMutationEvent (); - if (event != null) - doFree = true; - else - doFree = false; - if (reportMutations) - removalEvent (event, rmchild); - reparent (child); - if (children [i] != refChild) - i--; - children [i] = child; - rmchild.parent = null; - if (reportMutations) - insertionEvent (event, child); - if (doFree) - freeMutationEvent (); - - return refChild; - } - throw new DomEx (DomEx.NOT_FOUND_ERR, - "that's no child of mine", newChild, 0); - } else { -// XXX implement me - throw new DomEx (DomEx.NOT_SUPPORTED_ERR, - "replacing with fragment, NYI", null, 0); - } - } catch (ClassCastException e) { - throw new DomEx (DomEx.WRONG_DOCUMENT_ERR, - null, newChild, 0); - } - } - - - /** - * <b>DOM L1</b> - * Removes the specified child from this node's list of children, - * or else reports an exception. - * - * <p> Causes a DOMNodeRemoved mutation event to be reported. - * - * <p> If this DOM has been compiled without mutation event support, - * these events will not be reported. - */ - public Node removeChild (Node refChild) - { - if (readonly) - throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR, - null, this, 0); - for (int i = 0; i < length; i++) { - if (children [i] != refChild) - continue; - - DomNode child = (DomNode) refChild; - - if (reportMutations) - removalEvent (null, child); - for (int j = i + 1; j < length; j++, i++) - children [i] = children [j]; - children [i] = null; - child.parent = null; - length--; - - return refChild; - } - throw new DomEx (DomEx.NOT_FOUND_ERR, - "that's no child of mine", refChild, 0); - } - - - /** - * <b>DOM L1 (NodeList)</b> - * Returns the item with the specified index in this NodeList, - * else null. - */ - final public Node item (int index) - { - try { - if (index < length) - return children [index]; - } catch (RuntimeException e) { - // children == null or index < 0 ... bad parameter - // FALLTHROUGh - } - return null; - } - - - /** - * <b>DOM L1 (NodeList)</b> - * Returns the number of elements in this NodeList. - * (Note that many interfaces have a "Length" property, not just - * NodeList, and if a node subtype must implement one of those, - * it will also need to override getChildNodes.) - */ - public int getLength () - { return length; } - - - /** - * Minimize extra space consumed by this node to hold children and event - * listeners. - */ - public void trimToSize () - { - if (children != null && children.length != length) { - DomNode newKids [] = new DomNode [length]; - - for (int i = 0; i < length; i++) - newKids [i] = children [i]; - children = newKids; - } - - if (listeners != null && listeners.length != nListeners) { - ListenerRecord newKids [] = new ListenerRecord [length]; - - for (int i = 0; i < nListeners; i++) - newKids [i] = listeners [i]; - listeners = newKids; - } - } - - - /** - * <b>DOM L1</b> - * Returns the previous sibling, if one is known. - */ - final public Node getNextSibling () - { - if (parent == null || getNodeType() == ATTRIBUTE_NODE) - return null; - - // we know parent.getChildNodes () returns itself - // ... and that we're somewhere in parent.children[] - int index; - - if (parentIndex < parent.length - && parent.children [parentIndex] == this) { - index = parentIndex + 1; - if (index < parent.length) - return parent.children [index]; - else - return null; - } - - for (index = 0; index < parent.length; index++) { - if (parent.children [index] == this) { - parentIndex = index++; - if (index < parent.length) - return parent.children [index]; - else - break; - } - } - return null; - } - - - /** - * <b>DOM L1</b> - * Returns the previous sibling, if one is known. - */ - final public Node getPreviousSibling () - { - if (parent == null || getNodeType () == ATTRIBUTE_NODE) - return null; - - NodeList siblings = parent.getChildNodes (); - int len = siblings.getLength (); - - if (siblings.item (parentIndex) == this) - return siblings.item (parentIndex - 1); - - for (int i = 0; i < len; i++) - if (siblings.item (i) == this) { - parentIndex = i; - return siblings.item (--i); - } - return null; - } - - - /** - * <b>DOM L1</b> - * Returns the parent node, if one is known. - */ - final public Node getParentNode () - { return parent; } - - // parent node is only set in reparent() after sanity chex - - - /** - * <b>DOM L2</b> - * Consults the DOM implementation to determine if the requested - * feature is supported. DocumentType subclasses must override - * this method, and associate themselves directly with the - * DOMImplementation node used. (This method relies on being able - * to access the DOMImplementation from the owner document, but - * DocumentType nodes can be created without an owner.) - */ - public boolean isSupported (String feature, String version) - { - Document doc = owner; - DOMImplementation impl = null; - - if (doc == null && getNodeType () == DOCUMENT_NODE) - doc = (Document) this; - - if (doc == null) - // possible for DocumentType - throw new IllegalStateException ("unbound ownerDocument"); - - impl = doc.getImplementation (); - return impl.hasFeature (feature, version); - } - - - /** - * <b>DOM L1 (modified in L2)</b> - * Returns the owner document. This is only null for Document nodes, - * and (new in L2) for DocumentType nodes which have not yet been - * associated with the rest of their document. - */ - final public Document getOwnerDocument () - { return owner; } - - - /** - * <b>DOM L1</b> - * Does nothing; this must be overridden (along with the - * getNodeValue method) for nodes with a non-null defined value. - */ - public void setNodeValue (String value) - { } - - - /** - * <b>DOM L1</b> - * Returns null; this must be overridden for nodes types with - * a defined value, along with the setNodeValue method. - */ - public String getNodeValue () - { return null; } - - - /** This forces GCJ compatibility. - * Without this method GCJ is unable to compile to byte code. - */ - public abstract short getNodeType (); - - /** This forces GCJ compatibility. - * Without this method GCJ seems unable to natively compile GNUJAXP. - */ - public abstract String getNodeName (); - - /** - * <b>DOM L2</b> - * Does nothing; this must be overridden (along with the - * getPrefix method) for element and attribute nodes. - */ - public void setPrefix (String prefix) - { } - - - /** - * <b>DOM L2</b> - * Returns null; this must be overridden for element and - * attribute nodes. - */ - public String getPrefix () - { return null; } - - - /** - * <b>DOM L2</b> - * Returns null; this must be overridden for element and - * attribute nodes. - */ - public String getNamespaceURI () - { return null; } - - - /** - * <b>DOM L2</b> - * Returns the node name; this must be overridden for element and - * attribute nodes. - */ - public String getLocalName () - { return null; } - - - /** - * <b>DOM L1</b> - * Returns a clone of this node which optionally includes cloned - * versions of child nodes. Clones are always mutable, except for - * entity reference nodes. - */ - public Node cloneNode (boolean deep) - { - DomNode retval = (DomNode) clone (); - - if (deep && children != null) { - DomNode newKids [] = retval.children; - - if (newKids.length < length) - newKids = new DomNode [length]; - for (int i = 0; i < length; i++) - newKids [i] = (DomNode) children [i].cloneNode (true); - retval.children = newKids; - retval.length = length; - - if (getNodeType () == ENTITY_REFERENCE_NODE) - retval.makeReadonly (); - } - return retval; - } - - /** - * Clones this node; roughly equivalent to cloneNode(false). - * Element subclasses must provide a new implementation which - * invokes this method to handle the basics, and then arranges - * to clone any element attributes directly. Attribute subclasses - * must make similar arrangements, ensuring that existing ties to - * elements are broken by cloning. - */ - public Object clone () - { - try { - DomNode retval = (DomNode) super.clone (); - - retval.parent = null; - retval.readonly = false; - if (retval.children != null) { - retval.children = noKids; - retval.length = 0; - } - retval.listeners = null; - retval.nListeners = 0; - return retval; - - } catch (CloneNotSupportedException x) { - throw new Error ("clone didn't work"); - } - } - - - // the elements-by-tagname stuff is needed for both - // elements and documents ... this is in lieu of a - // common base class between Node and NodeNS. - - /** - * <b>DOM L1</b> - * Creates a NodeList giving array-style access to elements with - * the specified name. Access is fastest if indices change by - * small values, and the DOM is not modified. - */ - public NodeList getElementsByTagName (String tag) - { - return new ShadowList (null, tag); - } - - /** - * <b>DOM L2</b> - * Creates a NodeList giving array-style access to elements with - * the specified namespace and local name. Access is fastest if - * indices change by small values, and the DOM is not modified. - */ - public NodeList getElementsByTagNameNS (String namespace, String local) - { - return new ShadowList (namespace, local); - } - - - // - // This shadow class is GC-able even when the live list it shadows - // can't be, because of event registration hookups. Its finalizer - // makes that live list become GC-able. - // - final class ShadowList implements NodeList - { - private LiveNodeList liveList; - - ShadowList (String ns, String local) - { liveList = new LiveNodeList (ns, local); } - - public void finalize () - { - liveList.detach (); - liveList = null; - } - - public Node item (int index) - { return liveList.item (index); } - - public int getLength () - { return liveList.getLength (); } - } - - - final class LiveNodeList implements NodeList, EventListener, NodeFilter - { - private String elementURI; - private String elementName; - - private DomIterator current; - private int lastIndex; - - - LiveNodeList (String uri, String name) - { - elementURI = uri; - elementName = name; - DomNode.this.addEventListener ("DOMNodeInserted", this, true); - DomNode.this.addEventListener ("DOMNodeRemoved", this, true); - } - - void detach () - { - current.detach (); - current = null; - DomNode.this.removeEventListener ("DOMNodeInserted", this, true); - DomNode.this.removeEventListener ("DOMNodeRemoved", this, true); - } - - public short acceptNode (Node element) - { - if (element == DomNode.this) - return FILTER_SKIP; - - // use namespace-aware matching ... - if (elementURI != null) { - if (!("*".equals (elementURI) - || elementURI.equals (element.getNamespaceURI ()))) - return FILTER_SKIP; - if (!("*".equals (elementName) - || elementName.equals (element.getLocalName ()))) - return FILTER_SKIP; - - // ... or qName-based kind. - } else { - if (!("*".equals (elementName) - || elementName.equals (element.getNodeName ()))) - return FILTER_SKIP; - } - return FILTER_ACCEPT; - } - - private DomIterator createIterator () - { - return new DomIterator (DomNode.this, - NodeFilter.SHOW_ELEMENT, - this, /* filter */ - true /* expand entity refs */ - ); - } - - public void handleEvent (Event e) - { - MutationEvent mutation = (MutationEvent) e; - Node related = mutation.getRelatedNode (); - - // XXX if it's got children ... check all kids too, they - // will invalidate our saved index - - if (related.getNodeType () != Node.ELEMENT_NODE) - return; - if (related.getNodeName () != elementName) - return; - if (related.getNamespaceURI () != elementURI) - return; - - current = null; - } - - public Node item (int index) - { - if (current == null) { - current = createIterator (); - lastIndex = -1; - } - - // last node or before? go backwards - if (index <= lastIndex) { - while (index != lastIndex) { - current.previousNode (); - lastIndex--; - } - return current.previousNode (); - } - - // somewhere after last node - while (++lastIndex != index) - current.nextNode (); - return current.nextNode (); - } - - public int getLength () - { - int retval = 0; - NodeIterator iter = createIterator (); - - while (iter.nextNode () != null) - retval++; - return retval; - } - } - - // - // EventTarget support - // - static final class ListenerRecord { - String type; - EventListener listener; - boolean useCapture; - - // XXX use JDK 1.2 java.lang.ref.WeakReference to listener, - // and we can both get rid of "shadow" classes and remove - // the need for applications to apply similar trix ... but - // JDK 1.2 support isn't generally available yet - - ListenerRecord ( - String type, - EventListener listener, - boolean useCapture - ) { - this.type = type.intern (); - this.listener = listener; - this.useCapture = useCapture; - } - - boolean equals (ListenerRecord rec) - { - return listener == rec.listener - && useCapture == rec.useCapture - && type == rec.type; - } - } - - /** - * <b>DOM L2 (Events)</b> - * Returns an instance of the specified type of event object. - * Understands about DOM Mutation, HTML, and UI events. - * - * <p>If the name of the event type begins with "USER-", then an object - * implementing the "Event" class will be returned; this provides a - * limited facility for application-defined events to use the DOM event - * infrastructure. Alternatively, use one of the standard DOM event - * classes and initialize it using use such a "USER-" event type name; - * or defin, instantiate, and initialize an application-specific subclass - * of DomEvent and pass that to dispatchEvent(). - * - * @param eventType Identifies the particular DOM feature module - * defining the type of event, such as "MutationEvents". - * <em>The event "name" is a different kind of "type".</em> - */ - public Event createEvent (String eventType) - { - eventType = eventType.toLowerCase (); - - if ("mutationevents".equals (eventType)) - return new DomEvent.DomMutationEvent (null); - - if ("htmlevents".equals (eventType) - || "events".equals (eventType) - || "user-events".equals (eventType)) - return new DomEvent (null); - - if ("uievents".equals (eventType)) - return new DomEvent.DomUIEvent (null); - - // mouse events - - throw new DomEx (DomEx.NOT_SUPPORTED_ERR, - eventType, null, 0); - } - - - /** - * <b>DOM L2 (Events)</b> - * Registers an event listener's interest in a class of events. - */ - final public void addEventListener ( - String type, - EventListener listener, - boolean useCapture - ) { - if (listeners == null) - listeners = new ListenerRecord [1]; - else if (nListeners == listeners.length) { - ListenerRecord newListeners []; - newListeners = - new ListenerRecord [listeners.length + NKIDS_DELTA]; - for (int i = 0; i < nListeners; i++) - newListeners [i] = listeners [i]; - listeners = newListeners; - } - - // prune duplicates - ListenerRecord record; - - record = new ListenerRecord (type, listener, useCapture); - for (int i = 0; i < nListeners; i++) { - if (record.equals (listeners [i])) - return; - } - listeners [nListeners++] = record; - } - - - // XXX this exception should be discarded from DOM - - // this class can be instantiated, unlike the one in the spec - final static class DomEventException extends EventException { - DomEventException () - { super (UNSPECIFIED_EVENT_TYPE_ERR, "unspecified event type"); } - } - - - /** - * <b>DOM L2 (Events)</b> - * Delivers an event to all relevant listeners, returning true if the - * caller should perform their default action. Note that the event - * must have been provided by the createEvent() method on this - * class, else it can't be dispatched. - * - * @see #createEvent - * - * @exception NullPointerException When a null event is passed. - * @exception ClassCastException When the event wasn't provided by - * the createEvent method, or otherwise isn't a DomEvent. - * @exception EventException If the event type wasn't specified - */ - final public boolean dispatchEvent (Event event) - throws EventException - { - DomEvent e = (DomEvent) event; - DomNode ancestors [] = null; - int ancestorMax = 0; - boolean haveDispatchDataLock = false; - - if (e.type == null) - throw new DomEventException (); - - e.doDefault = true; - e.target = this; - - // - // Typical case: one nonrecursive dispatchEvent call at a time - // for this class. If that's our case, we can avoid allocating - // garbage, which is overall a big win. Even with advanced GCs - // that deal well with short-lived garbage, and wayfast allocators, - // it still helps. - // - // Remember -- EVERY mutation goes though here at least once. - // - // When populating a DOM tree, trying to send mutation events is - // the primary cost; this dominates the critical path. - // - try { - DomNode current; - int index; - boolean haveAncestorRegistrations = false; - ListenerRecord notificationSet []; - int ancestorLen; - - synchronized (lockNode) { - if (!dispatchDataLock) { - haveDispatchDataLock = dispatchDataLock = true; - notificationSet = DomNode.notificationSet; - ancestors = DomNode.ancestors; - } else { - notificationSet = new ListenerRecord [NOTIFICATIONS_INIT]; - ancestors = new DomNode [ANCESTORS_INIT]; - } - ancestorLen = ancestors.length; - } - - // XXX autogrow ancestors ... based on statistics - - // Climb to the top of this subtree and handle capture, letting - // each node (from the top down) capture until one stops it or - // until we get to this one. - - for (index = 0, current = parent; - current != null && index < ancestorLen; - index++, current = current.parent) { - if (current.nListeners != 0) - haveAncestorRegistrations = true; - ancestors [index] = current; - } - if (current != null) - throw new RuntimeException ("dispatchEvent capture stack size"); - - ancestorMax = index; - e.stop = false; - - if (haveAncestorRegistrations) { - e.eventPhase = Event.CAPTURING_PHASE; - while (!e.stop && index-- > 0) { - current = ancestors [index]; - if (current.nListeners != 0) - notifyNode (e, current, true, notificationSet); - } - } - - // Always deliver events to the target node (this) - // unless stopPropagation was called. If we saw - // no registrations yet (typical!), we never will. - if (!e.stop && nListeners != 0) { - e.eventPhase = Event.AT_TARGET; - notifyNode (e, this, false, notificationSet); - } else if (!haveAncestorRegistrations) - e.stop = true; - - // If the event bubbles and propagation wasn't halted, - // walk back up the ancestor list. Stop bubbling when - // any bubbled event handler stops it. - - if (!e.stop && e.bubbles) { - e.eventPhase = Event.BUBBLING_PHASE; - for (index = 0; - !e.stop - && index < ancestorMax - && (current = ancestors [index]) != null; - index++) { - if (current.nListeners != 0) - notifyNode (e, current, false, notificationSet); - } - } - e.eventPhase = 0; - - // Caller chooses whether to perform the default - // action based on return from this method. - return e.doDefault; - - } finally { - if (haveDispatchDataLock) { - // synchronize to force write ordering - synchronized (lockNode) { - // null out refs to ensure they'll be GC'd - for (int i = 0; i < ancestorMax; i++) - ancestors [i] = null; - // notificationSet handled by notifyNode - - dispatchDataLock = false; - } - } - } - } - - - private void notifyNode ( - DomEvent e, - DomNode current, - boolean capture, - ListenerRecord notificationSet [] - ) { - int count = 0; - - // do any of this set of listeners get notified? - for (int i = 0; i < current.nListeners; i++) { - ListenerRecord rec = current.listeners [i]; - - if (rec.useCapture != capture) - continue; - if (!e.type.equals (rec.type)) - continue; - if (count < notificationSet.length) - notificationSet [count++] = rec; - else - // XXX fire up some cheap growth algorithm - throw new RuntimeException ( - "Event notification set size exceeded"); - } - - // Notify just those listeners - e.currentNode = current; - for (int i = 0; i < count; i++) { - try { - // Late in the DOM CR process (3rd or 4th CR?) the - // removeEventListener spec became asymmetric with respect - // to addEventListener ... effect is now immediate. - for (int j = 0; j < current.nListeners; j++) { - if (current.listeners [j].equals (notificationSet [i])) { - notificationSet [i].listener.handleEvent (e); - break; - } - } - - } catch (Exception x) { - // ignore all exceptions - } - notificationSet [i] = null; // free for GC - } - } - - /** - * <b>DOM L2 (Events)</b> - * Unregisters an event listener. - */ - final public void removeEventListener ( - String type, - EventListener listener, - boolean useCapture - ) { - for (int i = 0; i < nListeners; i++) { - if (listeners [i].listener != listener) - continue; - if (listeners [i].useCapture != useCapture) - continue; - if (!listeners [i].type.equals (type)) - continue; - - if (nListeners == 1) { - listeners = null; - nListeners = 0; - } else { - for (int j = i + 1; j < nListeners; j++) - listeners [i++] = listeners [j++]; - listeners [--nListeners] = null; - } - break; - } - // no exceptions reported - } - - - /** - * <b>DOM L1 (relocated in DOM L2)</b> - * In this node and all contained nodes (including attributes if - * relevant) merge adjacent text nodes. This is done while ignoring - * text which happens to use CDATA delimiters). - */ - public void normalize () - { - int index = 0; - Node child, next; - Text temp; - NamedNodeMap attributes; - - while ((child = item (index)) != null) { - switch (child.getNodeType ()) { - case TEXT_NODE: - next = item (index + 1); - if (next == null || next.getNodeType () != TEXT_NODE) - break; - temp = (Text) child; - temp.appendData (next.getNodeValue ()); - removeChild (next); - // don't increment index ... we do extra fetches - // of the current node, affecting only speed. - continue; - - case ELEMENT_NODE: - child.normalize (); - attributes = child.getAttributes (); - for (int i = 0; i < attributes.getLength (); i++) - attributes.item (i).normalize (); - // FALLTHROUGH - } - index++; - continue; - } - } - - - /** - * Returns true iff node types match, and either (a) both nodes have no - * namespace and their getNodeName() values are the same, or (b) both - * nodes have the same getNamespaceURI() and same getLocalName() values. - * - * <p>Note that notion of a "Per-Element-Type" attribute name scope, as - * found in a non-normative appendix of the XML Namespaces specification, - * is not supported here. Your application must implement that notion, - * typically by not bothering to check nameAndTypeEquals for attributes - * without namespace URIs unless you already know their elements are - * nameAndTypeEquals. - */ - public boolean nameAndTypeEquals (Node other) - { - // node types must match - if (getNodeType () != other.getNodeType ()) - return false; - - // if both have namespaces, do a "full" comparision - // this is a "global" partition - String ns1 = this.getNamespaceURI (); - String ns2 = other.getNamespaceURI (); - - if (ns1 != null && ns2 != null) - return ns1.equals (ns2) - && getLocalName ().equals (other.getLocalName ()); - - // if neither has a namespace, this is a "no-namespace" name. - if (ns1 == null && ns2 == null) { - if (getNodeName().equals (other.getNodeName ()) == false) - return false; - // can test the non-normative "per-element-type" scope here. - // if this is an attribute node and both nodes have been bound - // to elements (!!), then return the nameAndTypeEquals() - // comparison of those elements. - return true; - } - - // otherwise they're unequal: one scoped, one not. - return false; - } -} |