Package org.apache.xindice.xml.dom

Source Code of org.apache.xindice.xml.dom.NodeImpl

/*
* 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);
    }
}
TOP

Related Classes of org.apache.xindice.xml.dom.NodeImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.