/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Id: NodeImpl.java 518460 2007-03-15 03:47:19Z vgritsenko $
*/
package org.apache.xindice.xml.dom;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.util.StringUtilities;
import org.apache.xindice.xml.NodeSource;
import org.apache.xindice.xml.TextWriter;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.UserDataHandler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* NodeImpl implements the foundation of the Xindice compressed DOM.
*
* @version $Revision: 518460 $, $Date: 2007-03-14 23:47:19 -0400 (Wed, 14 Mar 2007) $
*/
public abstract class NodeImpl implements CompressedNode, DBNode {
private static final Log log = LogFactory.getLog(NodeImpl.class);
public static final String XMLNS_PREFIX = "xmlns";
public static final String OBJECT_NS = "http://xml.apache.org/xindice/XMLObject";
public static final String OBJECT_HREF = "href";
public static final String OBJECT_TYPE = "type";
public static final String TYPE_CONTENT = "content";
public static final String TYPE_REPLACE = "replace";
public static final String TYPE_INSERT = "insert";
public static final String TYPE_APPEND = "append";
public static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/";
// Static Exception Instances
public static final DOMException EX_NO_MODIFICATION_ALLOWED =
new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, "This Node Is Read-Only");
public static final DOMException EX_INUSE_ATTRIBUTE =
new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, "This Attribute Belongs To Another Element");
public static final DOMException EX_WRONG_DOCUMENT =
new DOMException(DOMException.WRONG_DOCUMENT_ERR, "This Attribute Belongs To Another Document");
public static final DOMException EX_NOT_FOUND =
new DOMException(DOMException.NOT_FOUND_ERR, "This Node Does Not Belong To This Element");
public static final DOMException EX_HIERARCHY_REQUEST =
new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "This Node Cannot Contain This Child");
public static final DOMException EX_NO_DATA_ALLOWED =
new DOMException(DOMException.NO_DATA_ALLOWED_ERR, "This Node Has No Value");
public static final DOMException EX_INVALID_STATE =
new DOMException(DOMException.INVALID_STATE_ERR, "NodeIterator Has Been Detached");
public static final DOMException EX_DOMSTRING_SIZE =
new DOMException(DOMException.DOMSTRING_SIZE_ERR, "String Too Large For Type");
public static final DOMException EX_INDEX_SIZE =
new DOMException(DOMException.INDEX_SIZE_ERR, "Index Out Of Bounds");
// DocumentPosition flags
/**
* The two nodes are disconnected. Order between disconnected nodes is
* always implementation-specific.
*/
public static final short DOCUMENT_POSITION_DISCONNECTED = 0x01;
/**
* The second node precedes the reference node.
*/
public static final short DOCUMENT_POSITION_PRECEDING = 0x02;
/**
* The node follows the reference node.
*/
public static final short DOCUMENT_POSITION_FOLLOWING = 0x04;
/**
* The node contains the reference node. A node which contains is always
* preceding, too.
*/
public static final short DOCUMENT_POSITION_CONTAINS = 0x08;
/**
* The node is contained by the reference node. A node which is contained
* is always following, too.
*/
public static final short DOCUMENT_POSITION_CONTAINED_BY = 0x10;
/**
* The determination of preceding versus following is
* implementation-specific.
*/
public static final short DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;
// Serialization Stuff
protected NodeSource source;
protected byte[] data;
protected int pos;
protected int len;
protected boolean loaded; // Have the definitions for this node been loaded?
protected boolean dirty; // Has this node been modified?
// DOM Level 1 Stuff
protected NodeImpl parentNode;
protected String nodeName;
protected String nodeValue;
// DOM Level 2 Stuff
protected Document ownerDocument; // The Document object associated with this node
protected String nsURI; // Just for caching so we don't have to walk the tree
// DOM Level 3 Stuff
protected HashMap userData;
protected HashMap handlers;
protected Object key;
/**
* Create empty node
*/
public NodeImpl() {
}
/**
* Create node from compressed data
*
* @param parentNode the parent node
* @param data compressed document data byte array
* @param pos offset in the data byte array
* @param len length of node's data
*/
public NodeImpl(NodeImpl parentNode, byte[] data, int pos, int len) {
this.parentNode = parentNode;
this.data = data;
this.pos = pos;
this.len = len;
if (parentNode == null) {
ownerDocument = null;
} else if (parentNode.getNodeType() == DOCUMENT_TYPE_NODE) {
this.ownerDocument = (Document) parentNode;
} else {
this.ownerDocument = parentNode.getOwnerDocument();
}
}
public NodeImpl(NodeImpl parentNode, boolean dirty) {
this.parentNode = parentNode;
if (parentNode == null) {
ownerDocument = null;
} else if (parentNode.getNodeType() == DOCUMENT_TYPE_NODE) {
this.ownerDocument = (Document) parentNode;
} else {
this.ownerDocument = parentNode.getOwnerDocument();
}
if (dirty) {
setDirty();
}
}
public final boolean isLoaded() {
return loaded;
}
protected void checkLoaded() {
if (!loaded) {
loaded = true;
}
}
public void load() {
checkLoaded();
}
public void unload() {
if (loaded) {
loaded = false;
}
}
public short getSymbolID() {
return -1;
}
public final void checkReadOnly() throws DOMException {
DocumentImpl doc = (DocumentImpl) getOwnerDocument();
if (doc != null && doc.isReadOnly()) {
throw EX_NO_MODIFICATION_ALLOWED;
}
}
public final boolean isDefined() {
return data != null;
}
public final boolean isDirty() {
return dirty;
}
public final void setDirty() {
dirty = true;
if (parentNode != null) {
parentNode.setDirty();
}
}
public byte[] getDataBytes() {
return data;
}
public void setDataBytes(byte[] data, int pos, int len) {
this.data = data;
this.pos = pos;
this.len = len;
}
public void setDataBytes(byte[] data) {
this.data = data;
}
public int getDataPos() {
return pos;
}
public void setDataPos(int pos) {
this.pos = pos;
}
public int getDataLen() {
return len;
}
public void setDataLen(int len) {
this.len = len;
}
public final void setNodeName(String nodeName) {
checkLoaded();
this.nodeName = nodeName;
setDirty();
}
public final void setParentNode(NodeImpl parentNode) {
this.parentNode = parentNode;
}
protected Node getPreviousSibling(Node node) {
return null;
}
protected Node getNextSibling(Node node) {
return null;
}
public final void setSource(NodeSource source) {
this.source = source;
}
public final NodeSource getSource() {
if (source == null && parentNode != null) {
return parentNode.getSource();
} else {
return source;
}
}
public void expandSource() {
}
// Some DOM Level 1 Core Methods
/**
* A code representing the type of the underlying object, as defined above.
*/
public abstract short getNodeType();
/**
* The last child of this node. If there is no such node, this returns
* <code>null</code>.
*/
public Node getLastChild() {
return null;
}
/**
* The node immediately preceding this node. If there is no such node, this
* returns <code>null</code>.
*/
public final Node getPreviousSibling() {
return parentNode != null ? parentNode.getPreviousSibling(this)
: null;
}
/**
* The node immediately following this node. If there is no such node, this
* returns <code>null</code>.
*/
public final Node getNextSibling() {
return parentNode != null ? parentNode.getNextSibling(this)
: null;
}
/**
* A <code>NamedNodeMap</code> containing the attributes of this node (if it
* is an <code>Element</code>) or <code>null</code> otherwise.
*/
public NamedNodeMap getAttributes() {
return null;
}
public boolean hasAttributes() {
return false;
}
/**
* The <code>Document</code> object associated with this node. This is also
* the <code>Document</code> object used to create new nodes. When this
* node is a <code>Document</code> this is <code>null</code>.
*/
public final Document getOwnerDocument() {
if (getNodeType() == Node.DOCUMENT_NODE) {
return (Document) this;
}
return ownerDocument;
}
/**
* Inserts the node <code>newChild</code> before the existing child node
* <code>refChild</code>. If <code>refChild</code> is <code>null</code>,
* insert <code>newChild</code> at the end of the list of children.
* <br>If <code>newChild</code> is a <code>DocumentFragment</code> object,
* all of its children are inserted, in the same order, before
* <code>refChild</code>. If the <code>newChild</code> is already in the
* tree, it is first removed.
* @param newChild The node to insert.
* @param refChild The reference node, i.e., the node before which the new
* node must be inserted.
* @return The node being inserted.
* @exception DOMException
* HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not
* allow children of the type of the <code>newChild</code> node, or if
* the node to insert is one of this node's ancestors.
* <br>WRONG_DOCUMENT_ERR: Raised if <code>newChild</code> was created
* from a different document than the one that created this node.
* <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
* <br>NOT_FOUND_ERR: Raised if <code>refChild</code> is not a child of
* this node.
*/
public Node insertBefore(Node newChild, Node refChild) throws DOMException {
throw EX_HIERARCHY_REQUEST;
}
/**
* A <code>NodeList</code> that contains all children of this node. If there
* are no children, this is a <code>NodeList</code> containing no nodes.
* The content of the returned <code>NodeList</code> is "live" in the sense
* that, for instance, changes to the children of the node object that
* it was created from are immediately reflected in the nodes returned by
* the <code>NodeList</code> accessors; it is not a static snapshot of the
* content of the node. This is true for every <code>NodeList</code>,
* including the ones returned by the <code>getElementsByTagName</code>
* method.
*/
public NodeList getChildNodes() {
return new NodeListImpl(this);
}
/**
* The value of this node, depending on its type; see the table above.
* When it is defined to be <code>null</code> , setting it has no effect.
* @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 <code>DOMString</code> variable on the implementation
* platform.
*/
public void setNodeValue(String nodeValue) throws DOMException {
throw EX_NO_DATA_ALLOWED;
}
/**
* The parent of this node. All nodes, except <code>Document</code>,
* <code>DocumentFragment</code>, and <code>Attr</code> may have a parent.
* However, if a node has just been created and not yet added to the tree,
* or if it has been removed from the tree, this is <code>null</code>.
*/
public final Node getParentNode() {
return parentNode;
}
/**
* The first child of this node. If there is no such node, this returns
* <code>null</code>.
*/
public Node getFirstChild() {
return null;
}
/**
* The name of this node, depending on its type; see the table above.
*/
public String getNodeName() {
checkLoaded();
return nodeName;
}
/**
* The value of this node, depending on its type; see the table above.
* @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 <code>DOMString</code> variable on the implementation
* platform.
*/
public String getNodeValue() throws DOMException {
checkLoaded();
return nodeValue;
}
/**
* Replaces the child node <code>oldChild</code> with <code>newChild</code>
* in the list of children, and returns the <code>oldChild</code> node. If
* the <code>newChild</code> is already in the tree, it is first removed.
* @param newChild The new node to put in the child list.
* @param oldChild The node being replaced in the list.
* @return The node replaced.
* @exception DOMException
* HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not
* allow children of the type of the <code>newChild</code> node, or it
* the node to put in is one of this node's ancestors.
* <br>WRONG_DOCUMENT_ERR: Raised if <code>newChild</code> was created
* from a different document than the one that created this node.
* <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
* <br>NOT_FOUND_ERR: Raised if <code>oldChild</code> is not a child of
* this node.
*/
public synchronized Node replaceChild(Node newChild, Node oldChild) throws DOMException {
throw EX_HIERARCHY_REQUEST;
}
/**
* Removes the child node indicated by <code>oldChild</code> from the list
* of children, and returns it.
* @param oldChild The node being removed.
* @return The node removed.
* @exception DOMException
* NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
* <br>NOT_FOUND_ERR: Raised if <code>oldChild</code> is not a child of
* this node.
*/
public synchronized Node removeChild(Node oldChild) throws DOMException {
throw EX_NOT_FOUND;
}
/**
* Adds the node <code>newChild</code> to the end of the list of children of
* this node. If the <code>newChild</code> is already in the tree, it is
* first removed.
* @param newChild The node to add.If it is a <code>DocumentFragment</code>
* object, the entire contents of the document fragment are moved into
* the child list of this node
* @return The node added.
* @exception DOMException
* HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not
* allow children of the type of the <code>newChild</code> node, or if
* the node to append is one of this node's ancestors.
* <br>WRONG_DOCUMENT_ERR: Raised if <code>newChild</code> was created
* from a different document than the one that created this node.
* <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
*/
public synchronized Node appendChild(Node newChild) throws DOMException {
throw EX_HIERARCHY_REQUEST;
}
/**
* This is a convenience method to allow easy determination of whether a
* node has any children.
* @return <code>true</code> if the node has any children,
* <code>false</code> if the node has no children.
*/
public boolean hasChildNodes() {
return false;
}
/**
* Returns a duplicate of this node, i.e., serves as a generic copy
* constructor for nodes. The duplicate node has no parent (
* <code>parentNode</code> returns <code>null</code>.).
* <br>Cloning an <code>Element</code> copies all attributes and their
* values, including those generated by the XML processor to represent
* defaulted attributes, but this method does not copy any text it contains
* unless it is a deep clone, since the text is contained in a child
* <code>Text</code> node. Cloning any other type of node simply returns a
* copy of this node.
* @param deep If <code>true</code>, recursively clone the subtree under the
* specified node; if <code>false</code>, clone only the node itself (and
* its attributes, if it is an <code>Element</code>).
* @return The duplicate node.
*/
public final Node cloneNode(boolean deep) {
return cloneNode(deep, true);
}
protected final synchronized Node cloneNode(boolean deep, boolean invokeHandler) {
DocumentImpl doc = (DocumentImpl) getOwnerDocument();
// compressed documents
if (deep && this.data != null) {
byte[] data = this.data;
int pos = this.pos;
int len = this.len;
if (dirty) {
data = DOMCompressor.compress(this, doc.getSymbols());
pos = 0;
len = data.length;
}
NodeImpl newNode = null;
switch (getNodeType()) {
case Node.ATTRIBUTE_NODE:
newNode = new AttrImpl(this, true);
newNode.setNodeName(getNodeName());
newNode.setNodeValue(getNodeValue());
break;
case Node.CDATA_SECTION_NODE:
newNode = new CDATASectionImpl(this, data, pos, len);
break;
case Node.COMMENT_NODE:
newNode = new CommentImpl(this, data, pos, len);
break;
case Node.DOCUMENT_FRAGMENT_NODE:
DocumentFragmentImpl df = new DocumentFragmentImpl(this);
NodeList nl = getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
df.appendChild(nl.item(i).cloneNode(deep));
}
newNode = df;
break;
case Node.ELEMENT_NODE:
newNode = new ElementImpl(this, data, pos, len);
newNode.setNodeName(getNodeName());
break;
case Node.ENTITY_REFERENCE_NODE:
newNode = new EntityReferenceImpl(this, data, pos, len);
break;
case Node.NOTATION_NODE:
newNode = new NotationImpl(this, data, pos, len);
break;
case Node.PROCESSING_INSTRUCTION_NODE:
newNode = new ProcessingInstructionImpl(this, data, pos, len);
break;
case Node.TEXT_NODE:
newNode = new TextImpl(this, data, pos, len);
break;
default:
if (log.isWarnEnabled()) {
log.warn("invalid node type : " + getNodeType());
}
}
if (newNode != null) {
// cloned node must not have a parent
newNode.parentNode = null;
if (invokeHandler) {
invokeHandlers(UserDataHandler.NODE_CLONED, this, newNode);
}
return newNode;
}
}
checkLoaded();
NodeImpl node = null;
switch (getNodeType()) {
case Node.ATTRIBUTE_NODE:
AttrImpl attr = (AttrImpl) doc.createAttribute(nodeName);
attr.setValue(nodeValue);
node = attr;
break;
case Node.CDATA_SECTION_NODE:
node = (NodeImpl) doc.createCDATASection(nodeValue);
break;
case Node.COMMENT_NODE:
node = (NodeImpl) doc.createComment(nodeValue);
break;
case Node.DOCUMENT_FRAGMENT_NODE:
node = (NodeImpl) doc.createDocumentFragment();
break;
case Node.ELEMENT_NODE:
ElementImpl elem = (ElementImpl) doc.createElement(nodeName);
NamedNodeMap attrs = getAttributes();
int size = attrs.getLength();
for (int i = 0; i < size; i++) {
Attr a = (Attr) attrs.item(i);
elem.setAttribute(a.getName(), a.getValue());
}
if (getPrefix() != null) {
elem.nsURI = lookupNamespaceURI(getPrefix());
} else {
elem.nsURI = lookupDefaultNamespaceURI();
}
node = elem;
break;
case Node.ENTITY_REFERENCE_NODE:
node = (NodeImpl) doc.createEntityReference(nodeValue);
break;
case Node.PROCESSING_INSTRUCTION_NODE:
node = (NodeImpl) doc.createProcessingInstruction(nodeName, nodeValue);
break;
case Node.TEXT_NODE:
node = (NodeImpl) doc.createTextNode(nodeValue);
break;
default:
if (log.isWarnEnabled()) {
log.warn("invalid node type : " + getNodeType());
}
break;
}
if (node != null && deep) {
NodeList list = getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
node.appendChild(list.item(i).cloneNode(deep));
}
}
if (node != null && invokeHandler) {
node.parentNode = null;
invokeHandlers(UserDataHandler.NODE_CLONED, this, node);
}
return node;
}
// Some DOM Level 2 Core Methods
/**
* 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 neither adjacent
* <code>Text</code> nodes nor empty <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.
* @since DOM Level 2
*/
public void normalize() {
}
/**
* Tests whether the DOM implementation implements a specific feature and
* that feature is supported by this node.
* @param feature The name of the feature to test. This is the same name
* which can be passed to the method <code>hasFeature</code> on
* <code>DOMImplementation</code> .
* @param version This is the version number of the feature 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 <code>true</code> .
* @return Returns <code>true</code> if the specified feature is supported
* on this node, <code>false</code> otherwise.
* @since DOM Level 2
*/
public final boolean isSupported(String feature, String version) {
return DOMImplementationImpl.HasFeature(feature, version);
}
/**
* The namespace URI of this node, or <code>null</code> if it is
* unspecified.
* <br> 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.
* <br> For nodes of any type other than <code>ELEMENT_NODE</code> and
* <code>ATTRIBUTE_NODE</code> and nodes created with a DOM Level 1
* method, such as <code>createElement</code> from the
* <code>Document</code> interface, this is always <code>null</code> .
* Per the Namespaces in XML Specification an attribute does not
* inherit its namespace from the element it is attached to. If an
* attribute is not explicitly given a namespace, it simply has no
* namespace.
* @since DOM Level 2
*/
public final String getNamespaceURI() {
short nodeType = getNodeType();
if ((nodeType == Node.ATTRIBUTE_NODE)
&& (nodeName.equals(XMLNS_PREFIX) || nodeName.startsWith(XMLNS_PREFIX + ":"))) {
return XMLNS_URI;
}
if (nsURI != null) {
return nsURI;
}
if (!(nodeType == Node.ELEMENT_NODE || nodeType == Node.ATTRIBUTE_NODE)) {
return null;
}
String prefix = getPrefix();
return prefix != null ? lookupNamespaceURI(prefix)
: lookupDefaultNamespaceURI();
}
/**
* The namespace prefix of this node, or <code>null</code> if it is
* unspecified.
* <br> Note that setting this attribute, when permitted, changes the
* <code>nodeName</code> attribute, which holds the qualified name , as
* well as the <code>tagName</code> and <code>name</code> attributes of
* the <code>Element</code> and <code>Attr</code> interfaces, when
* applicable.
* <br> Note also that changing the prefix of an attribute that is known to
* have a default value, does not make a new attribute with the default
* value and the original prefix appear, since the
* <code>namespaceURI</code> and <code>localName</code> do not change.
* @exception DOMException
* INVALID_CHARACTER_ERR: Raised if the specified prefix contains an
* illegal character.
* <br> NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
* <br> NAMESPACE_ERR: Raised if the specified <code>prefix</code> is
* malformed, if the <code>namespaceURI</code> of this node is
* <code>null</code> , if the specified prefix is "xml" and the
* <code>namespaceURI</code> of this node is different from
* "http://www.w3.org/XML/1998/namespace", if this node is an attribute
* and the specified prefix is "xmlns" and the <code>namespaceURI</code>
* of this node is different from "http://www.w3.org/2000/xmlns/", or
* if this node is an attribute and the <code>qualifiedName</code> of
* this node is "xmlns" .
* @since DOM Level 2
*/
public final String getPrefix() {
short nodeType = getNodeType();
if (!(nodeType == Node.ELEMENT_NODE || nodeType == Node.ATTRIBUTE_NODE)) {
return null;
}
checkLoaded();
int idx = nodeName.indexOf(':');
return idx != -1 ? nodeName.substring(0, idx)
: null;
}
/**
* The namespace prefix of this node, or <code>null</code> if it is
* unspecified.
* <br> Note that setting this attribute, when permitted, changes the
* <code>nodeName</code> attribute, which holds the qualified name , as
* well as the <code>tagName</code> and <code>name</code> attributes of
* the <code>Element</code> and <code>Attr</code> interfaces, when
* applicable.
* <br> Note also that changing the prefix of an attribute that is known to
* have a default value, does not make a new attribute with the default
* value and the original prefix appear, since the
* <code>namespaceURI</code> and <code>localName</code> do not change.
* @exception DOMException
* INVALID_CHARACTER_ERR: Raised if the specified prefix contains an
* illegal character.
* <br> NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
* <br> NAMESPACE_ERR: Raised if the specified <code>prefix</code> is
* malformed, if the <code>namespaceURI</code> of this node is
* <code>null</code> , if the specified prefix is "xml" and the
* <code>namespaceURI</code> of this node is different from
* "http://www.w3.org/XML/1998/namespace", if this node is an attribute
* and the specified prefix is "xmlns" and the <code>namespaceURI</code>
* of this node is different from "http://www.w3.org/2000/xmlns/", or
* if this node is an attribute and the <code>qualifiedName</code> of
* this node is "xmlns" .
* @since DOM Level 2
*/
public final void setPrefix(String prefix) {
short nodeType = getNodeType();
if (!(nodeType == Node.ELEMENT_NODE || nodeType == Node.ATTRIBUTE_NODE)) {
return;
}
checkReadOnly();
String ln = getLocalName();
if (prefix != null) {
setNodeName(prefix + ':' + ln);
} else {
setNodeName(ln);
}
}
/**
* Returns the local part of the qualified name of this node.
* <br> For nodes created with a DOM Level 1 method, such as
* <code>createElement</code> from the <code>Document</code> interface,
* it is <code>null</code> .
* @since DOM Level 2
*/
public final String getLocalName() {
short nodeType = getNodeType();
if (!(nodeType == Node.ELEMENT_NODE || nodeType == Node.ATTRIBUTE_NODE)) {
return null;
}
String prefix = getPrefix();
return prefix != null ? getNodeName().substring(prefix.length() + 1)
: getNodeName();
}
/**
* getDefaultNamespaceURI returns the default Namespace URI in this
* scope.
*
* @return The URI (or null)
*/
public final String lookupDefaultNamespaceURI() {
if (getNodeType() != Node.ELEMENT_NODE) {
return null;
}
String uri = ((Element) this).getAttribute(XMLNS_PREFIX);
if (uri != null && uri.length() > 0) {
return uri;
}
return parentNode != null ? parentNode.lookupDefaultNamespaceURI()
: null;
}
//
// DOM Level 3 Implementation
//
/**
* Returns whether this node is the same node as the given one.
* @param other The node to test against.
* @return Returns <code>true</code> if the nodes are the same,
* <code>false</code> otherwise.
* @since DOM Level 3
*/
public final boolean isSameNode(Node other) {
return this == other;
}
/**
* Look up the prefix associated to the given namespace URI, starting from
* this node. The default namespace declarations are ignored by this method.
* @param namespaceURI The namespace URI to look for.
* @return Returns the associated namespace prefix or <code>null</code>
* if none is found.
* @since DOM Level 3
*/
public final String lookupPrefix(String namespaceURI) {
if (getNodeType() == Node.ELEMENT_NODE) {
NamedNodeMap map = getAttributes();
int size = map.getLength();
for (int i = 0; i < size; i++) {
Attr attr = (Attr) map.item(i);
String name = attr.getName();
if (name.startsWith(XMLNS_PREFIX + ':')
&& attr.getValue().equals(namespaceURI)) {
return name.substring(XMLNS_PREFIX.length() + 1);
}
}
}
return parentNode != null ? parentNode.lookupPrefix(namespaceURI) : null;
}
/**
* Look up the namespace URI associated to the given prefix.
*
* Starts from this node.Name. May need to change depending on ending of the
* relative namespace URI reference nightmare.
* @param prefix The prefix to look for.
* @return Returns the associated namespace URI or <code>null</code> if
* none is found.
* @since DOM Level 3
*/
public final String lookupNamespaceURI(String prefix) {
String uri = null;
if (getNodeType() == Node.ELEMENT_NODE) {
uri = ((Element) this).getAttribute(XMLNS_PREFIX + ':' + prefix);
}
if (uri != null && uri.length() > 0) {
return uri;
}
return parentNode != null ? parentNode.lookupNamespaceURI(prefix) : null;
}
/**
* This method allows to attach a user object to a Node. This object can
* then be retreived using <code>getUserData</code>.Is this really worth
* it?Could we live without a key?What happens if the node is cloned,
* imported, adopted? Should some event mechanism be specified to notify
* the application?What happens if the node is cloned?What should Object
* be mapped to in ECMAScript? For IDL we need to define this type
* somehow.
* @param data The piece of data to attach to this node.
* @param key The key to associate this data to.
* @return The object previously associated to this node and the given
* key or <code>null</code>.
* @since DOM Level 3
*/
public final synchronized Object setUserData(String key, Object data, UserDataHandler handler) {
if (userData == null) {
userData = new HashMap();
}
if (handlers == null) {
handlers = new HashMap();
}
Object oldData = userData.get(key);
if (data != null) {
userData.put(key, data);
if (handler != null) {
handlers.put(key, handler);
}
} else {
userData.remove(key);
handlers.remove(key);
}
return oldData;
}
/**
* This method allows to retreive a user object previously attached to a
* Node with <code>setUserData</code>.
* @param key The key to look for.
* @return The object associated to this node and the given key or
* <code>null</code>.
* @since DOM Level 3
*/
public final synchronized Object getUserData(String key) {
if (userData == null) {
return null;
}
return userData.get(key);
}
Node renameNode(String namespaceURI, String qualifiedName, String prefix) throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Only Element and Attribute nodes can be renamed.");
}
/**
* @since DOM Level 3
*/
public String getBaseURI() {
return null;
}
/**
* This method returns the text content of this node and its descendants.
* No serialization is performed, the returned string does not contain any
* markup. No whitespace normalization is performed and the returned string
* does not contain the white spaces in element content (see the attribute
* Text.isElementContentWhitespace).
* <br>
* The string returned is made of the text content of this node depending on
* its type, as defined below:
* <table border=1><tr><th>Node type</th><th>Content</th></tr>
* <tr><td>ELEMENT_NODE, ATTRIBUTE_NODE, ENTITY_NODE, ENTITY_REFERENCE_NODE,
* DOCUMENT_FRAGMENT_NODE</td>
* <td>concatenation of the textContent attribute value of every child node,
* excluding COMMENT_NODE and PROCESSING_INSTRUCTION_NODE nodes. This is the
* empty string if the node has no children.</td></tr>
* <tr><td>TEXT_NODE, CDATA_SECTION_NODE, COMMENT_NODE,
* PROCESSING_INSTRUCTION_NODE</td><td>nodeValue</td></tr>
* <tr><td>DOCUMENT_NODE, DOCUMENT_TYPE_NODE, NOTATION_NODE</td>
* <td>null</td></tr></table>
* @return the text content of this node and its descendants
* @since DOM Level 3
*/
public String getTextContent() {
return getNodeValue();
}
/**
* This method changes the text content of this node. When it is defined to be
* null, setting it has no effect. On setting, any possible children this node
* may have are removed and, if it the new string is not empty or null, replaced
* by a single Text node containing the string this attribute is set to.
* No parsing is performed, the input string is taken as pure textual content.
* @param textContent new text content
* @throws DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly
*/
public void setTextContent(String textContent) throws DOMException {
checkReadOnly();
checkLoaded();
if (textContent == null || "".equals(textContent)) return;
// remove all child nodes, if any
if (hasChildNodes()) {
int num = getChildNodes().getLength();
for (int i = 0; i < num; i++) {
removeChild(getChildNodes().item(0));
}
}
appendChild(new TextImpl(this, textContent));
}
/**
* Tests whether two nodes are equal.
* This 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
* <code>Node.isSameNode()</code>. All nodes that are the same will also be
* equal, though the reverse may not be true.
* <br>
* Two nodes are equal if and only if the following conditions are satisfied:
* <ul><li>The two nodes are of the same type.</li>
* <li>The following string attributes are equal: nodeName, localName,
* namespaceURI, prefix, nodeValue. This is: they are both null, or they have
* the same length and are character for character identical.</li>
* <li>The attributes <code>NamedNodeMaps</code> 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.</li>
* <li>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. Note that
* normalization can affect equality; to avoid this, nodes should be normalized
* before being compared.</li></ul>
* <br>
* For two DocumentType nodes to be equal, the following conditions must also
* be satisfied:
* <ul><li>The following string attributes are equal: publicId, systemId,
* internalSubset.</li>
* <li>The entities <code>NamedNodeMaps</code> are equal.</li>
* <li>The notations <code>NamedNodeMaps</code> are equal.</li></ul>
* <br>
* On the other hand, the following do not affect equality: the ownerDocument,
* baseURI, and parentNode attributes, the specified attribute for Attr nodes,
* the schemaTypeInfo attribute for Attr and Element nodes,
* the Text.isElementContentWhitespace attribute for Text nodes, as well as any
* user data or event listeners registered on the nodes.
* @param other Node to test againts
* @return true if nodes are equal, false otherwise
* @since DOM Level 3
*/
public boolean isEqualNode(Node other) {
if (this.isSameNode(other)) return true;
// Node has to be of the same type
if (other == null || other.getNodeType() != this.getNodeType()) {
return false;
}
// The following string attributes have to be equal: nodeName, localName,
// namespaceURI, prefix, nodeValue
return StringUtilities.equals(getNodeName(), other.getNodeName()) &&
StringUtilities.equals(getLocalName(), other.getLocalName()) &&
StringUtilities.equals(getNamespaceURI(), other.getNamespaceURI()) &&
StringUtilities.equals(getPrefix(), other.getPrefix()) &&
StringUtilities.equals(getNodeValue(), other.getNodeValue());
}
/**
* @since DOM Level 3
*/
public Object getFeature(String feature, String version) {
if (DOMImplementationImpl.HasFeature(feature, version)) {
return this;
}
return null;
}
/**
* @since DOM Level 3
*/
public boolean isDefaultNamespace(String namespaceURI) {
return namespaceURI.equals(lookupDefaultNamespaceURI());
}
/**
* Compares the reference node, i.e. the node on which this method is being
* called, with a node, i.e. the one passed as a parameter, with regard to
* their position in the document and according to the document order.
* @param other The node to compare against the reference node.
* @return Returns how the node is positioned relatively to the reference
* node.
* @throws DOMException NOT_SUPPORTED_ERR: when the compared nodes are
* from different DOM implementations that do not coordinate to return
* consistent implementation-specific results.
* @since DOM Level 3
*/
public short compareDocumentPosition(Node other) throws DOMException {
if (this == other) {
return 0;
}
if (!(other instanceof NodeImpl)) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Nodes are from different DOM implementations");
}
if (getOwnerDocument() != other.getOwnerDocument()) {
return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
ArrayList ancestors = new ArrayList();
Node root = findAncestors(this, other, ancestors);
Node ancNode = (Node) ancestors.get(0);
Node ancOther = (Node) ancestors.get(1);
if (root == null) {
return DOCUMENT_POSITION_DISCONNECTED;
}
if (root == other) {
return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING;
}
if (root == this) {
return DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING;
}
short ancNodeType = ancNode.getNodeType();
short ancOtherType = ancOther.getNodeType();
boolean nodeIsChild = ancNodeType != ATTRIBUTE_NODE && ancNodeType != NOTATION_NODE;
boolean otherIsChild = ancOtherType != ATTRIBUTE_NODE && ancOtherType != NOTATION_NODE;
if (nodeIsChild && otherIsChild) {
Node child = root.getFirstChild();
while (child != null) {
if (child == ancNode) {
return DOCUMENT_POSITION_FOLLOWING;
} else if (child == ancOther) {
return DOCUMENT_POSITION_PRECEDING;
}
child = child.getNextSibling();
}
return 0;
} else if (nodeIsChild ^ otherIsChild) {
return nodeIsChild ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING;
} else {
if (ancNodeType != ancOtherType) {
return ancNodeType > ancOtherType ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING;
} else {
return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
}
}
private Node findParent(Set seen, Node node, ArrayList path) {
if (node == null) {
return null;
}
seen.add(node);
path.add(node);
return node.getParentNode();
}
private Node findAncestors(Node node1, Node node2, ArrayList ancestors) {
ArrayList path1 = new ArrayList();
ArrayList path2 = new ArrayList();
HashSet seen = new HashSet();
Node root = null;
do {
node1 = findParent(seen, node1, path1);
node2 = findParent(seen, node2, path2);
if (seen.contains(node1)) {
root = node1;
ancestors.add(path1.get(path1.size() - 1));
int idx = path2.indexOf(node1);
ancestors.add(path2.get(idx > 0 ? idx - 1 : 0));
break;
} else if (seen.contains(node2)) {
root = node2;
int idx = path1.indexOf(node2);
ancestors.add(path1.get(idx > 0 ? idx - 1 : 0));
ancestors.add(path2.get(path2.size() - 1));
break;
} else if (node1 != null && node1 == node2) {
root = node1;
ancestors.add(path1.get(path1.size() - 1));
ancestors.add(path2.get(path2.size() - 1));
break;
}
} while (node1 != null || node2 != null);
return root;
}
//
// Implementation Methods
//
/**
* This method walks down the tree, starting from this node, and adds
* namespace declarations where needed so that every namespace being
* used is properly declared. It also changes or assign prefixes when
* needed. This effectively makes this node subtree is "namespace
* wellformed".
* <br>What the generated prefixes are and/or how prefixes are changed to
* achieve this is implementation dependent.Any other name?How specific
* should this be? Should we not even specify that this should be done
* by walking down the tree?What does this do on attribute nodes?Doesn't
* do anything (F2F 1 Aug 2000).How does it work with entity reference
* subtree which may be broken?This doesn't affect entity references
* which are not visited in this operation (F2F 1 Aug 2000).Should this
* be really be on Node?Yes, but this only works on Document, Element,
* and DocumentFragment. On other types it is a no-op. (F2F 1 Aug 2000).
* What happens with read-only nodes?What/how errors should be reported?
* Are there any?
public void normalizeNS() throws DOMException {
}
*/
/**
* This attribute returns a unique key identifying this node.
*
* What type should this really be? In what space is this key
* unique (Document, DOMImplementation)? What is the lifetime of
* the uniqueness of this key (Node, Document, ...)?
public final synchronized Object getKey() {
if (key == null) {
key = new Object();
}
return key;
}
*/
/** Invoke user data handlers with provided parameters. */
protected void invokeHandlers(short op, Node src, Node dst) {
if (!(src instanceof NodeImpl)) {
return;
}
((NodeImpl) src).invokeHandlers(op, dst);
}
protected synchronized void invokeHandlers(short op, Node dst) {
if (handlers == null || handlers.isEmpty()) {
return;
}
for (Iterator i = handlers.entrySet().iterator(); i.hasNext(); ) {
final Map.Entry entry = (Map.Entry) i.next();
final String key = (String) entry.getKey();
final UserDataHandler handler = (UserDataHandler) entry.getValue();
try {
handler.handle(op, key, userData.get(key), this, dst);
} catch (Exception e) {
log.error("User data handler '" + key + "' failed for operation " + op, e);
}
}
}
/**
* Converts this node into its textual representation.
*/
public String toString() {
return TextWriter.toString(this);
}
}