| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * |
| * Copyright (c) 1999 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, |
| * if any, must include the following acknowledgment: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "Xerces" and "Apache Software Foundation" must |
| * not be used to endorse or promote products derived from this |
| * software without prior written permission. For written |
| * permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", |
| * nor may "Apache" appear in their name, without prior written |
| * permission of the Apache Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation and was |
| * originally based on software copyright (c) 1999, International |
| * Business Machines, Inc., http://www.apache.org. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| |
| package org.apache.xerces.dom; |
| |
| import java.io.*; |
| import java.util.Vector; |
| |
| import org.w3c.dom.*; |
| |
| //import org.apache.xerces.domx.events.*; |
| import org.apache.xerces.dom.events.EventImpl; |
| import org.apache.xerces.dom.events.MutationEventImpl; |
| import org.w3c.dom.events.*; |
| |
| /** |
| * Node provides the basic structure of a DOM tree. It is never used |
| * directly, but instead is subclassed to add type and data |
| * information, and additional methods, appropriate to each node of |
| * the tree. Only its subclasses should be instantiated -- and those, |
| * with the exception of Document itself, only through a specific |
| * Document's factory methods. |
| * <P> |
| * The Node interface provides shared behaviors such as siblings and |
| * children, both for consistancy and so that the most common tree |
| * operations may be performed without constantly having to downcast |
| * to specific node types. When there is no obvious mapping for one of |
| * these queries, it will respond with null. |
| * Note that the default behavior is that children are forbidden. To |
| * permit them, the subclass ParentNode overrides several methods. |
| * <P> |
| * NodeImpl also implements NodeList, so it can return itself in |
| * response to the getChildNodes() query. This eliminiates the need |
| * for a separate ChildNodeList object. Note that this is an |
| * IMPLEMENTATION DETAIL; applications should _never_ assume that |
| * this identity exists. |
| * <P> |
| * All nodes in a single document must originate |
| * in that document. (Note that this is much tighter than "must be |
| * same implementation") Nodes are all aware of their ownerDocument, |
| * and attempts to mismatch will throw WRONG_DOCUMENT_ERR. |
| * <P> |
| * However, to save memory not all nodes always have a direct reference |
| * to their ownerDocument. When a node is owned by another node it relies |
| * on its owner to store its ownerDocument. Parent nodes always store it |
| * though, so there is never more than one level of indirection. |
| * And when a node doesn't have an owner, ownerNode refers to its |
| * ownerDocument. |
| * |
| * @version |
| * @since PR-DOM-Level-1-19980818. |
| */ |
| public abstract class NodeImpl |
| implements Node, NodeList, EventTarget, Cloneable, Serializable { |
| |
| // |
| // Constants |
| // |
| |
| /** Serialization version. */ |
| static final long serialVersionUID = -6316591992167219696L; |
| |
| // public |
| |
| /** Element definition node type. */ |
| public static final short ELEMENT_DEFINITION_NODE = -1; |
| |
| // |
| // Data |
| // |
| |
| // links |
| |
| protected NodeImpl ownerNode; // typically the parent but not always! |
| |
| // data |
| |
| protected short flags; |
| |
| protected final static short READONLY = 0x1<<0; |
| protected final static short SYNCDATA = 0x1<<1; |
| protected final static short SYNCCHILDREN = 0x1<<2; |
| protected final static short OWNED = 0x1<<3; |
| protected final static short FIRSTCHILD = 0x1<<4; |
| protected final static short SPECIFIED = 0x1<<5; |
| protected final static short IGNORABLEWS = 0x1<<6; |
| protected final static short SETVALUE = 0x1<<7; |
| |
| // |
| // Constructors |
| // |
| |
| /** |
| * No public constructor; only subclasses of Node should be |
| * instantiated, and those normally via a Document's factory methods |
| * <p> |
| * Every Node knows what Document it belongs to. |
| */ |
| protected NodeImpl(DocumentImpl ownerDocument) { |
| // as long as we do not have any owner, ownerNode is our ownerDocument |
| ownerNode = ownerDocument; |
| } // <init>(DocumentImpl) |
| |
| /** Constructor for serialization. */ |
| public NodeImpl() {} |
| |
| // |
| // Node methods |
| // |
| |
| /** |
| * A short integer indicating what type of node this is. The named |
| * constants for this value are defined in the org.w3c.dom.Node interface. |
| */ |
| public abstract short getNodeType(); |
| |
| /** |
| * the name of this node. |
| */ |
| public abstract String getNodeName(); |
| |
| /** |
| * Returns the node value. |
| */ |
| public String getNodeValue() { |
| return null; // overridden in some subclasses |
| } |
| |
| /** |
| * Sets the node value. |
| * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) |
| */ |
| public void setNodeValue(String x) |
| throws DOMException { |
| // Default behavior is to do nothing, overridden in some subclasses |
| } |
| |
| /** |
| * Adds a child node to the end of the list of children for this node. |
| * Convenience shorthand for insertBefore(newChild,null). |
| * @see #insertBefore(Node, Node) |
| * <P> |
| * By default we do not accept any children, ParentNode overrides this. |
| * @see ParentNode |
| * |
| * @returns newChild, in its new state (relocated, or emptied in the |
| * case of DocumentNode.) |
| * |
| * @throws DOMException(HIERARCHY_REQUEST_ERR) if newChild is of a |
| * type that shouldn't be a child of this node. |
| * |
| * @throws DOMException(WRONG_DOCUMENT_ERR) if newChild has a |
| * different owner document than we do. |
| * |
| * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is |
| * read-only. |
| */ |
| public Node appendChild(Node newChild) throws DOMException { |
| return insertBefore(newChild, null); |
| } |
| |
| /** |
| * Returns a duplicate of a given node. You can consider this a |
| * generic "copy constructor" for nodes. The newly returned object should |
| * be completely independent of the source object's subtree, so changes |
| * in one after the clone has been made will not affect the other. |
| * <P> |
| * Note: since we never have any children deep is meaningless here, |
| * ParentNode overrides this behavior. |
| * @see ParentNode |
| * |
| * <p> |
| * Example: Cloning a Text node will copy both the node and the text it |
| * contains. |
| * <p> |
| * Example: Cloning something that has children -- Element or Attr, for |
| * example -- will _not_ clone those children unless a "deep clone" |
| * has been requested. A shallow clone of an Attr node will yield an |
| * empty Attr of the same name. |
| * <p> |
| * NOTE: Clones will always be read/write, even if the node being cloned |
| * is read-only, to permit applications using only the DOM API to obtain |
| * editable copies of locked portions of the tree. |
| */ |
| public Node cloneNode(boolean deep) { |
| |
| if (needsSyncData()) { |
| synchronizeData(); |
| } |
| |
| NodeImpl newnode; |
| try { |
| newnode = (NodeImpl)clone(); |
| } |
| catch (CloneNotSupportedException e) { |
| // Revisit : don't fail silently - but don't want to tie to parser guts |
| // System.out.println("UNEXPECTED "+e); |
| return null; |
| } |
| |
| // Need to break the association w/ original kids |
| newnode.ownerNode = ownerDocument(); |
| newnode.isOwned(false); |
| |
| // REVISIT: What to do when readOnly? -Ac |
| newnode.isReadOnly(false); |
| |
| return newnode; |
| |
| } // cloneNode(boolean):Node |
| |
| /** |
| * Find the Document that this Node belongs to (the document in |
| * whose context the Node was created). The Node may or may not |
| * currently be part of that Document's actual contents. |
| */ |
| public Document getOwnerDocument() { |
| // if we have an owner simply forward the request |
| // otherwise ownerNode is our ownerDocument |
| if (isOwned()) { |
| return ownerNode.ownerDocument(); |
| } else { |
| return (Document) ownerNode; |
| } |
| } |
| |
| /** |
| * same as above but returns internal type and this one is not overridden |
| * by DocumentImpl to return null |
| */ |
| DocumentImpl ownerDocument() { |
| // if we have an owner simply forward the request |
| // otherwise ownerNode is our ownerDocument |
| if (isOwned()) { |
| return ownerNode.ownerDocument(); |
| } else { |
| return (DocumentImpl) ownerNode; |
| } |
| } |
| |
| /** |
| * NON-DOM |
| * set the ownerDocument of this node |
| */ |
| void setOwnerDocument(DocumentImpl doc) { |
| if (needsSyncData()) { |
| synchronizeData(); |
| } |
| // if we have an owner we rely on it to have it right |
| // otherwise ownerNode is our ownerDocument |
| if (!isOwned()) { |
| ownerNode = doc; |
| } |
| } |
| |
| /** |
| * Obtain the DOM-tree parent of this node, or null if it is not |
| * currently active in the DOM tree (perhaps because it has just been |
| * created or removed). Note that Document, DocumentFragment, and |
| * Attribute will never have parents. |
| */ |
| public Node getParentNode() { |
| return null; // overriden by ChildNode |
| } |
| |
| /* |
| * same as above but returns internal type |
| */ |
| NodeImpl parentNode() { |
| return null; |
| } |
| |
| /** The next child of this node's parent, or null if none */ |
| public Node getNextSibling() { |
| return null; // default behavior, overriden in ChildNode |
| } |
| |
| /** The previous child of this node's parent, or null if none */ |
| public Node getPreviousSibling() { |
| return null; // default behavior, overriden in ChildNode |
| } |
| |
| ChildNode previousSibling() { |
| return null; // default behavior, overriden in ChildNode |
| } |
| |
| // overriden in subclasses ParentNode and ChildAndParentNode |
| public void reduceWaste () {} |
| |
| protected int getIndexOf (Node maybeChild) { |
| return -1; // overridden by ParentNode |
| } |
| |
| /** |
| * Return the collection of attributes associated with this node, |
| * or null if none. At this writing, Element is the only type of node |
| * which will ever have attributes. |
| * |
| * @see ElementImpl |
| */ |
| public NamedNodeMap getAttributes() { |
| return null; // overridden in ElementImpl |
| } |
| |
| /** |
| * Returns whether this node (if it is an element) has any attributes. |
| * @return <code>true</code> if this node has any attributes, |
| * <code>false</code> otherwise. |
| * @since DOM Level 2 |
| * @see ElementImpl |
| */ |
| public boolean hasAttributes() { |
| return false; // overridden in ElementImpl |
| } |
| |
| /** |
| * Test whether this node has any children. Convenience shorthand |
| * for (Node.getFirstChild()!=null) |
| * <P> |
| * By default we do not have any children, ParentNode overrides this. |
| * @see ParentNode |
| */ |
| public boolean hasChildNodes() { |
| return false; |
| } |
| |
| /** |
| * Obtain a NodeList enumerating all children of this node. If there |
| * are none, an (initially) empty NodeList is returned. |
| * <p> |
| * NodeLists are "live"; as children are added/removed the NodeList |
| * will immediately reflect those changes. Also, the NodeList refers |
| * to the actual nodes, so changes to those nodes made via the DOM tree |
| * will be reflected in the NodeList and vice versa. |
| * <p> |
| * In this implementation, Nodes implement the NodeList interface and |
| * provide their own getChildNodes() support. Other DOMs may solve this |
| * differently. |
| */ |
| public NodeList getChildNodes() { |
| return this; |
| } |
| |
| /** The first child of this Node, or null if none. |
| * <P> |
| * By default we do not have any children, ParentNode overrides this. |
| * @see ParentNode |
| */ |
| public Node getFirstChild() { |
| return null; |
| } |
| |
| /** The first child of this Node, or null if none. |
| * <P> |
| * By default we do not have any children, ParentNode overrides this. |
| * @see ParentNode |
| */ |
| public Node getLastChild() { |
| return null; |
| } |
| |
| /** |
| * Move one or more node(s) to our list of children. Note that this |
| * implicitly removes them from their previous parent. |
| * <P> |
| * By default we do not accept any children, ParentNode overrides this. |
| * @see ParentNode |
| * |
| * @param newChild The Node to be moved to our subtree. As a |
| * convenience feature, inserting a DocumentNode will instead insert |
| * all its children. |
| * |
| * @param refChild Current child which newChild should be placed |
| * immediately before. If refChild is null, the insertion occurs |
| * after all existing Nodes, like appendChild(). |
| * |
| * @returns newChild, in its new state (relocated, or emptied in the |
| * case of DocumentNode.) |
| * |
| * @throws DOMException(HIERARCHY_REQUEST_ERR) if newChild is of a |
| * type that shouldn't be a child of this node, or if newChild is an |
| * ancestor of this node. |
| * |
| * @throws DOMException(WRONG_DOCUMENT_ERR) if newChild has a |
| * different owner document than we do. |
| * |
| * @throws DOMException(NOT_FOUND_ERR) if refChild is not a child of |
| * this node. |
| * |
| * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is |
| * read-only. |
| */ |
| public Node insertBefore(Node newChild, Node refChild) |
| throws DOMException { |
| throw new DOMExceptionImpl(DOMException.HIERARCHY_REQUEST_ERR, |
| "DOM006 Hierarchy request error"); |
| } |
| |
| /** |
| * Remove a child from this Node. The removed child's subtree |
| * remains intact so it may be re-inserted elsewhere. |
| * <P> |
| * By default we do not have any children, ParentNode overrides this. |
| * @see ParentNode |
| * |
| * @return oldChild, in its new state (removed). |
| * |
| * @throws DOMException(NOT_FOUND_ERR) if oldChild is not a child of |
| * this node. |
| * |
| * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is |
| * read-only. |
| */ |
| public Node removeChild(Node oldChild) |
| throws DOMException { |
| throw new DOMExceptionImpl(DOMException.NOT_FOUND_ERR, |
| "DOM008 Not found"); |
| } |
| |
| /** |
| * Make newChild occupy the location that oldChild used to |
| * have. Note that newChild will first be removed from its previous |
| * parent, if any. Equivalent to inserting newChild before oldChild, |
| * then removing oldChild. |
| * <P> |
| * By default we do not have any children, ParentNode overrides this. |
| * @see ParentNode |
| * |
| * @returns oldChild, in its new state (removed). |
| * |
| * @throws DOMException(HIERARCHY_REQUEST_ERR) if newChild is of a |
| * type that shouldn't be a child of this node, or if newChild is |
| * one of our ancestors. |
| * |
| * @throws DOMException(WRONG_DOCUMENT_ERR) if newChild has a |
| * different owner document than we do. |
| * |
| * @throws DOMException(NOT_FOUND_ERR) if oldChild is not a child of |
| * this node. |
| * |
| * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if this node is |
| * read-only. |
| */ |
| public Node replaceChild(Node newChild, Node oldChild) |
| throws DOMException { |
| throw new DOMExceptionImpl(DOMException.HIERARCHY_REQUEST_ERR, |
| "DOM006 Hierarchy request error"); |
| } |
| |
| // |
| // NodeList methods |
| // |
| |
| /** |
| * NodeList method: Count the immediate children of this node |
| * <P> |
| * By default we do not have any children, ParentNode overrides this. |
| * @see ParentNode |
| * |
| * @return int |
| */ |
| public int getLength() { |
| return 0; |
| } |
| |
| /** |
| * NodeList method: Return the Nth immediate child of this node, or |
| * null if the index is out of bounds. |
| * <P> |
| * By default we do not have any children, ParentNode overrides this. |
| * @see ParentNode |
| * |
| * @return org.w3c.dom.Node |
| * @param Index int |
| */ |
| public Node item(int index) { |
| return null; |
| } |
| |
| // |
| // DOM2: methods, getters, setters |
| // |
| |
| /** |
| * Puts all <code>Text</code> nodes in the full depth of the sub-tree |
| * underneath this <code>Node</code>, including attribute nodes, into a |
| * "normal" form where only markup (e.g., tags, comments, processing |
| * instructions, CDATA sections, and entity references) separates |
| * <code>Text</code> nodes, i.e., there are no adjacent <code>Text</code> |
| * nodes. This can be used to ensure that the DOM view of a document is |
| * the same as if it were saved and re-loaded, and is useful when |
| * operations (such as XPointer lookups) that depend on a particular |
| * document tree structure are to be used.In cases where the document |
| * contains <code>CDATASections</code>, the normalize operation alone may |
| * not be sufficient, since XPointers do not differentiate between |
| * <code>Text</code> nodes and <code>CDATASection</code> nodes. |
| * <p> |
| * Note that this implementation simply calls normalize() on this Node's |
| * children. It is up to implementors or Node to override normalize() |
| * to take action. |
| */ |
| public void normalize() { |
| /* by default we do not have any children, |
| ParentNode overrides this behavior */ |
| } |
| |
| /** |
| * Introduced in DOM Level 2. <p> |
| * Tests whether the DOM implementation implements a specific feature and that |
| * feature is supported by this node. |
| * @param feature The package name of the feature to test. This is the |
| * same name as what can be passed to the method |
| * hasFeature on DOMImplementation. |
| * @param version This is the version number of the package name to |
| * test. In Level 2, version 1, this is the string "2.0". If |
| * the version is not specified, supporting any version of |
| * the feature will cause the method to return true. |
| * @return boolean Returns true if this node defines a subtree within which the |
| * specified feature is supported, false otherwise. |
| * @since WD-DOM-Level-2-19990923 |
| */ |
| public boolean supports(String feature, String version) |
| { |
| return ownerDocument().getImplementation().hasFeature(feature, |
| version); |
| } |
| |
| /** |
| * Introduced in DOM Level 2. <p> |
| * |
| * The namespace URI of this node, or null if it is unspecified. When this node |
| * is of any type other than ELEMENT_NODE and ATTRIBUTE_NODE, this is always |
| * null and setting it has no effect. <p> |
| * |
| * This is not a computed value that is the result of a namespace lookup based on |
| * an examination of the namespace declarations in scope. It is merely the |
| * namespace URI given at creation time.<p> |
| * |
| * For nodes created with a DOM Level 1 method, such as createElement |
| * from the Document interface, this is null. |
| * @since WD-DOM-Level-2-19990923 |
| * @see AttrNSImpl |
| * @see ElementNSImpl |
| */ |
| public String getNamespaceURI() |
| { |
| return null; |
| } |
| |
| /** |
| * Introduced in DOM Level 2. <p> |
| * |
| * The namespace prefix of this node, or null if it is unspecified. When this |
| * node is of any type other than ELEMENT_NODE and ATTRIBUTE_NODE this is |
| * always null and setting it has no effect.<p> |
| * |
| * For nodes created with a DOM Level 1 method, such as createElement |
| * from the Document interface, this is null. <p> |
| * |
| * @since WD-DOM-Level-2-19990923 |
| * @see AttrNSImpl |
| * @see ElementNSImpl |
| */ |
| public String getPrefix() |
| { |
| return null; |
| } |
| |
| /** |
| * Introduced in DOM Level 2. <p> |
| * |
| * The namespace prefix of this node, or null if it is unspecified. When this |
| * node is of any type other than ELEMENT_NODE and ATTRIBUTE_NODE this is |
| * always null and setting it has no effect.<p> |
| * |
| * For nodes created with a DOM Level 1 method, such as createElement |
| * from the Document interface, this is null.<p> |
| * |
| * Note that setting this attribute changes the nodeName attribute, which holds the |
| * qualified name, as well as the tagName and name attributes of the Element |
| * and Attr interfaces, when applicable.<p> |
| * |
| * @throws INVALID_CHARACTER_ERR Raised if the specified |
| * prefix contains an invalid character. |
| * |
| * @since WD-DOM-Level-2-19990923 |
| * @see AttrNSImpl |
| * @see ElementNSImpl |
| */ |
| public void setPrefix(String prefix) |
| throws DOMException |
| { |
| throw new DOMExceptionImpl(DOMException.NAMESPACE_ERR, |
| "DOM003 Namespace error"); |
| } |
| |
| /** |
| * Introduced in DOM Level 2. <p> |
| * |
| * Returns the local part of the qualified name of this node. |
| * For nodes created with a DOM Level 1 method, such as createElement |
| * from the Document interface, and for nodes of any type other than |
| * ELEMENT_NODE and ATTRIBUTE_NODE this is the same as the nodeName |
| * attribute. |
| * @since WD-DOM-Level-2-19990923 |
| * @see AttrNSImpl |
| * @see ElementNSImpl |
| */ |
| public String getLocalName() |
| { |
| return null; |
| } |
| |
| |
| // |
| // EventTarget support (public and internal) |
| // |
| // Constants |
| // |
| /** Compile-time flag. If false, disables our code for |
| the DOM Level 2 Events module, perhaps allowing it |
| to be optimized out to save bytecodes. |
| */ |
| protected final static boolean MUTATIONEVENTS=true; |
| |
| /** The MUTATION_ values are parameters to the NON-DOM |
| internalInsertBefore() and internalRemoveChild() operations, |
| allowing us to control which MutationEvents are generated. |
| */ |
| protected final static int MUTATION_NONE=0x00; |
| protected final static int MUTATION_LOCAL=0x01; |
| protected final static int MUTATION_AGGREGATE=0x02; |
| protected final static int MUTATION_ALL=0xffff; |
| |
| /* NON-DOM INTERNAL: Class LEntry is just a struct used to represent |
| * event listeners registered with this node. Copies of this object |
| * are hung from the nodeListeners Vector. |
| * <p> |
| * I considered using two vectors -- one for capture, |
| * one for bubble -- but decided that since the list of listeners |
| * is probably short in most cases, it might not be worth spending |
| * the space. ***** REVISIT WHEN WE HAVE MORE EXPERIENCE. |
| */ |
| class LEntry |
| { |
| String type; |
| EventListener listener; |
| boolean useCapture; |
| |
| /** NON-DOM INTERNAL: Constructor for Listener list Entry |
| * @param type Event name (NOT event group!) to listen for. |
| * @param listener Who gets called when event is dispatched |
| * @param useCaptue True iff listener is registered on |
| * capturing phase rather than at-target or bubbling |
| */ |
| LEntry(String type,EventListener listener,boolean useCapture) |
| { |
| this.type=type;this.listener=listener;this.useCapture=useCapture; |
| } |
| }; // LEntry |
| |
| /** Introduced in DOM Level 2. <p> |
| * Register an event listener with this Node. A listener may be independently |
| * registered as both Capturing and Bubbling, but may only be |
| * registered once per role; redundant registrations are ignored. |
| * @param type Event name (NOT event group!) to listen for. |
| * @param listener Who gets called when event is dispatched |
| * @param useCapture True iff listener is registered on |
| * capturing phase rather than at-target or bubbling |
| */ |
| public void addEventListener(String type,EventListener listener,boolean useCapture) |
| { |
| // We can't dispatch to blank type-name, and of course we need |
| // a listener to dispatch to |
| if(type==null || type.equals("") || listener==null) |
| return; |
| |
| // Each listener may be registered only once per type per phase. |
| // Simplest way to code that is to zap the previous entry, if any. |
| removeEventListener(type,listener,useCapture); |
| |
| Vector nodeListeners = ownerDocument().getEventListeners(this); |
| if(nodeListeners==null) { |
| nodeListeners=new Vector(); |
| ownerDocument().setEventListeners(this, nodeListeners); |
| } |
| nodeListeners.addElement(new LEntry(type,listener,useCapture)); |
| |
| // Record active listener |
| LCount lc=LCount.lookup(type); |
| if(useCapture) |
| ++lc.captures; |
| else |
| ++lc.bubbles; |
| |
| } // addEventListener(String,EventListener,boolean) :void |
| |
| /** Introduced in DOM Level 2. <p> |
| * Deregister an event listener previously registered with this Node. |
| * A listener must be independently removed from the |
| * Capturing and Bubbling roles. Redundant removals (of |
| * listeners not currently registered for this role) are ignored. |
| * @param type Event name (NOT event group!) to listen for. |
| * @param listener Who gets called when event is dispatched |
| * @param useCapture True iff listener is registered on |
| * capturing phase rather than at-target or bubbling |
| */ |
| public void removeEventListener(String type,EventListener listener,boolean useCapture) |
| { |
| // If this couldn't be a valid listener registration, ignore request |
| Vector nodeListeners = ownerDocument().getEventListeners(this); |
| if(nodeListeners==null || type==null || type.equals("") || listener==null) |
| return; |
| |
| // Note that addListener has previously ensured that |
| // each listener may be registered only once per type per phase. |
| for(int i=nodeListeners.size()-1;i>=0;--i) // count-down is OK for deletions! |
| { |
| LEntry le=(LEntry)(nodeListeners.elementAt(i)); |
| if(le.useCapture==useCapture && le.listener==listener && |
| le.type.equals(type)) |
| { |
| nodeListeners.removeElementAt(i); |
| // Storage management: Discard empty listener lists |
| if(nodeListeners.size()==0) |
| ownerDocument().setEventListeners(this, null); |
| |
| // Remove active listener |
| LCount lc=LCount.lookup(type); |
| if(useCapture) |
| --lc.captures; |
| else |
| --lc.bubbles; |
| |
| break; // Found it; no need to loop farther. |
| } |
| } |
| } // removeEventListener(String,EventListener,boolean) :void |
| |
| /** COMMENTED OUT ** |
| Now that event listeners are stored on the document with the node |
| as the key, nodes can't be finalized if they have any event |
| listener. This finalize method becomes useless... This is a place |
| where we could definitely use weak references!! If we did, then |
| this finalize method could be put back in (which is why I don't |
| remove if completely). - ALH |
| ** NON-DOM INTERNAL: |
| A finalizer has added to NodeImpl in order to correct the event-usage |
| counts of any remaining listeners before discarding the Node. |
| This isn't absolutely required, and finalizers are of dubious |
| reliability and have odd effects on some implementations of GC. |
| But given the expense of event generation and distribution it |
| seems a worthwhile safety net. |
| ***** RECONSIDER at some future point. |
| |
| protected void finalize() throws Throwable |
| { |
| super.finalize(); |
| if(nodeListeners!=null) |
| for(int i=nodeListeners.size()-1;i>=0;--i) // count-down is OK for deletions! |
| { |
| LEntry le=(LEntry)(nodeListeners.elementAt(i)); |
| LCount lc=LCount.lookup(le.type); |
| if(le.useCapture) |
| --lc.captures; |
| else |
| --lc.bubbles; |
| } |
| } |
| */ |
| |
| /** |
| * Introduced in DOM Level 2. <p> |
| * Distribution engine for DOM Level 2 Events. |
| * <p> |
| * Event propagation runs as follows: |
| * <ol> |
| * <li>Event is dispatched to a particular target node, which invokes |
| * this code. Note that the event's stopPropagation flag is |
| * cleared when dispatch begins; thereafter, if it has |
| * been set before processing of a node commences, we instead |
| * immediately advance to the DEFAULT phase. |
| * <li>The node's ancestors are established as destinations for events. |
| * For capture and bubble purposes, node ancestry is determined at |
| * the time dispatch starts. If an event handler alters the document |
| * tree, that does not change which nodes will be informed of the event. |
| * <li>CAPTURING_PHASE: Ancestors are scanned, root to target, for |
| * Capturing listeners. If found, they are invoked (see below). |
| * <li>AT_TARGET: |
| * Event is dispatched to NON-CAPTURING listeners on the |
| * target node. Note that capturing listeners on this node are _not_ |
| * invoked. |
| * <li>BUBBLING_PHASE: Ancestors are scanned, target to root, for |
| * non-capturing listeners. |
| * <li>Default processing: Some DOMs have default behaviors bound to specific |
| * nodes. If this DOM does, and if the event's preventDefault flag has |
| * not been set, we now return to the target node and process its |
| * default handler for this event, if any. |
| * </ol> |
| * <p> |
| * Note that (de)registration of handlers during |
| * processing of an event does not take effect during |
| * this phase of this event; they will not be called until |
| * the next time this node is visited by dispatchEvent. |
| * <p> |
| * If an event handler itself causes events to be dispatched, they are |
| * processed synchronously, before processing resumes |
| * on the event which triggered them. Please be aware that this may |
| * result in events arriving at listeners "out of order" relative |
| * to the actual sequence of requests. |
| * <p> |
| * Note that our implementation resets the event's stop/prevent flags |
| * when dispatch begins. |
| * I believe the DOM's intent is that event objects be redispatchable, |
| * though it isn't stated in those terms. |
| * @param event the event object to be dispatched to |
| * registered EventListeners |
| * @return true if the event's <code>preventDefault()</code> |
| * method was invoked by an EventListener; otherwise false. |
| */ |
| public boolean dispatchEvent(Event event) |
| { |
| if(event==null) return false; |
| |
| // Can't use anyone else's implementation, since there's no public |
| // API for setting the event's processing-state fields. |
| EventImpl evt=(EventImpl)event; |
| |
| // VALIDATE -- must have been initialized at least once, must have |
| // a non-null non-blank name. |
| if(!evt.initialized || evt.type==null || evt.type.equals("")) |
| throw new DOMExceptionImpl(DOMExceptionImpl.UNSPECIFIED_EVENT_TYPE, |
| "DOM010 Unspecified event type"); |
| |
| // If nobody is listening for this event, discard immediately |
| LCount lc=LCount.lookup(evt.getType()); |
| if(lc.captures+lc.bubbles+lc.defaults==0) |
| return evt.preventDefault; |
| |
| // INITIALIZE THE EVENT'S DISPATCH STATUS |
| // (Note that Event objects are reusable in our implementation; |
| // that doesn't seem to be explicitly guaranteed in the DOM, but |
| // I believe it is the intent.) |
| evt.target=this; |
| evt.stopPropagation=false; |
| evt.preventDefault=false; |
| |
| // Capture pre-event parentage chain, not including target; |
| // use pre-event-dispatch ancestors even if event handlers mutate |
| // document and change the target's context. |
| // Note that this is parents ONLY; events do not |
| // cross the Attr/Element "blood/brain barrier". |
| // DOMAttrModified. which looks like an exception, |
| // is issued to the Element rather than the Attr |
| // and causes a _second_ DOMSubtreeModified in the Element's |
| // tree. |
| Vector pv=new Vector(10,10); |
| Node p=this,n=p.getParentNode(); |
| while(n!=null) |
| { |
| pv.addElement(n); |
| p=n; |
| n=n.getParentNode(); |
| } |
| |
| //CAPTURING_PHASE: |
| if(lc.captures>0) |
| { |
| evt.eventPhase=Event.CAPTURING_PHASE; |
| //Ancestors are scanned, root to target, for |
| //Capturing listeners. |
| for(int j=pv.size()-1;j>=0;--j) |
| { |
| if(evt.stopPropagation) |
| break; // Someone set the flag. Phase ends. |
| |
| // Handle all capturing listeners on this node |
| NodeImpl nn=(NodeImpl)pv.elementAt(j); |
| evt.currentNode=nn; |
| Vector nodeListeners = ownerDocument().getEventListeners(nn); |
| if(nodeListeners!=null) |
| { |
| Vector nl=(Vector)(nodeListeners.clone()); |
| for(int i=nl.size()-1;i>=0;--i) // count-down more efficient |
| { |
| LEntry le=(LEntry)(nl.elementAt(i)); |
| if(le.useCapture && le.type.equals(evt.type)) |
| try |
| { |
| le.listener.handleEvent(evt); |
| } |
| catch(Exception e) |
| { |
| // All exceptions are ignored. |
| } |
| } |
| } |
| } |
| } |
| |
| //Both AT_TARGET and BUBBLE use non-capturing listeners. |
| if(lc.bubbles>0) |
| { |
| //AT_TARGET PHASE: Event is dispatched to NON-CAPTURING listeners |
| //on the target node. Note that capturing listeners on the target node |
| //are _not_ invoked, even during the capture phase. |
| evt.eventPhase=Event.AT_TARGET; |
| evt.currentNode=this; |
| Vector nodeListeners = ownerDocument().getEventListeners(this); |
| if(!evt.stopPropagation && nodeListeners!=null) |
| { |
| Vector nl=(Vector)nodeListeners.clone(); |
| for(int i=nl.size()-1;i>=0;--i) // count-down is more efficient |
| { |
| LEntry le=(LEntry)nl.elementAt(i); |
| if(le!=null && !le.useCapture && le.type.equals(evt.type)) |
| try |
| { |
| le.listener.handleEvent(evt); |
| } |
| catch(Exception e) |
| { |
| // All exceptions are ignored. |
| } |
| } |
| } |
| //BUBBLING_PHASE: Ancestors are scanned, target to root, for |
| //non-capturing listeners. If the event's preventBubbling flag has |
| //been set before processing of a node commences, we instead |
| //immediately advance to the default phase. |
| //Note that not all events bubble. |
| if(evt.bubbles) |
| { |
| evt.eventPhase=Event.BUBBLING_PHASE; |
| for(int j=0;j<pv.size();++j) |
| { |
| if(evt.stopPropagation) |
| break; // Someone set the flag. Phase ends. |
| |
| // Handle all bubbling listeners on this node |
| NodeImpl nn=(NodeImpl)pv.elementAt(j); |
| evt.currentNode=nn; |
| nodeListeners = ownerDocument().getEventListeners(nn); |
| if(nodeListeners!=null) |
| { |
| Vector nl=(Vector)(nodeListeners.clone()); |
| for(int i=nl.size()-1;i>=0;--i) // count-down more efficient |
| { |
| LEntry le=(LEntry)(nl.elementAt(i)); |
| if(!le.useCapture && le.type.equals(evt.type)) |
| try |
| { |
| le.listener.handleEvent(evt); |
| } |
| catch(Exception e) |
| { |
| // All exceptions are ignored. |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| //DEFAULT PHASE: Some DOMs have default behaviors bound to specific |
| //nodes. If this DOM does, and if the event's preventDefault flag has |
| //not been set, we now return to the target node and process its |
| //default handler for this event, if any. |
| // No specific phase value defined, since this is DOM-internal |
| if(lc.defaults>0 && (!evt.cancelable || !evt.preventDefault)) |
| { |
| // evt.eventPhase=Event.DEFAULT_PHASE; |
| // evt.currentNode=this; |
| // DO_DEFAULT_OPERATION |
| } |
| |
| return evt.preventDefault; |
| } // dispatchEvent(Event) :boolean |
| |
| |
| /** NON-DOM INTERNAL: DOMNodeInsertedIntoDocument and ...RemovedFrom... |
| * are dispatched to an entire subtree. This is the distribution code |
| * therefor. They DO NOT bubble, thanks be, but may be captured. |
| * <p> |
| * ***** At the moment I'm being sloppy and using the normal |
| * capture dispatcher on every node. This could be optimized hugely |
| * by writing a capture engine that tracks our position in the tree to |
| * update the capture chain without repeated chases up to root. |
| * @param n node which was directly inserted or removed |
| * @param e event to be sent to that node and its subtree |
| */ |
| void dispatchEventToSubtree(Node n,Event e) |
| { |
| if(MUTATIONEVENTS && ownerDocument().mutationEvents) |
| { |
| Vector nodeListeners = ownerDocument().getEventListeners(this); |
| if(nodeListeners==null || n==null) |
| return; |
| |
| // ***** Recursive implementation. This is excessively expensive, |
| // and should be replaced in conjunction with optimization |
| // mentioned above. |
| ((NodeImpl)n).dispatchEvent(e); |
| if(n.getNodeType()==Node.ELEMENT_NODE) |
| { |
| NamedNodeMap a=n.getAttributes(); |
| for(int i=a.getLength()-1;i>=0;--i) |
| dispatchEventToSubtree(a.item(i),e); |
| } |
| dispatchEventToSubtree(n.getFirstChild(),e); |
| dispatchEventToSubtree(n.getNextSibling(),e); |
| } |
| } // dispatchEventToSubtree(Node,Event) :void |
| |
| /** NON-DOM INTERNAL: Return object for getEnclosingAttr. Carries |
| * (two values, the Attr node affected (if any) and its previous |
| * string value. Simple struct, no methods. |
| */ |
| class EnclosingAttr |
| { |
| AttrImpl node; |
| String oldvalue; |
| } //EnclosingAttr |
| |
| /** NON-DOM INTERNAL: Pre-mutation context check, in |
| * preparation for later generating DOMAttrModified events. |
| * Determines whether this node is within an Attr |
| * @return either a description of that Attr, or Null |
| * if none such. |
| */ |
| EnclosingAttr getEnclosingAttr() |
| { |
| if(MUTATIONEVENTS && ownerDocument().mutationEvents) |
| { |
| NodeImpl eventAncestor=this; |
| while(true) |
| { |
| if(eventAncestor==null) |
| return null; |
| int type=eventAncestor.getNodeType(); |
| if(type==Node.ATTRIBUTE_NODE) |
| { |
| EnclosingAttr retval=new EnclosingAttr(); |
| retval.node=(AttrImpl)eventAncestor; |
| retval.oldvalue=retval.node.getNodeValue(); |
| return retval; |
| } |
| else if(type==Node.ENTITY_REFERENCE_NODE) |
| eventAncestor=eventAncestor.parentNode(); |
| else |
| return null; |
| // Any other parent means we're not in an Attr |
| } |
| } |
| return null; // Safety net, should never be reached |
| } // getEnclosingAttr() :EnclosingAttr |
| |
| |
| /** NON-DOM INTERNAL: Convenience wrapper for calling |
| * dispatchAggregateEvents when the context was established |
| * by <code>getEnclosingAttr</code>. |
| * @param ea description of Attr affected by current operation |
| */ |
| void dispatchAggregateEvents(EnclosingAttr ea) |
| { |
| if(ea!=null) |
| dispatchAggregateEvents(ea.node,ea.oldvalue); |
| else |
| dispatchAggregateEvents(null,null); |
| |
| } // dispatchAggregateEvents(EnclosingAttr) :void |
| |
| /** NON-DOM INTERNAL: Generate the "aggregated" post-mutation events |
| * DOMAttrModified and DOMSubtreeModified. |
| * Both of these should be issued only once for each user-requested |
| * mutation operation, even if that involves multiple changes to |
| * the DOM. |
| * For example, if a DOM operation makes multiple changes to a single |
| * Attr before returning, it would be nice to generate only one |
| * DOMAttrModified, and multiple changes over larger scope but within |
| * a recognizable single subtree might want to generate only one |
| * DOMSubtreeModified, sent to their lowest common ancestor. |
| * <p> |
| * To manage this, use the "internal" versions of insert and remove |
| * with MUTATION_LOCAL, then make an explicit call to this routine |
| * at the higher level. Some examples now exist in our code. |
| * |
| * @param enclosingAttr The Attr node (if any) whose value has |
| * been changed as a result of the DOM operation. Null if none such. |
| * @param oldValue The String value previously held by the |
| * enclosingAttr. Ignored if none such. |
| */ |
| void dispatchAggregateEvents(AttrImpl enclosingAttr,String oldvalue) |
| { |
| if(MUTATIONEVENTS && ownerDocument().mutationEvents) |
| { |
| // We have to send DOMAttrModified. |
| NodeImpl owner=null; |
| if(enclosingAttr!=null) |
| { |
| LCount lc=LCount.lookup(MutationEventImpl.DOM_ATTR_MODIFIED); |
| if(lc.captures+lc.bubbles+lc.defaults>0) |
| { |
| owner=((NodeImpl)(enclosingAttr.getOwnerElement())); |
| if(owner!=null) |
| { |
| MutationEvent me= |
| new MutationEventImpl(); |
| //?????ownerDocument.createEvent("MutationEvents"); |
| me.initMutationEvent(MutationEventImpl.DOM_ATTR_MODIFIED,true,false, |
| null,oldvalue,enclosingAttr.getNodeValue(),enclosingAttr.getNodeName()); |
| owner.dispatchEvent(me); |
| } |
| } |
| } |
| |
| // DOMSubtreeModified gets sent to the lowest common root of a |
| // set of changes. |
| // "This event is dispatched after all other events caused by the |
| // mutation have been fired." |
| LCount lc=LCount.lookup(MutationEventImpl.DOM_SUBTREE_MODIFIED); |
| if(lc.captures+lc.bubbles+lc.defaults>0) |
| { |
| MutationEvent me= |
| new MutationEventImpl(); |
| //?????ownerDocument.createEvent("MutationEvents"); |
| me.initMutationEvent(MutationEventImpl.DOM_SUBTREE_MODIFIED,true,false, |
| null,null,null,null); |
| |
| |
| // If we're within an Attr, DStM gets sent to the Attr |
| // and to its owningElement. Otherwise we dispatch it |
| // locally. |
| if(enclosingAttr!=null) |
| { |
| enclosingAttr.dispatchEvent(me); |
| if(owner!=null) |
| owner.dispatchEvent(me); |
| } |
| else |
| dispatchEvent(me); |
| } |
| } |
| } //dispatchAggregateEvents(AttrImpl,String) :void |
| |
| |
| // |
| // Public methods |
| // |
| |
| /** |
| * NON-DOM: PR-DOM-Level-1-19980818 mentions readonly nodes in conjunction |
| * with Entities, but provides no API to support this. |
| * <P> |
| * Most DOM users should not touch this method. Its anticpated use |
| * is during construction of EntityRefernces, where it will be used to |
| * lock the contents replicated from Entity so they can't be casually |
| * altered. It _could_ be published as a DOM extension, if desired. |
| * <P> |
| * Note: since we never have any children deep is meaningless here, |
| * ParentNode overrides this behavior. |
| * @see ParentNode |
| * |
| * @param readOnly True or false as desired. |
| * @param deep If true, children are also toggled. Note that this will |
| * not change the state of an EntityReference or its children, |
| * which are always read-only. |
| */ |
| public void setReadOnly(boolean readOnly, boolean deep) { |
| |
| if (needsSyncData()) { |
| synchronizeData(); |
| } |
| isReadOnly(readOnly); |
| |
| } // setReadOnly(boolean,boolean) |
| |
| /** |
| * NON-DOM: Returns true if this node is read-only. This is a |
| * shallow check. |
| */ |
| public boolean getReadOnly() { |
| |
| if (needsSyncData()) { |
| synchronizeData(); |
| } |
| return isReadOnly(); |
| |
| } // getReadOnly():boolean |
| |
| /** |
| * NON-DOM: As an alternative to subclassing the DOM, this implementation |
| * has been extended with the ability to attach an object to each node. |
| * (If you need multiple objects, you can attach a collection such as a |
| * vector or hashtable, then attach your application information to that.) |
| * <p><b>Important Note:</b> You are responsible for removing references |
| * to your data on nodes that are no longer used. Failure to do so will |
| * prevent the nodes, your data is attached to, to be garbage collected |
| * until the whole document is. |
| * |
| * @param data the object to store or null to remove any existing reference |
| */ |
| public void setUserData(Object data) { |
| ownerDocument().setUserData(this, data); |
| } |
| |
| /** |
| * NON-DOM: |
| * Returns the user data associated to this node. |
| */ |
| public Object getUserData() { |
| return ownerDocument().getUserData(this); |
| } |
| |
| // |
| // Protected methods |
| // |
| |
| /** |
| * Denotes that this node has changed. |
| */ |
| protected void changed() { |
| // we do not actually store this information on every node, we only |
| // have a global indicator on the Document. Doing otherwise cost us too |
| // much for little gain. |
| ownerDocument().changed(); |
| } |
| |
| /** |
| * Returns the number of changes to this node. |
| */ |
| protected int changes() { |
| // we do not actually store this information on every node, we only |
| // have a global indicator on the Document. Doing otherwise cost us too |
| // much for little gain. |
| return ownerDocument().changes(); |
| } |
| |
| /** |
| * Override this method in subclass to hook in efficient |
| * internal data structure. |
| */ |
| protected void synchronizeData() { |
| // By default just change the flag to avoid calling this method again |
| needsSyncData(false); |
| } |
| |
| |
| /* |
| * Flags setters and getters |
| */ |
| |
| final boolean isReadOnly() { |
| return (flags & READONLY) != 0; |
| } |
| |
| final void isReadOnly(boolean value) { |
| flags = (short) (value ? flags | READONLY : flags & ~READONLY); |
| } |
| |
| final boolean needsSyncData() { |
| return (flags & SYNCDATA) != 0; |
| } |
| |
| final void needsSyncData(boolean value) { |
| flags = (short) (value ? flags | SYNCDATA : flags & ~SYNCDATA); |
| } |
| |
| final boolean needsSyncChildren() { |
| return (flags & SYNCCHILDREN) != 0; |
| } |
| |
| final void needsSyncChildren(boolean value) { |
| flags = (short) (value ? flags | SYNCCHILDREN : flags & ~SYNCCHILDREN); |
| } |
| |
| final boolean isOwned() { |
| return (flags & OWNED) != 0; |
| } |
| |
| final void isOwned(boolean value) { |
| flags = (short) (value ? flags | OWNED : flags & ~OWNED); |
| } |
| |
| final boolean isFirstChild() { |
| return (flags & FIRSTCHILD) != 0; |
| } |
| |
| final void isFirstChild(boolean value) { |
| flags = (short) (value ? flags | FIRSTCHILD : flags & ~FIRSTCHILD); |
| } |
| |
| final boolean isSpecified() { |
| return (flags & SPECIFIED) != 0; |
| } |
| |
| final void isSpecified(boolean value) { |
| flags = (short) (value ? flags | SPECIFIED : flags & ~SPECIFIED); |
| } |
| |
| // inconsistent name to avoid clash with public method on TextImpl |
| final boolean internalIsIgnorableWhitespace() { |
| return (flags & IGNORABLEWS) != 0; |
| } |
| |
| final void isIgnorableWhitespace(boolean value) { |
| flags = (short) (value ? flags | IGNORABLEWS : flags & ~IGNORABLEWS); |
| } |
| |
| final boolean setValueCalled() { |
| return (flags & SETVALUE) != 0; |
| } |
| |
| final void setValueCalled(boolean value) { |
| flags = (short) (value ? flags | SETVALUE : flags & ~SETVALUE); |
| } |
| |
| // |
| // Object methods |
| // |
| |
| /** NON-DOM method for debugging convenience. */ |
| public String toString() { |
| return "["+getNodeName()+": "+getNodeValue()+"]"; |
| } |
| |
| // |
| // Serialization methods |
| // |
| |
| /** Serialize object. */ |
| private void writeObject(ObjectOutputStream out) throws IOException { |
| |
| // synchronize data |
| if (needsSyncData()) { |
| synchronizeData(); |
| } |
| // write object |
| out.defaultWriteObject(); |
| |
| } // writeObject(ObjectOutputStream) |
| |
| } // class NodeImpl |