/** * Changes for Persistent DOM running with ozone are * Copyright 1999 by softwarebuero m&b (SMB). All rights reserved. */ package org.ozoneDB.xml.dom; import java.util.*; import java.io.*; import org.ozoneDB.*; import org.ozoneDB.xml.dom.iterator.*; import org.w3c.dom.*; public abstract class NodeImpl extends OzoneObject implements NodeProxy, Externalizable { final static long serialVersionUID = 1; /** * Abstract method must be implemented by each node class. * * @see org.w3c.dom.Node#getNodeType */ public abstract short getNodeType(); /** * Returns the name of the node, set from the constructor. Some derived classes * do not have the notion of a name, and will return the same name each time. * They should do so by setting the default name (e.g. "#comment") * in the constructor. This value is never null. * * @see org.w3c.dom.Node#getNodeName */ public final String getNodeName() { return _nodeName; } /** */ public final void setNodeName (String nodeName) { _nodeName = nodeName; } /** * Returns the value of the node. Depending on the node type, this value * is either the node value (e.g. the text in {@link org.w3c.dom.Text}), * or always null is node has no notion of a value (e.g. {@link * org.w3c.dom.Element}). For complete list of which node types will return * what, see {@link #setNodeValue}. * * @return Value of node, null if node has no value */ public final String getNodeValue() { return _nodeValue; } /** * Changes the value of the node. Not all node types support the notion of * a value. If the value is not supported by a particular node type, it will * throw an exception when calling this method. The following table specifies * which node types support values: *
* Element Not supported * Attr Value supported * Text Value supported * CDATASection Value supported * EntityReference Not supported * Entity Not supported * ProcessingInstruction Value supported * Comment Value supported * Document Not supported * DocumentType Not supported * DocumentFragment Not supported * Notation Not supported ** For most node types, if the value is set to null, {@link #getNodeValue} * will return an empty string instead. * * @param value New value of node * @throws org.w3c.dom.DOMExceptionImpl NO_MODIFICATION_ALLOWED_ERR * Node is read-only and cannot be modified * @throws org.w3c.dom.DOMExceptionImpl NO_DATA_ALLOWED_ERR * This node does not support a value */ public void setNodeValue( String value ) { if ( isReadOnly() ) throw new DOMExceptionImpl( DOMException.NO_MODIFICATION_ALLOWED_ERR ); _nodeValue = value == null ? "" : value; } /** * Returns the parent node of this node. Node may not necessarily have a * parent node. If node has been created but not added to any other node, * it will be parentless. The {@link org.w3c.dom.Document} node is always * parentless. * * @return Parent node of this node */ public Node getParentNode() { return _parent; } /** */ public void setParentNode (Node newParent) { _parent = (NodeProxy)newParent; } /** * Called to notify all the iterators created from this node that a * child of this node has been removed. Iterators that point at this * child might choose to select another child to point to. This method * is called before the child is removed. *
* The removed node is a direct child of this node. Affected iterators * are those that point at the document tree directly below this node, * or the tree below one of its parents. Other iterators are not affected * by the change. This method also performs a notification on all the * parents of this node. * * @param removedChild The child node being removed */ protected void notifyIterators( Node removedChild ) { /* FIXME NodeProxy node; int i; node = this; while ( node != null ) { if ( node._iterators != null ) for ( i = node._iterators.length ; i -- > 0 ; ) ( (NodeIteratorListener) node._iterators[ i ] ).removeNode( removedChild ); node = (NodeProxy) node.getParentNode(); } */ } /** * Returns a {@link org.w3c.dom.NodeList} object that can be used to traverse * this node's children. The node list is live, so every change to this node * is reflected in it. *
* If children are not supported by the derived class, an exception is thrown. * * @return {@link org.w3c.dom.NodeList} on this node * @throws org.w3c.dom.DOMException HIERARCHY_REQUEST_ERR Childern not supported * by this node type * @see org.w3c.dom.NodeList * @see NodeListImpl */ public NodeList getChildNodes() { // Throw exception if children not supported by derived class. if (!supportsChildern ()) throw new DOMExceptionImpl (DOMException.HIERARCHY_REQUEST_ERR, "No childern supported by this node type."); return (NodeList) new org.ozoneDB.xml.dom.NodeListImpl (this); } /** * Returns the first child of the node. If node has no children, returns null. * * @return First child or null */ public final Node getFirstChild() { return _firstChild; } /** * Returns the last child of the node. If node has no children, returns null. * * @return Last child or null */ public final Node getLastChild() { return _lastChild; } /** * Returns the previous sibling of this node. If node has no previous siblings, * returns null. * * @return Previous sibling or null */ public Node getPreviousSibling() { return _prevNode; } /** */ public void setPreviousSibling (Node prevNode) { _prevNode = (NodeProxy)prevNode; } /** * Returns the next sibling of this node. If node has no next siblings, * returns null. * * @return Next sibling or null */ public Node getNextSibling() { return _nextNode; } /** */ public void setNextSibling (Node nextNode) { _nextNode = (NodeProxy)nextNode; } /** * Return attributes of node. Returns null unless node is of type {@link * org.w3c.dom.Element}, in which case the returned {@link * org.w3c.dom.NamedNodeMap} will provide access to all the element's * attributes. * * @return Attributes of node or null */ public NamedNodeMap getAttributes() { return null; } public final Document getOwnerDocument() { if ( _ownerDocument != this ) return _ownerDocument; else return null; } /** * Return true if there are any childern to this node. Less intensive than * calling {#link getChildNodes}. * * @return True if node has any children */ public final boolean hasChildNodes() { return ( _firstChild != null ); } /** * Insert newChild as the last child of this parent. *
* If newChild is null, newChild does not belong to this DOM, * or childern are not supported by this node type, an exception is thrown. *
* newChild is removed from its original parent before adding to this * parent. If newChild is a {@link org.w3c.dom.DocumentFragment}, all * its children are inserted one by one into this parent. * * @param newChild The new child to add * @return The newly inserted child * @throws org.w3c.dom.DOMException NO_MODIFICATION_ALLOWED_ERR * Node is read-only and cannot be modified * @throws org.w3c.dom.DOMException HIERARCHY_REQUEST_ERR * Children are not supported by this node type, or newChild is not * a compatible type for this node * @see #castNewChild * @see #castOldChild */ public final synchronized Node appendChild (Node newChild) { // Node arguments must be casted to NodeEx in order to operate on them. NodeProxy newChildX; // Make sure the node is not read-only. // Throw exception if children not supported by derived class. if ( isReadOnly() ) throw new DOMExceptionImpl( DOMException.NO_MODIFICATION_ALLOWED_ERR ); if ( ! supportsChildern() ) throw new DOMExceptionImpl( DOMException.HIERARCHY_REQUEST_ERR, "No childern supported by this node type." ); // Cast newChild to NodeImpl and make sure it can be inserted to this node. newChildX = (NodeProxy)castNewChild (newChild); // We're going to mess with this child node, so make sure no other thread // is touching it synchronized ( newChild ) { // If the newChild is already a child or some node, remove it first // before becoming child of this node. Make sure that parent is not // read-only. if ( newChildX.getParentNode () != null) { if ( ((NodeProxy)(newChildX.getParentNode())).isReadOnly () ) throw new DOMExceptionImpl( DOMException.NO_MODIFICATION_ALLOWED_ERR ); newChildX.getParentNode().removeChild (newChildX); } // Special case: newChild is a DocumentFragment and instead of adding // itself, all of its childs are added one by one. if ( newChildX instanceof DocumentFragment ) { NodeProxy nextChild; newChildX = (NodeProxy)newChildX.getFirstChild (); while ( newChildX != null ) { nextChild = (NodeProxy)newChildX.getNextSibling (); appendChild ( newChildX ); newChildX = nextChild; } return newChild; } // Node becomes child of this parent and part of this document. // Note that this code comes after the test for a DocumentFragment. // A fragment does not become part of this node, only its children. // The fragment becomes parent-less and child-less. // newChildX._parent = this; newChildX.setParentNode ( this ); if ( _ownerDocument != null ) newChildX.setOwnerDocument( _ownerDocument ); // If the list has no end (it is empty) then newChild is added as the // only child in it. if ( _lastChild == null ) { _lastChild = newChildX; _firstChild = newChildX; newChildX.setPreviousSibling (null); newChildX.setNextSibling (null); } // newChild becomes the new end of the list, adjusting the previous // last child. else { _lastChild.setNextSibling (newChildX); newChildX.setPreviousSibling (_lastChild); newChildX.setNextSibling (null); _lastChild = newChildX; } // Keep this count accurate at all times. ++ _childsCount; } return newChild; } /** * Remove oldChild from this parent. If oldChild is not * a direct child of this parent, or childern are not supported by this node * type, an exception is thrown. * * @param oldChild The child to remove * @return The removed child * @throws org.w3c.dom.DOMException NO_MODIFICATION_ALLOWED_ERR * Node is read-only and cannot be modified * @throws org.w3c.dom.DOMException HIERARCHY_REQUEST_ERR * Children are not supported by this node type * @throws org.w3c.dom.DOMException NOT_FOUND_ERR * oldChild is not a direct child of this node * @see #castOldChild */ public final synchronized Node removeChild( Node oldChild ) throws DOMException { NodeProxy oldChildX; int i; // Make sure the node is not read-only. // Throw exception if children not supported by derived class. if ( isReadOnly() ) throw new DOMExceptionImpl( DOMException.NO_MODIFICATION_ALLOWED_ERR ); if ( ! supportsChildern() ) throw new DOMExceptionImpl( DOMException.HIERARCHY_REQUEST_ERR, "No childern supported by this node type." ); // Cast refChild to NodeImpl, making sure it is a child of this node. oldChildX = (NodeProxy)castOldChild( oldChild ); // We're going to mess with this child node, so make sure no other thread // is touching it synchronized (oldChild) { // Need to tell all the iterators that might be observing the // child node that the child node is removed from the current // tree. The iterators will reflect the changed by selecting // a different child to point to. Interesting iterators are // those the observer the tree underneath this node and all its // parents. notifyIterators (oldChild); // Child becomes orphan. It is no longer first or last child of this // node. Removed from linked list. oldChildX.setParentNode (null); if ( _firstChild != null && _firstChild.equals( oldChildX ) ) _firstChild = (NodeProxy)oldChildX.getNextSibling (); if (_lastChild != null && _lastChild.equals( oldChildX ) ) _lastChild = (NodeProxy)oldChildX.getPreviousSibling (); if ( oldChildX.getPreviousSibling () != null ) ((NodeProxy)oldChildX.getPreviousSibling()).setNextSibling ( oldChildX.getNextSibling() ); if ( oldChildX.getNextSibling () != null ) ((NodeProxy)oldChildX.getNextSibling()).setPreviousSibling ( oldChildX.getPreviousSibling () ); oldChildX.setPreviousSibling (null); oldChildX.setNextSibling (null); // Keep this count accurate at all times. -- _childsCount; } return oldChild; } /** * Replace oldChild with newChild, adding the new child and * removing the old one. *
* If newChild does not belong to this DOM, oldChild is not * a direct child of this parent, or childern are not supported by this node * type, an exception is thrown. *
* newChild is removed from its original parent before adding to this * parent. If newChild is a {@link org.w3c.dom.DocumentFragment}, all * its children are inserted one by one into this parent. * * @param newChild The new child to add * @param oldChild The old child to take away * @return The old child * @throws org.w3c.dom.DOMException NO_MODIFICATION_ALLOWED_ERR * Node is read-only and cannot be modified * @throws org.w3c.dom.DOMException HIERARCHY_REQUEST_ERR * Children are not supported by this node type, or newChild is not * a compatible type for this node * @throws org.w3c.dom.DOMException NOT_FOUND_ERR * oldChild is not a direct child of this node * @see #castNewChild * @see #castOldChild */ public final synchronized Node replaceChild( Node newChild, Node oldChild ) throws DOMException { // Node arguments must be casted to NodeEx in order to operate on them. NodeProxy newChildX; NodeProxy oldChildX; // Make sure the node is not read-only. // Throw exception if children not supported by derived class. if ( isReadOnly() ) throw new DOMExceptionImpl( DOMException.NO_MODIFICATION_ALLOWED_ERR ); if ( ! supportsChildern() ) throw new DOMExceptionImpl( DOMException.HIERARCHY_REQUEST_ERR, "No childern supported by this node type." ); // Cast newChild to NodeImpl and make sure it can be inserted to this node. // Cast oldChild to NodeImpl, making sure it is a child of this node. if ( newChild != null ) newChildX = (NodeProxy)castNewChild( newChild ); oldChildX = (NodeProxy)castOldChild( oldChild ); // We're going to mess with this child node, so make sure no other thread // is touching it synchronized ( oldChild ) { if ( newChild != null ) { // .. or this synchronized ( newChild ) { // Lazy implementation adds newChild before oldChild and then takes // oldChild away. Might be a touch slowed, but is way more reliable. insertBefore ( newChild, oldChild ); removeChild ( oldChild ); } } else // The case of just removing the old child, when the new one // is null. removeChild ( oldChild ); } return oldChild; } /** * Insert newChild in this parent, before the existing child * refChild. If refChild is null, insert newChild * as the last child of this parent, akin to calling {@link #appendChild}. *
* If newChild is null, newChild does not belong to this DOM, * refChild is not a direct child of this node, or childern are not * supported by this node type, an exception is thrown. *
* newChild is removed from its original parent before adding to this * parent. If newChild is a {@link org.w3c.dom.DocumentFragment}, all * its children are inserted one by one into this parent. * * @param newChild The new child to add * @param refChild Insert new child before this child, or insert at the end * if this child is null * @return The newly inserted child * @throws org.w3c.dom.DOMException NO_MODIFICATION_ALLOWED_ERR * Node is read-only and cannot be modified * @throws org.w3c.dom.DOMException HIERARCHY_REQUEST_ERR * Children are not supported by this node type, or newChild is not * a compatible type for this node * @throws org.w3c.dom.DOMException NOT_FOUND_ERR * oldChild is not null and not a direct child of this node * @see #castNewChild * @see #castOldChild */ public final synchronized Node insertBefore( Node newChild, Node refChild ) throws DOMException { // Node arguments must be casted to NodeEx in order to operate on them. NodeProxy newChildX; NodeProxy refChildX; // Make sure the node is not read-only. // Throw exception if children not supported by derived class. if ( isReadOnly() ) throw new DOMExceptionImpl( DOMException.NO_MODIFICATION_ALLOWED_ERR ); if ( ! supportsChildern() ) throw new DOMExceptionImpl( DOMException.HIERARCHY_REQUEST_ERR, "No childern supported by this node type." ); // If refChild is null, act as if appendChild was called. if ( refChild == null ) return appendChild ( newChild ); // Cast newChild to NodeImpl and make sure it can be inserted to this node. // Cast refChild to NodeImpl, making sure it is a child of this node. newChildX = (NodeProxy)castNewChild( newChild ); refChildX = (NodeProxy)castOldChild( refChild ); // We're going to mess with this child node, so make sure no other thread // is touching it synchronized ( newChild ) { // .. or this synchronized ( refChild ) { // If the newChild is already a child or some node, remove it first // before becoming child of this node. Make sure that parent is not // read-only. if ( newChildX.getParentNode () != null ) { if ( ((NodeProxy)newChildX.getParentNode()).isReadOnly ()) throw new DOMExceptionImpl( DOMException.NO_MODIFICATION_ALLOWED_ERR ); newChildX.getParentNode().removeChild ( newChildX ); } // Special case: newChild is a DocumentFragment and instead of // inserting itself, all of its childs are inserted one by one. if ( newChildX instanceof DocumentFragment ) { NodeProxy nextChild; newChildX = (NodeProxy)newChildX.getFirstChild (); while ( newChildX != null ) { nextChild = (NodeProxy)newChildX.getNextSibling (); insertBefore( newChildX, refChild ); newChildX = nextChild; } return newChild; } // Node becomes child of this parent and part of this document. // Note that this code comes after the test for a DocumentFragment. // A fragment does not become part of this node, only its children. // The fragment becomes parent-less and child-less. newChildX.setParentNode (this); newChildX.setOwnerDocument( _ownerDocument ); // If refChild is the first child, newChild becomes the first // child on the list. if ( _firstChild.equals( refChildX ) ) _firstChild = newChildX; // refChild is not the first child, so adjust the previous child // to point at newChild instead. if ( refChildX.getPreviousSibling () != null ) { newChildX.setPreviousSibling ( refChildX.getPreviousSibling() ); ((NodeProxy)refChildX.getPreviousSibling()).setNextSibling ( newChildX ); } // Adjust the refChild to point at this child and vice versa. refChildX.setPreviousSibling ( newChildX ); newChildX.setNextSibling ( refChildX ); // Keep this count accurate at all times. ++ _childsCount; } } return newChild; } /** * Checks whether newChild can be added to this node as a child, and * if so, performs a necessary cast. newChild cannot be null and must * belong to this DOM. It is impossible to transfer nodes between different * DOM implementations. *
* The following rules govern the allowed newChild types: *
* into must be a valid node of the exact same class as this one. * deep is true if deep cloning (includes all children nodes) is to * be performed. If deep is false, the clone might not pass the * equality test. *
* Derived classes override and call this method to add per-class variable * copying. This method is called by {@link #cloneNode} and the default * {@link java.lang.Object#clone} method. *
* Contents cloning duplicates the node's name and value, and its children. * It does not duplicate it's context, that is, the node's parent or sibling. * Initially a clone node has no parents or siblings. However, the node does * belong to the same document, since all nodes must belong to some document. * The cloned node is never read-only. * * @param into A node into which to duplicate this one * @param deep True if deep cloning is required */ public synchronized void cloneInto (NodeProxy into, boolean deep) { NodeProxy child; // Make sure no function messed up with the class types. if ( this.getClass() != into.getClass() ) throw new IllegalArgumentException( "Argument 'into' not same type as this node." ); // Duplicate node name and value. into.setNodeName (_nodeName); into.setNodeValue (_nodeValue); into.setOwnerDocument (_ownerDocument); if ( deep ) { child = (NodeProxy)getFirstChild(); while (child != null) { into.appendChild ((Node)child.cloneNode (true)); child = (NodeProxy)child.getNextSibling(); } } } public synchronized void setOwnerDocument( Document owner ) { Node node; if ( owner == null ) _ownerDocument = null; else { if ( ! ( owner instanceof DocumentProxy ) ) throw new IllegalArgumentException( "Argument 'owner' not of compatible DOM class." ); _ownerDocument = (DocumentProxy) owner; } node = getFirstChild (); while ( node != null ) { ( (NodeProxy) node ).setOwnerDocument( owner ); node = node.getNextSibling(); } } /** * Renders this node read only, preventing it's contents from being modified. * Attempts to modify the node's contents will throw an exception. The node's * children are also made read-only. */ public final synchronized void setReadOnly() { NodeProxy child; _readOnly = true; // Make all children read-only as well: this allows us to lock a branch // but, for example, move it to a different tree. child = (NodeProxy)getFirstChild(); while ( child != null ) { child.setReadOnly(); child = (NodeProxy)child.getNextSibling(); } } /** * Returns true if node is read-only and cannot be modified, or if node * belongs to a read-only document. * * @return True if node is read-only and cannot be modified * @see #setReadOnly */ public final boolean isReadOnly() { return _readOnly; } /** * Returns true if this node supports children. Other methods query this to * determine whether to properly support childern, return null or throw an * exception in response. The default method returns false. * * @return True if childern supported by this node type */ boolean supportsChildern() { return false; } /** * Returns the index-th child of this node. This method is used * exclusively by {@link NodeListImpl}. * * @param index Index of child to retrieve * @return The child node or null * @see NodeListImpl#item(int) */ public final synchronized Node getChild( int index ) { NodeProxy node; if ( index < 0 || index > _childsCount ) return null; node = (NodeProxy)getFirstChild(); while ( node != null && index > 0 ) { node = (NodeProxy)node.getNextSibling(); --index; } return node; } /** * Returns the number of children in this node. This method is used * exclusively by {@link NodeListImpl}. * * @return Number of childern in this node * @see NodeListImpl#getLength */ public final int getChildCount() { return _childsCount; } /** * Hidden constructor creates a new node. Only one constructor is supported, * although cloning is also supported. Owner document must be supplied except * for {@link DocumentImpl} in which case the document itself becomes its * owner. Name must be supplied, either dynamic or static (e.g. "#document#"). *
* If checkName is true, the supplied named is assumed to be a valid
* XML name token, one that can contain any Unicode letter and digit, must
* start with a letter, and may also contain hyphen, underscore, digit or colon.
*
* @param owner Document owner of this node, or null
* @param name Name of node
* @param value Initial value of node or null
* @param checkName True if name is an XML name token
* @throws org.w3c.dom.DOMException INVALID_CHARACTER_ERR
* Node name cannot contain whitespaces or non-printable characters
*/
protected NodeImpl (DocumentImpl owner, String name, String value,
boolean checkName) throws DOMException {
init (owner, name, value, checkName);
}
protected NodeImpl () {
}
public final void init (DocumentProxy owner, String name, String value,
boolean checkName) throws DOMException {
char ch;
int i;
if ( name == null )
throw new NullPointerException( "Argument 'name' is null." );
_nodeName = name;
_ownerDocument = owner;
// Check the node name one character at a time to assure that no
// illegal characters are used. Node name must conform to Name token
// as defined in XML spec, including use of all Unicode letters and
// digits.
if (checkName && name.length () > 0) {
ch = name.charAt( 0 );
if ( ! Character.isLetter( ch ) && ch != '_' && ch != ':' )
throw new DOMExceptionImpl( DOMException.INVALID_CHARACTER_ERR );
for ( i = 1 ; i < name.length() ; ++i ) {
ch = name.charAt( 1 );
if ( ! Character.isLetterOrDigit( ch ) &&
ch != '_' && ch != ':' && ch != '-' && ch != '.' )
throw new DOMExceptionImpl (DOMException.INVALID_CHARACTER_ERR);
}
}
if (value != null)
setNodeValue (value);
}
/**
* Element declaration node. Not part of the DOM, identifies an element
* declaration node appearing in the DTD.
*/
public static final short ELEMENT_DECL_NODE = 13;
/**
* Attributes list declaration node. Not part of the DOM, identifies an
* attributes list declaration node appearing in the DTD..
*/
public static final short ATTLIST_DECL_NODE = 14;
/**
* Parameter entity declaration node. Not part of the DOM, identifies an
* internal or external parameter entity declaration node appearing in the
* DTD (see {@link org.openxml.dom.ParamEntity}).
*/
public static final short PARAM_ENTITY_NODE = 15;
/**
* This node ia part of a double-linked list that belongs to its parent.
* This reference identifies the next child in the list. Class access
* required by derived classes.
*/
NodeProxy _nextNode;
/**
* This node ia part of a double-linked list that belongs to its parent.
* This reference identifies the previous child in the list. Class access
* required by derived classes.
*/
NodeProxy _prevNode;
/**
* The parent of this node or null if the node has no parent. Class access
* required by derived classes.
*/
NodeProxy _parent;
/**
* The document owner of this node, or the document itself. If the node belongs
* to any document, this will point to that document. For a document this will
* point at the document itself ({@link #getOwnerDocument} will return null,
* though). Class access required by derived classes.
*/
DocumentProxy _ownerDocument;
/**
* The name of this node. All nodes have names, some are dynamic (e.g. the
* tag name of an element), others are static (e.g. "#document").
*/
private String _nodeName;
/**
* The value of this node. Not all nodes support values and this might be
* null for some nodes.
*/
private String _nodeValue;
/**
* The children of this node are arranged in a doubly linked lists.
* This reference identifies the first child in the list.
*/
private NodeProxy _firstChild;
/**
* The children of this node are arranged in a doubly linked lists.
* This reference identifies the last child in the list.
*/
private NodeProxy _lastChild;
/**
* Counts how many children nodes belong to this parent. Used to speed up
* some checks.
*/
private int _childsCount;
/**
* True if this node is read-only and its contents cannot be modified.
*/
private boolean _readOnly;
/**
* Holdes a list of iterators that are observing this node of its
* childern.
*/
private NodeIteratorListener[] _iterators;
/**
*/
public void writeExternal (ObjectOutput out) throws IOException {
out.writeObject (_nextNode);
out.writeObject (_prevNode);
out.writeObject (_parent);
out.writeObject (_ownerDocument);
out.writeObject (_nodeName);
out.writeObject (_nodeValue);
out.writeObject (_firstChild);
out.writeObject (_lastChild);
out.writeInt (_childsCount);
out.writeBoolean (_readOnly);
if (_iterators != null) {
int len = _iterators.length;
out.writeInt (len);
for (int i=0; i