/*
* 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: ContainerNodeImpl.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.ByteArrayInput;
import org.apache.xindice.xml.SymbolTable;
import org.apache.xindice.xml.XMLCompressedInput;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* ContainerNodeImpl performs most of the child-rearing behavior of the
* Element and Document implementations.
*
* @version $Revision: 518460 $, $Date: 2007-03-14 23:47:19 -0400 (Wed, 14 Mar 2007) $
*/
public abstract class ContainerNodeImpl extends NodeImpl {
private static final Log log = LogFactory.getLog(ContainerNodeImpl.class);
protected NodeListImpl childNodes = new NodeListImpl(this);
public ContainerNodeImpl() {
}
public ContainerNodeImpl(NodeImpl parentNode, byte[] data, int pos, int len) {
super(parentNode, data, pos, len);
}
public ContainerNodeImpl(NodeImpl parentNode, boolean dirty) {
super(parentNode, dirty);
}
protected boolean isNodeTypeValid(short type) {
return true;
}
protected final Node getPreviousSibling(Node node) {
checkLoaded();
int pos = childNodes.indexOf(node) - 1;
return pos >= 0 ? childNodes.item(pos) : null;
}
protected final Node getNextSibling(Node node) {
checkLoaded();
int pos = childNodes.indexOf(node) + 1;
return pos < childNodes.getLength() ? childNodes.item(pos) : null;
}
protected void checkLoaded() {
if (loaded) {
return;
}
loaded = true;
try {
if (data != null) {
DocumentImpl doc = (DocumentImpl) getOwnerDocument();
loadChildren(doc.getSymbols());
}
} catch (Exception e) {
if (log.isWarnEnabled()) {
log.warn("ignored exception", e);
}
}
}
protected final void loadChildren(SymbolTable st) throws IOException {
ByteArrayInput bis = new ByteArrayInput(data, pos, len);
XMLCompressedInput in = new XMLCompressedInput(bis, st);
if (getNodeType() == Node.ELEMENT_NODE) {
// Have to skip the attributes
in.readSignature();
in.readContentSize();
in.readShort(); // Element Symbol
int attrCount = in.readAttributeCount();
for (int i = 0; i < attrCount; i++) {
in.readShort(); // Attribute Symbol
in.skip(in.readShort()); // Attribute Length
}
} else {
in.readInt();
}
while (in.available() > 0) {
int pos = bis.getPos();
in.readSignature(); // Skip signature
int len = in.readContentSize();
if (len == 0) {
len = 1;
}
switch (in.getNodeType()) {
case Node.ELEMENT_NODE:
childNodes.add(new ElementImpl(this, data, pos, len));
break;
case Node.TEXT_NODE:
childNodes.add(new TextImpl(this, data, pos, len));
break;
case Node.CDATA_SECTION_NODE:
childNodes.add(new CDATASectionImpl(this, data, pos, len));
break;
case Node.ENTITY_REFERENCE_NODE:
childNodes.add(new EntityReferenceImpl(this, data, pos, len));
break;
case Node.ENTITY_NODE:
childNodes.add(new EntityImpl(this, data, pos, len));
break;
case Node.PROCESSING_INSTRUCTION_NODE:
childNodes.add(new ProcessingInstructionImpl(this, data, pos, len));
break;
case Node.COMMENT_NODE:
childNodes.add(new CommentImpl(this, data, pos, len));
break;
case Node.NOTATION_NODE:
childNodes.add(new NotationImpl(this, data, pos, len));
break;
default:
if (log.isWarnEnabled()) {
log.warn("invalid node type : " + in.getNodeType());
}
}
bis.setPos(pos);
bis.skip(len);
}
}
/**
* 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 final boolean hasChildNodes() {
checkLoaded();
return childNodes.getLength() > 0;
}
public final NodeList getChildNodes() {
checkLoaded();
return childNodes;
}
/**
* The first child of this node. If there is no such node, this returns
* <code>null</code>.
*/
public final Node getFirstChild() {
checkLoaded();
if (childNodes.size() > 0) {
return childNodes.item(0);
} else {
return null;
}
}
/**
* The last child of this node. If there is no such node, this returns
* <code>null</code>.
*/
public final Node getLastChild() {
checkLoaded();
if (childNodes.size() > 0) {
return childNodes.item(childNodes.getLength() - 1);
} else {
return null;
}
}
/**
* 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 final synchronized Node replaceChild(Node newChild, Node oldChild) throws DOMException {
checkLoaded();
checkReadOnly();
if (!isNodeTypeValid(newChild.getNodeType())) {
throw EX_HIERARCHY_REQUEST;
}
int idx = childNodes.indexOf(oldChild);
if (idx >= 0) {
if (newChild.getNodeType() == DOCUMENT_FRAGMENT_NODE) {
childNodes.remove(idx);
NodeList nl = newChild.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
NodeImpl impl = (NodeImpl) nl.item(i);
impl.setParentNode(this);
childNodes.add(idx + i, impl);
}
} else {
NodeImpl impl = (NodeImpl) newChild;
impl.setParentNode(this);
childNodes.set(idx, impl);
}
}
setDirty();
return oldChild;
}
/**
* 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 final synchronized Node insertBefore(Node newChild, Node refChild) throws DOMException {
checkLoaded();
checkReadOnly();
int idx = childNodes.indexOf(refChild);
if (idx >= 0) {
if (!isNodeTypeValid(newChild.getNodeType())) {
throw EX_HIERARCHY_REQUEST;
}
if (newChild.getNodeType() == DOCUMENT_FRAGMENT_NODE) {
NodeList nl = newChild.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
NodeImpl impl = (NodeImpl) nl.item(i);
impl.setParentNode(this);
childNodes.add(idx + i, impl);
}
} else {
NodeImpl impl = (NodeImpl) newChild;
impl.setParentNode(this);
childNodes.add(idx, impl);
}
}
setDirty();
return newChild;
}
/**
* 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 final synchronized Node removeChild(Node oldChild) throws DOMException {
checkLoaded();
checkReadOnly();
if (!childNodes.remove(oldChild)) {
throw EX_NOT_FOUND;
}
setDirty();
return oldChild;
}
/**
* 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 final synchronized Node appendChild(Node newChild) throws DOMException {
checkLoaded();
checkReadOnly();
if (!isNodeTypeValid(newChild.getNodeType())) {
throw EX_HIERARCHY_REQUEST;
}
if (newChild.getNodeType() == DOCUMENT_FRAGMENT_NODE) {
NodeList nl = newChild.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
NodeImpl impl = (NodeImpl) nl.item(i);
impl.setParentNode(this);
childNodes.add(impl);
}
} else {
NodeImpl impl = (NodeImpl) newChild;
impl.setParentNode(this);
childNodes.add(impl);
}
setDirty();
return newChild;
}
/**
* 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 final synchronized void normalize() {
checkLoaded();
checkReadOnly();
List set = new ArrayList();
NodeListImpl newList = new NodeListImpl(this);
boolean modified = false;
int size = childNodes.size();
for (int i = 0; i < size; i++) {
Node add = null;
Node n = (Node) childNodes.get(i);
short type = n.getNodeType();
switch (type) {
case Node.TEXT_NODE:
set.add(n);
break;
case Node.ELEMENT_NODE:
n.normalize();
default :
add = n;
}
if (!set.isEmpty() && (type != Node.TEXT_NODE || i == size - 1)) {
Text s = (Text) set.get(0);
int len = set.size();
for (int j = 1; j < len; j++) {
modified = true;
Text a = (Text) set.get(j);
s.appendData(a.getData());
}
newList.add(s);
set.clear();
}
if (add != null) {
newList.add(add);
}
}
if (modified) {
childNodes = newList;
setDirty();
}
}
/**
* Returns a <code>NodeList</code> of all descendant elements with a given
* tag name, in the order in which they would be encountered in a preorder
* traversal of the <code>Element</code> tree.
*
* @param name The name of the tag to match on. The special value "*"
* matches all tags.
* @return A list of matching <code>Element</code> nodes.
*/
public final NodeList getElementsByTagName(final String name) {
checkLoaded();
NodeListImpl list = new NodeListImpl(this);
NodeFilter filter = new NodeFilter() {
public short acceptNode(Node node) {
if (node.getNodeName().equals(name)) {
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_SKIP;
}
}
};
NodeIterator iter = ((DocumentTraversal) getOwnerDocument()).createNodeIterator(this, NodeFilter.SHOW_ELEMENT, filter, false);
Node node;
do {
node = iter.nextNode();
if (node != null) {
list.add(node);
}
} while (node != null);
return list;
}
public final NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) {
checkLoaded();
NodeListImpl list = new NodeListImpl(this);
NodeFilter filter = new NodeFilter() {
public short acceptNode(Node node) {
if (node.getLocalName().equals(localName) && node.getNamespaceURI().equals(namespaceURI)) {
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_SKIP;
}
}
};
NodeIterator iter = ((DocumentTraversal) getOwnerDocument()).createNodeIterator(this, NodeFilter.SHOW_ELEMENT, filter, false);
Node node;
do {
node = iter.nextNode();
if (node != null) {
list.add(node);
}
} while (node != null);
return list;
}
public final Element getElementById(String elementId) {
return null;
}
//
// DOM Level 3 Implementation
//
/**
* @since DOM Level 3
*/
public String getTextContent() {
StringBuffer val = new StringBuffer();
NodeList children = getChildNodes();
if (children == null || children.getLength() == 0) {
return "";
}
for (int i = 0; i < children.getLength(); i++ ) {
val.append(((NodeImpl) children.item(i)).getTextContent());
}
return val.toString();
}
/**
* @since DOM Level 3
*/
public boolean isEqualNode(Node other) {
if (!super.isEqualNode(other)) {
return false;
}
// The childNodes NodeLists have to be equal
other.normalize();
normalize();
if (!hasChildNodes() && !other.hasChildNodes()) {
return true;
}
if (!hasChildNodes() || !other.hasChildNodes()) {
return false;
}
NodeList thisChildren = getChildNodes();
NodeList otherChildren = other.getChildNodes();
if (thisChildren.getLength() != otherChildren.getLength()) {
return false;
}
for (int i = 0; i < thisChildren.getLength(); i++) {
if (!((NodeImpl) thisChildren.item(i)).isEqualNode(otherChildren.item(i))) {
return false;
}
}
return true;
}
}