/* * The Apache Software License, Version 1.1 * * * Copyright (c) 1999-2002 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 * . */ package com.sun.org.apache.xerces.internal.dom; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Hashtable; import org.w3c.dom.UserDataHandler; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.DocumentType; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.events.Event; import org.w3c.dom.events.EventListener; import org.w3c.dom.events.EventTarget; /** * NodeImpl 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. *

* 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. *

* 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. *

* 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. *

* 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. *

* This class doesn't directly support mutation events, however, it still * implements the EventTarget interface and forward all related calls to the * document so that the document class do so. * * @author Arnaud Le Hors, IBM * @author Joe Kesselman, IBM * @version $Id: NodeImpl.java,v 1.71 2004/01/16 16:23:50 elena Exp $ * @since PR-DOM-Level-1-19980818. */ public abstract class NodeImpl implements Node, NodeList, EventTarget, Cloneable, Serializable{ // // Constants // // TreePosition Constants. // Taken from DOM L3 Node interface. /** * The node precedes the reference node. */ public static final short TREE_POSITION_PRECEDING = 0x01; /** * The node follows the reference node. */ public static final short TREE_POSITION_FOLLOWING = 0x02; /** * The node is an ancestor of the reference node. */ public static final short TREE_POSITION_ANCESTOR = 0x04; /** * The node is a descendant of the reference node. */ public static final short TREE_POSITION_DESCENDANT = 0x08; /** * The two nodes have an equivalent position. This is the case of two * attributes that have the same ownerElement, and two * nodes that are the same. */ public static final short TREE_POSITION_EQUIVALENT = 0x10; /** * The two nodes are the same. Two nodes that are the same have an * equivalent position, though the reverse may not be true. */ public static final short TREE_POSITION_SAME_NODE = 0x20; /** * The two nodes are disconnected, they do not have any common ancestor. * This is the case of two nodes that are not in the same document. */ public static final short TREE_POSITION_DISCONNECTED = 0x00; // DocumentPosition public static final short DOCUMENT_POSITION_DISCONNECTED = 0x01; public static final short DOCUMENT_POSITION_PRECEDING = 0x02; public static final short DOCUMENT_POSITION_FOLLOWING = 0x04; public static final short DOCUMENT_POSITION_CONTAINS = 0x08; public static final short DOCUMENT_POSITION_IS_CONTAINED = 0x10; public static final short DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20; /** Serialization version. */ static final long serialVersionUID = -6316591992167219696L; // public /** Element definition node type. */ public static final short ELEMENT_DEFINITION_NODE = 21; // // 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 HASSTRING = 0x1<<7; protected final static short NORMALIZED = 0x1<<8; protected final static short ID = 0x1<<9; // // Constructors // /** * No public constructor; only subclasses of Node should be * instantiated, and those normally via a Document's factory methods *

* Every Node knows what Document it belongs to. */ protected NodeImpl(CoreDocumentImpl ownerDocument) { // as long as we do not have any owner, ownerNode is our ownerDocument ownerNode = ownerDocument; } // (CoreDocumentImpl) /** 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. * @throws DOMException(DOMSTRING_SIZE_ERR) */ public String getNodeValue() throws DOMException { 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) *

* By default we do not accept any children, ParentNode overrides this. * @see ParentNode * * @return 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. *

* Note: since we never have any children deep is meaningless here, * ParentNode overrides this behavior. * @see ParentNode * *

* Example: Cloning a Text node will copy both the node and the text it * contains. *

* 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. *

* 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) { // if we get here we have an error in our program we may as well // be vocal about it, so that people can take appropriate action. throw new RuntimeException("**Internal Error**" + e); } // Need to break the association w/ original kids newnode.ownerNode = ownerDocument(); newnode.isOwned(false); // By default we make all clones readwrite, // this is overriden in readonly subclasses newnode.isReadOnly(false); ownerDocument().callUserDataHandlers(this, newnode, UserDataHandler.NODE_CLONED); 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 CoreDocumentImpl to return null */ CoreDocumentImpl ownerDocument() { // if we have an owner simply forward the request // otherwise ownerNode is our ownerDocument if (isOwned()) { return ownerNode.ownerDocument(); } else { return (CoreDocumentImpl) ownerNode; } } /** * NON-DOM * set the ownerDocument of this node */ void setOwnerDocument(CoreDocumentImpl 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; } } /** * Returns the node number */ protected int getNodeNumber() { int nodeNumber; CoreDocumentImpl cd = (CoreDocumentImpl)(this.getOwnerDocument()); nodeNumber = cd.getNodeNumber(this); return nodeNumber; } /** * 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 } /** * 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 true if this node has any attributes, * false 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) *

* 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. *

* 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. *

* 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. *

* 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. *

* 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. *

* 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(). * * @return 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 DOMException(DOMException.HIERARCHY_REQUEST_ERR, DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null)); } /** * Remove a child from this Node. The removed child's subtree * remains intact so it may be re-inserted elsewhere. *

* 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 DOMException(DOMException.NOT_FOUND_ERR, DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null)); } /** * 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. *

* By default we do not have any children, ParentNode overrides this. * @see ParentNode * * @return 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 DOMException(DOMException.HIERARCHY_REQUEST_ERR, DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null)); } // // NodeList methods // /** * NodeList method: Count the immediate children of this node *

* 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. *

* 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 Text nodes in the full depth of the sub-tree * underneath this Node, including attribute nodes, into a * "normal" form where only markup (e.g., tags, comments, processing * instructions, CDATA sections, and entity references) separates * Text nodes, i.e., there are no adjacent Text * 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 CDATASections, the normalize operation alone may * not be sufficient, since XPointers do not differentiate between * Text nodes and CDATASection nodes. *

* 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.

* 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 isSupported(String feature, String version) { return ownerDocument().getImplementation().hasFeature(feature, version); } /** * Introduced in DOM Level 2.

* * 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.

* * 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.

* * 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.

* * 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.

* * 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 getPrefix() { return null; } /** * Introduced in DOM Level 2.

* * 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.

* * For nodes created with a DOM Level 1 method, such as createElement from * the Document interface, this is null.

* * 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.

* * @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 DOMException(DOMException.NAMESPACE_ERR, DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NAMESPACE_ERR", null)); } /** * Introduced in DOM Level 2.

* * 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 void addEventListener(String type, EventListener listener, boolean useCapture) { // simply forward to Document ownerDocument().addEventListener(this, type, listener, useCapture); } public void removeEventListener(String type, EventListener listener, boolean useCapture) { // simply forward to Document ownerDocument().removeEventListener(this, type, listener, useCapture); } public boolean dispatchEvent(Event event) { // simply forward to Document return ownerDocument().dispatchEvent(this, event); } // // Public DOM Level 3 methods // /** * The absolute base URI of this node or null if undefined. * This value is computed according to . However, when the * Document supports the feature "HTML" , the base URI is * computed using first the value of the href attribute of the HTML BASE * element if any, and the value of the documentURI * attribute from the Document interface otherwise. *
When the node is an Element, a Document * or a a ProcessingInstruction, this attribute represents * the properties [base URI] defined in . When the node is a * Notation, an Entity, or an * EntityReference, this attribute represents the * properties [declaration base URI] in the . How will this be affected * by resolution of relative namespace URIs issue?It's not.Should this * only be on Document, Element, ProcessingInstruction, Entity, and * Notation nodes, according to the infoset? If not, what is it equal to * on other nodes? Null? An empty string? I think it should be the * parent's.No.Should this be read-only and computed or and actual * read-write attribute?Read-only and computed (F2F 19 Jun 2000 and * teleconference 30 May 2001).If the base HTML element is not yet * attached to a document, does the insert change the Document.baseURI? * Yes. (F2F 26 Sep 2001) * @since DOM Level 3 */ public String getBaseURI() { return null; } /** * Compares a node with this node with regard to their position in the * tree and according to the document order. This order can be extended * by module that define additional types of nodes. * @param other The node to compare against this node. * @return Returns how the given node is positioned relatively to this * node. * @since DOM Level 3 * @deprecated */ public short compareTreePosition(Node other) { // Questions of clarification for this method - to be answered by the // DOM WG. Current assumptions listed - LM // // 1. How do ENTITY nodes compare? // Current assumption: TREE_POSITION_DISCONNECTED, as ENTITY nodes // aren't really 'in the tree' // // 2. How do NOTATION nodes compare? // Current assumption: TREE_POSITION_DISCONNECTED, as NOTATION nodes // aren't really 'in the tree' // // 3. Are TREE_POSITION_ANCESTOR and TREE_POSITION_DESCENDANT // only relevant for nodes that are "part of the document tree"? // // // // Is the element node "outer" considered an ancestor of "myattr"? // Current assumption: No. // // 4. How do children of ATTRIBUTE nodes compare (with eachother, or // with children of other attribute nodes with the same element) // Current assumption: Children of ATTRIBUTE nodes are treated as if // they they are the attribute node itself, unless the 2 nodes // are both children of the same attribute. // // 5. How does an ENTITY_REFERENCE node compare with it's children? // Given the DOM, it should precede its children as an ancestor. // Given "document order", does it represent the same position? // Current assumption: An ENTITY_REFERENCE node is an ancestor of its // children. // // 6. How do children of a DocumentFragment compare? // Current assumption: If both nodes are part of the same document // fragment, there are compared as if they were part of a document. // If the nodes are the same... if (this==other) return (TREE_POSITION_SAME_NODE | TREE_POSITION_EQUIVALENT); // If either node is of type ENTITY or NOTATION, compare as disconnected short thisType = this.getNodeType(); short otherType = other.getNodeType(); // If either node is of type ENTITY or NOTATION, compare as disconnected if (thisType == Node.ENTITY_NODE || thisType == Node.NOTATION_NODE || otherType == Node.ENTITY_NODE || otherType == Node.NOTATION_NODE ) { return TREE_POSITION_DISCONNECTED; } // Find the ancestor of each node, and the distance each node is from // its ancestor. // During this traversal, look for ancestor/descendent relationships // between the 2 nodes in question. // We do this now, so that we get this info correct for attribute nodes // and their children. Node node; Node thisAncestor = this; Node otherAncestor = other; int thisDepth=0; int otherDepth=0; for (node=this; node != null; node = node.getParentNode()) { thisDepth +=1; if (node == other) // The other node is an ancestor of this one. return (TREE_POSITION_ANCESTOR | TREE_POSITION_PRECEDING); thisAncestor = node; } for (node=other; node!=null; node=node.getParentNode()) { otherDepth +=1; if (node == this) // The other node is a descendent of the reference node. return (TREE_POSITION_DESCENDANT | TREE_POSITION_FOLLOWING); otherAncestor = node; } Node thisNode = this; Node otherNode = other; int thisAncestorType = thisAncestor.getNodeType(); int otherAncestorType = otherAncestor.getNodeType(); // if the ancestor is an attribute, get owning element. // we are now interested in the owner to determine position. if (thisAncestorType == Node.ATTRIBUTE_NODE) { thisNode = ((AttrImpl)thisAncestor).getOwnerElement(); } if (otherAncestorType == Node.ATTRIBUTE_NODE) { otherNode = ((AttrImpl)otherAncestor).getOwnerElement(); } // Before proceeding, we should check if both ancestor nodes turned // out to be attributes for the same element if (thisAncestorType == Node.ATTRIBUTE_NODE && otherAncestorType == Node.ATTRIBUTE_NODE && thisNode==otherNode) return TREE_POSITION_EQUIVALENT; // Now, find the ancestor of the owning element, if the original // ancestor was an attribute // Note: the following 2 loops are quite close to the ones above. // May want to common them up. LM. if (thisAncestorType == Node.ATTRIBUTE_NODE) { thisDepth=0; for (node=thisNode; node != null; node=node.getParentNode()) { thisDepth +=1; if (node == otherNode) // The other node is an ancestor of the owning element { return TREE_POSITION_PRECEDING; } thisAncestor = node; } } // Now, find the ancestor of the owning element, if the original // ancestor was an attribute if (otherAncestorType == Node.ATTRIBUTE_NODE) { otherDepth=0; for (node=otherNode; node != null; node=node.getParentNode()) { otherDepth +=1; if (node == thisNode) // The other node is a descendent of the reference // node's element return TREE_POSITION_FOLLOWING; otherAncestor = node; } } // thisAncestor and otherAncestor must be the same at this point, // otherwise, we are not in the same tree or document fragment if (thisAncestor != otherAncestor) return TREE_POSITION_DISCONNECTED; // Go up the parent chain of the deeper node, until we find a node // with the same depth as the shallower node if (thisDepth > otherDepth) { for (int i=0; i thisDocNum) return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; else return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_PRECEDING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; } // Find the ancestor of each node, and the distance each node is from // its ancestor. // During this traversal, look for ancestor/descendent relationships // between the 2 nodes in question. // We do this now, so that we get this info correct for attribute nodes // and their children. Node node; Node thisAncestor = this; Node otherAncestor = other; int thisDepth=0; int otherDepth=0; for (node=this; node != null; node = node.getParentNode()) { thisDepth +=1; if (node == other) // The other node is an ancestor of this one. return (DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING); thisAncestor = node; } for (node=other; node!=null; node=node.getParentNode()) { otherDepth +=1; if (node == this) // The other node is a descendent of the reference node. return (DOCUMENT_POSITION_IS_CONTAINED | DOCUMENT_POSITION_FOLLOWING); otherAncestor = node; } int thisAncestorType = thisAncestor.getNodeType(); int otherAncestorType = otherAncestor.getNodeType(); Node thisNode = this; Node otherNode = other; // Special casing for ENTITY, NOTATION, DOCTYPE and ATTRIBUTES // LM: should rewrite this. switch (thisAncestorType) { case Node.NOTATION_NODE: case Node.ENTITY_NODE: { DocumentType container = thisOwnerDoc.getDoctype(); if (container == otherAncestor) return (DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING); switch (otherAncestorType) { case Node.NOTATION_NODE: case Node.ENTITY_NODE: { if (thisAncestorType != otherAncestorType) // the nodes are of different types return ((thisAncestorType>otherAncestorType) ? DOCUMENT_POSITION_PRECEDING:DOCUMENT_POSITION_FOLLOWING); else { // the nodes are of the same type. Find order. if (thisAncestorType == Node.NOTATION_NODE) if (((NamedNodeMapImpl)container.getNotations()).precedes(otherAncestor,thisAncestor)) return (DOCUMENT_POSITION_PRECEDING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC); else return (DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC); else if (((NamedNodeMapImpl)container.getEntities()).precedes(otherAncestor,thisAncestor)) return (DOCUMENT_POSITION_PRECEDING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC); else return (DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC); } } } thisNode = thisAncestor = thisOwnerDoc; break; } case Node.DOCUMENT_TYPE_NODE: { if (otherNode == thisOwnerDoc) return (DOCUMENT_POSITION_PRECEDING | DOCUMENT_POSITION_CONTAINS); else if (thisOwnerDoc!=null && thisOwnerDoc==otherOwnerDoc) return (DOCUMENT_POSITION_FOLLOWING); break; } case Node.ATTRIBUTE_NODE: { thisNode = ((AttrImpl)thisAncestor).getOwnerElement(); if (otherAncestorType==Node.ATTRIBUTE_NODE) { otherNode = ((AttrImpl)otherAncestor).getOwnerElement(); if (otherNode == thisNode) { if (((NamedNodeMapImpl)thisNode.getAttributes()).precedes(other,this)) return (DOCUMENT_POSITION_PRECEDING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC); else return (DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC); } } // Now, find the ancestor of the element thisDepth=0; for (node=thisNode; node != null; node=node.getParentNode()) { thisDepth +=1; if (node == otherNode) { // The other node is an ancestor of the owning element return (DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING); } thisAncestor = node; } } } switch (otherAncestorType) { case Node.NOTATION_NODE: case Node.ENTITY_NODE: { DocumentType container = thisOwnerDoc.getDoctype(); if (container == this) return (DOCUMENT_POSITION_IS_CONTAINED | DOCUMENT_POSITION_FOLLOWING); otherNode = otherAncestor = thisOwnerDoc; break; } case Node.DOCUMENT_TYPE_NODE: { if (thisNode == otherOwnerDoc) return (DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_IS_CONTAINED); else if (otherOwnerDoc!=null && thisOwnerDoc==otherOwnerDoc) return (DOCUMENT_POSITION_PRECEDING); break; } case Node.ATTRIBUTE_NODE: { otherDepth=0; otherNode = ((AttrImpl)otherAncestor).getOwnerElement(); for (node=otherNode; node != null; node=node.getParentNode()) { otherDepth +=1; if (node == thisNode) // The other node is a descendent of the reference // node's element return DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_IS_CONTAINED; otherAncestor = node; } } } // thisAncestor and otherAncestor must be the same at this point, // otherwise, the original nodes are disconnected if (thisAncestor != otherAncestor) { int thisAncestorNum, otherAncestorNum; thisAncestorNum = ((NodeImpl)thisAncestor).getNodeNumber(); otherAncestorNum = ((NodeImpl)otherAncestor).getNodeNumber(); if (thisAncestorNum > otherAncestorNum) return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; else return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_PRECEDING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; } // Go up the parent chain of the deeper node, until we find a node // with the same depth as the shallower node if (thisDepth > otherDepth) { for (int i=0; iText node containing the string * this attribute is set to. On getting, no serialization is performed, * the returned string does not contain any markup. No whitespace * normalization is performed, the returned string does not contain the * element content whitespaces . Similarly, on setting, no parsing is * performed either, the input string is taken as pure textual content. *
The string returned is made of the text content of this node * depending on its type, as defined below: * * * * * /** * This attribute returns the text content of this node and its * descendants. When it is defined to be null, setting it has no effect. * When set, any possible children this node may have are removed and * replaced by a single Text node containing the string * this attribute is set to. On getting, no serialization is performed, * the returned string does not contain any markup. No whitespace * normalization is performed, the returned string does not contain the * element content whitespaces . Similarly, on setting, no parsing is * performed either, the input string is taken as pure textual content. *
The string returned is made of the text content of this node * depending on its type, as defined below: *
Node typeContent
* * * * * * * * * * * * * * * * *
Node typeContent
* ELEMENT_NODE, ENTITY_NODE, ENTITY_REFERENCE_NODE, * DOCUMENT_FRAGMENT_NODEconcatenation of the textContent * attribute value of every child node, excluding COMMENT_NODE and * PROCESSING_INSTRUCTION_NODE nodes
ATTRIBUTE_NODE, TEXT_NODE, * CDATA_SECTION_NODE, COMMENT_NODE, PROCESSING_INSTRUCTION_NODE * nodeValue
DOCUMENT_NODE, DOCUMENT_TYPE_NODE, NOTATION_NODE * null
* @exception DOMException * NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly. * @exception DOMException * DOMSTRING_SIZE_ERR: Raised when it would return more characters than * fit in a DOMString variable on the implementation * platform. * @since DOM Level 3 */ public String getTextContent() throws DOMException { return getNodeValue(); // overriden in some subclasses } // internal method taking a StringBuffer in parameter void getTextContent(StringBuffer buf) throws DOMException { String content = getNodeValue(); if (content != null) { buf.append(content); } } /** * This attribute returns the text content of this node and its * descendants. When it is defined to be null, setting it has no effect. * When set, any possible children this node may have are removed and * replaced by a single Text node containing the string * this attribute is set to. On getting, no serialization is performed, * the returned string does not contain any markup. No whitespace * normalization is performed, the returned string does not contain the * element content whitespaces . Similarly, on setting, no parsing is * performed either, the input string is taken as pure textual content. *
The string returned is made of the text content of this node * depending on its type, as defined below: * * * * * * * * * * * * * * * * * *
Node typeContent
* ELEMENT_NODE, ENTITY_NODE, ENTITY_REFERENCE_NODE, * DOCUMENT_FRAGMENT_NODEconcatenation of the textContent * attribute value of every child node, excluding COMMENT_NODE and * PROCESSING_INSTRUCTION_NODE nodes
ATTRIBUTE_NODE, TEXT_NODE, * CDATA_SECTION_NODE, COMMENT_NODE, PROCESSING_INSTRUCTION_NODE * nodeValue
DOCUMENT_NODE, DOCUMENT_TYPE_NODE, NOTATION_NODE * null
* @exception DOMException * NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly. * @exception DOMException * DOMSTRING_SIZE_ERR: Raised when it would return more characters than * fit in a DOMString variable on the implementation * platform. * @since DOM Level 3 */ public void setTextContent(String textContent) throws DOMException { setNodeValue(textContent); } /** * Returns whether this node is the same node as the given one. *
This method provides a way to determine whether two * Node references returned by the implementation reference * the same object. When two Node references are references * to the same object, even if through a proxy, the references may be * used completely interchangably, such that all attributes have the * same values and calling the same DOM method on either reference * always has exactly the same effect. * @param other The node to test against. * @return Returns true if the nodes are the same, * false otherwise. * @since DOM Level 3 */ public boolean isSameNode(Node other) { // we do not use any wrapper so the answer is obvious return this == other; } /** * DOM Level 3: Experimental * This method checks if the specified namespaceURI is the * default namespace or not. * @param namespaceURI The namespace URI to look for. * @return true if the specified namespaceURI * is the default namespace, false otherwise. * @since DOM Level 3 */ public boolean isDefaultNamespace(String namespaceURI){ // REVISIT: remove casts when DOM L3 becomes REC. short type = this.getNodeType(); switch (type) { case Node.ELEMENT_NODE: { String namespace = this.getNamespaceURI(); String prefix = this.getPrefix(); // REVISIT: is it possible that prefix is empty string? if (prefix == null || prefix.length() == 0) { if (namespaceURI == null) { return (namespace == namespaceURI); } return namespaceURI.equals(namespace); } if (this.hasAttributes()) { ElementImpl elem = (ElementImpl)this; NodeImpl attr = (NodeImpl)elem.getAttributeNodeNS("http://www.w3.org/2000/xmlns/", "xmlns"); if (attr != null) { String value = attr.getNodeValue(); if (namespaceURI == null) { return (namespace == value); } return namespaceURI.equals(value); } } NodeImpl ancestor = (NodeImpl)getElementAncestor(this); if (ancestor != null) { return ancestor.isDefaultNamespace(namespaceURI); } return false; } case Node.DOCUMENT_NODE:{ return((NodeImpl)((Document)this).getDocumentElement()).isDefaultNamespace(namespaceURI); } case Node.ENTITY_NODE : case Node.NOTATION_NODE: case Node.DOCUMENT_FRAGMENT_NODE: case Node.DOCUMENT_TYPE_NODE: // type is unknown return false; case Node.ATTRIBUTE_NODE:{ if (this.ownerNode.getNodeType() == Node.ELEMENT_NODE) { return ownerNode.isDefaultNamespace(namespaceURI); } return false; } default:{ NodeImpl ancestor = (NodeImpl)getElementAncestor(this); if (ancestor != null) { return ancestor.isDefaultNamespace(namespaceURI); } return false; } } } /** * * DOM Level 3 - Experimental: * Look up the prefix associated to the given namespace URI, starting from this node. * * @param namespaceURI * @return the prefix for the namespace */ public String lookupPrefix(String namespaceURI){ // REVISIT: When Namespaces 1.1 comes out this may not be true // Prefix can't be bound to null namespace if (namespaceURI == null) { return null; } short type = this.getNodeType(); switch (type) { case Node.ELEMENT_NODE: { String namespace = this.getNamespaceURI(); // to flip out children return lookupNamespacePrefix(namespaceURI, (ElementImpl)this); } case Node.DOCUMENT_NODE:{ return((NodeImpl)((Document)this).getDocumentElement()).lookupPrefix(namespaceURI); } case Node.ENTITY_NODE : case Node.NOTATION_NODE: case Node.DOCUMENT_FRAGMENT_NODE: case Node.DOCUMENT_TYPE_NODE: // type is unknown return null; case Node.ATTRIBUTE_NODE:{ if (this.ownerNode.getNodeType() == Node.ELEMENT_NODE) { return ownerNode.lookupPrefix(namespaceURI); } return null; } default:{ NodeImpl ancestor = (NodeImpl)getElementAncestor(this); if (ancestor != null) { return ancestor.lookupPrefix(namespaceURI); } return null; } } } /** * DOM Level 3 - Experimental: * Look up the namespace URI associated to the given prefix, starting from this node. * Use lookupNamespaceURI(null) to lookup the default namespace * * @param namespaceURI * @return th URI for the namespace * @since DOM Level 3 */ public String lookupNamespaceURI(String specifiedPrefix) { short type = this.getNodeType(); switch (type) { case Node.ELEMENT_NODE : { String namespace = this.getNamespaceURI(); String prefix = this.getPrefix(); if (namespace !=null) { // REVISIT: is it possible that prefix is empty string? if (specifiedPrefix== null && prefix==specifiedPrefix) { // looking for default namespace return namespace; } else if (prefix != null && prefix.equals(specifiedPrefix)) { // non default namespace return namespace; } } if (this.hasAttributes()) { NamedNodeMap map = this.getAttributes(); int length = map.getLength(); for (int i=0;iThis method tests for equality of nodes, not sameness (i.e., * whether the two nodes are references to the same object) which can be * tested with Node.isSameNode. All nodes that are the same * will also be equal, though the reverse may not be true. *
Two nodes are equal if and only if the following conditions are * satisfied: The two nodes are of the same type.The following string * attributes are equal: nodeName, localName, * namespaceURI, prefix, nodeValue * , baseURI. This is: they are both null, or * they have the same length and are character for character identical. * The attributes NamedNodeMaps are equal. * This is: they are both null, or they have the same * length and for each node that exists in one map there is a node that * exists in the other map and is equal, although not necessarily at the * same index.The childNodes NodeLists are * equal. This is: they are both null, or they have the * same length and contain equal nodes at the same index. This is true * for Attr nodes as for any other type of node. Note that * normalization can affect equality; to avoid this, nodes should be * normalized before being compared. *
For two DocumentType nodes to be equal, the following * conditions must also be satisfied: The following string attributes * are equal: publicId, systemId, * internalSubset.The entities * NamedNodeMaps are equal.The notations * NamedNodeMaps are equal. *
On the other hand, the following do not affect equality: the * ownerDocument attribute, the specified * attribute for Attr nodes, the * isWhitespaceInElementContent attribute for * Text nodes, as well as any user data or event listeners * registered on the nodes. * @param arg The node to compare equality with. * @param deep If true, recursively compare the subtrees; if * false, compare only the nodes themselves (and its * attributes, if it is an Element). * @return If the nodes, and possibly subtrees are equal, * true otherwise false. * @since DOM Level 3 */ public boolean isEqualNode(Node arg) { if (arg == this) { return true; } if (arg.getNodeType() != getNodeType()) { return false; } // in theory nodeName can't be null but better be careful // who knows what other implementations may be doing?... if (getNodeName() == null) { if (arg.getNodeName() != null) { return false; } } else if (!getNodeName().equals(arg.getNodeName())) { return false; } if (getLocalName() == null) { if (arg.getLocalName() != null) { return false; } } else if (!getLocalName().equals(arg.getLocalName())) { return false; } if (getNamespaceURI() == null) { if (arg.getNamespaceURI() != null) { return false; } } else if (!getNamespaceURI().equals(arg.getNamespaceURI())) { return false; } if (getPrefix() == null) { if (arg.getPrefix() != null) { return false; } } else if (!getPrefix().equals(arg.getPrefix())) { return false; } if (getNodeValue() == null) { if (arg.getNodeValue() != null) { return false; } } else if (!getNodeValue().equals(arg.getNodeValue())) { return false; } return true; } /** * @since DOM Level 3 */ public Object getFeature(String feature, String version) { // we don't have any alternate node, either this node does the job // or we don't have anything that does return isSupported(feature, version) ? this : null; } /** * Associate an object to a key on this node. The object can later be * retrieved from this node by calling getUserData with the * same key. * @param key The key to associate the object to. * @param data The object to associate to the given key, or * null to remove any existing association to that key. * @param handler The handler to associate to that key, or * null. * @return Returns the DOMObject previously associated to * the given key on this node, or null if there was none. * @since DOM Level 3 */ public Object setUserData(String key, Object data, UserDataHandler handler) { return ownerDocument().setUserData(this, key, data, handler); } /** * Retrieves the object associated to a key on a this node. The object * must first have been set to this node by calling * setUserData with the same key. * @param key The key the object is associated to. * @return Returns the DOMObject associated to the given key * on this node, or null if there was none. * @since DOM Level 3 */ public Object getUserData(String key) { return ownerDocument().getUserData(this, key); } /** */ protected Hashtable getUserDataRecord(){ return ownerDocument().getUserDataRecord(this); } // // Public methods // /** * NON-DOM: PR-DOM-Level-1-19980818 mentions readonly nodes in conjunction * with Entities, but provides no API to support this. *

* 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. *

* 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.) *

Important Note: 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); } /** * For non-child nodes, the node which "points" to this node. * For example, the owning element for an attribute */ protected Node getContainer() { return null; } /* * 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; } public 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 hasStringValue() { return (flags & HASSTRING) != 0; } final void hasStringValue(boolean value) { flags = (short) (value ? flags | HASSTRING : flags & ~HASSTRING); } final boolean isNormalized() { return (flags & NORMALIZED) != 0; } final void isNormalized(boolean value) { // See if flag should propagate to parent. if (!value && isNormalized() && ownerNode != null) { ownerNode.isNormalized(false); } flags = (short) (value ? flags | NORMALIZED : flags & ~NORMALIZED); } final boolean isIdAttribute() { return (flags & ID) != 0; } final void isIdAttribute(boolean value) { flags = (short) (value ? flags | ID : flags & ~ID); } // // 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