Package org.exolab.castor.types

Source Code of org.exolab.castor.types.AnyNode

/*
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided
* that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright
*    statements and notices.  Redistributions must also contain a
*    copy of this document.
*
* 2. Redistributions in binary form must reproduce the
*    above copyright notice, this list of conditions and the
*    following disclaimer in the documentation and/or other
*    materials provided with the distribution.
*
* 3. The name "Exolab" must not be used to endorse or promote
*    products derived from this Software without prior written
*    permission of Intalio, Inc.  For written permission,
*    please contact info@exolab.org.
*
* 4. Products derived from this Software may not be called "Exolab"
*    nor may "Exolab" appear in their names without prior written
*    permission of Intalio, Inc. Exolab is a registered
*    trademark of Intalio, Inc.
*
* 5. Due credit should be given to the Exolab Project
*    (http://www.exolab.org/).
*
* THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
* INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 2001 (C) Intalio, Inc. All Rights Reserved.
*
* $Id: AnyNode.java 7289 2007-10-22 12:42:23Z wguttmn $
* Date         Author              Changes
* 04/24/2001   Arnaud Blandin      Rewrited from scratch
* 04/22/2001   Arnaud Blandin      Clean-up and support of comments
* 04/04/2001   Arnaud Blandin      Created
*/
package org.exolab.castor.types;

import java.io.StringWriter;
import java.util.Stack;

import org.castor.xml.BackwardCompatibilityContext;
import org.exolab.castor.xml.Serializer;
import org.exolab.castor.xml.util.AnyNode2SAX;

/**
* A class used to represent an XML node. This is an alternative to DOM which is
* too heavy for our purpose (mainly handle XML Fragment when {@literal <any>}
* is used in an XML schema). The model is based on XPath Node. An AnyNode can
* be a:
* <ul>
* <li>ELEMENT</li>
* <li>ATTRIBUTE</li>
* <li>NAMESPACE</li>
* <li>COMMENT</li>
* <li>TEXT</li>
* <li>PROCESSING INSTRUCTION</li>
* </ul>
*
* @author <a href="mailto:arkin@intalio.com">Assaf Arkin</a>
* @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a>
* @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
* @version $Revision: 7289 $ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
*/
public final class AnyNode implements java.io.Serializable {
    //TODO Processing Instructions
    //TODO Full handling of namespaces

    /** SerialVersionUID */
    private static final long serialVersionUID = -4104117996051705975L;

    /**
     * The prefix for XML namespace
     */
    private final static String XMLNS_PREFIX  = "xmlns";

    /**
     * Representation for an element node.
     */
    public static final short ELEMENT = 1;

    /**
     * Representation for an attribute node.
     */
    public static final short ATTRIBUTE = 2;

    /**
     * Representation for a Namespace node.
     */
    public static final short NAMESPACE = 3;

    /**
     * Representation for a processing instruction node.
     */
    public static final short PI = 4;

    /**
     * Representation for a comment node.
     */
    public static final short COMMENT = 5;

    /**
     * Representation for a text node.
     */
    public static final short TEXT  = 6;

    /**
     * The type of the current node.
     * ELEMENT is the default value.
     */
    private short _nodeType = ELEMENT;

    /**
     * The next sibling of this AnyNode
     */
    private AnyNode _nextSiblingNode = null;

    /**
     * The first child of this AnyNode
     */
    private AnyNode _firstChildNode = null;


    /**
     * the local name of the current node.
     */
    private String _localName;

    /**
     * the Namespace URI of the current node
     */
    private String _uri;

    /**
     * The prefix of the Namespace
     */
    private String _prefix;

    /**
     * A stack used for avoiding endless loop
     * in toString()
     */
    private static Stack _elements;

    /**
     * The namespace context used in the toString()
     */

    /**
     * The value of this node defined as follow:
     * <ul>
     *     <li>for an element the value is its TEXT NODE value (if any)</li>
     *     <li>for an attribute the value is the value of the attribute</li>
     *     <li>for a text node it is the character data</li>
     *     <li>for a namespace it is the namespace URI that is being bound
     *     to the namespace prefix</li>
     *     <li>for a comment it is the content of the comment not including the
     *     opening &lt;!-- and the closing --&gt;. </li>
     * </ul>
     */
    private String _value;

    /**
     * Default constructor: creates an empty element node
     */
    public AnyNode() {
        this(ELEMENT, null, null, null, null);
    }

    /**
     * Creates a node given all the necessary information:
     * type, localName, prefix, uri and value.
     * This constructor is not user-friendly and launched
     * RunTime exception is you try to instantiate non-valid node.
     * @param type the node type.
     * @param localName the name of the node.
     * @param prefix the prefix if any of the namespace.
     * @param uri the namespace uri of this node.
     * @param value the value of this node.
     */
    public AnyNode(short type, String localName, String prefix, String uri, String value) {
        if ( (type > 6) && (type<1) ) {
            throw new IllegalArgumentException("Illegal node type");
        }
        _nodeType = type;

        //comment and text nodes don't have name
        if ( (type>PI) && (localName != null) ) {
            String err = "This node can not have a local name";
            throw new IllegalArgumentException(err);
        }
        _localName = localName;

        //for comment, pi or text we should have no namespaces
        if ((type>NAMESPACE) && ( (uri != null) || (prefix != null)) ) {
            String err = "This node can not handle namespace";
            throw new IllegalArgumentException(err);
        }
        _uri = uri;
        _prefix = prefix;

        //attributes can not be namespaces
        if (type == AnyNode.ATTRIBUTE)
            if (localName.startsWith(XMLNS_PREFIX)) {
                String err = "Namespaces can't be used as attributes.";
                throw new IllegalArgumentException(err);
            }

        //you can't set value for element
        if ( (type == ELEMENT) && (value != null) ) {
            String err = "You can't set a value for this node type";
            throw new IllegalArgumentException(err);
        }
        _value = value;
    }

    /**
     * Adds an AnyNode to the current node
     * @param node the node to append
     */
    public void addAnyNode(AnyNode node) {
        if (node == null) {
            throw new IllegalArgumentException("null argument in addAnyNode");
        }

        switch(node.getNodeType()) {
            case ATTRIBUTE:
                addAttribute(node);
                break;
            case NAMESPACE:
                addNamespace(node);
                break;
            default:
                addChild(node);
                break;
        }
    }

    /**
     * <p>
     * Adds a child AnyNode to this node. A 'child' can be either an ELEMENT
     * node, a COMMENT node, a TEXT node or a PROCESSING INSTRUCTION. If the
     * current node already has a child then the node to add will be append as a
     * sibling.
     * <p>
     * Note: you cannot add a child to a TEXT node.
     *
     * @param node
     *            the node to add.
     */
    public void addChild(AnyNode node) {
        if (node == null) {
            throw new IllegalArgumentException("null argument in appendChild");
        }

        if (node.getNodeType() == ATTRIBUTE || node.getNodeType() == NAMESPACE) {
            throw new IllegalArgumentException("An Attribute or an Namespace can't be added as a child");
        }

        if (this.getNodeType() == TEXT) {
            throw new IllegalArgumentException("a TEXT node can't have children.");
        }

        if (_firstChildNode == null) {
            _firstChildNode = node;
        } else if (_firstChildNode.getNodeType() == ATTRIBUTE || _firstChildNode.getNodeType() == NAMESPACE) {
            _firstChildNode.addChild(node);
        } else  {
            _firstChildNode.appendSibling(node);
        }
    }

    /**
     * Adds an attribute to the current node.
     * @param node the attribute to add.
     */
    public void addAttribute(AnyNode node) {
        if (node == null) {
            throw new IllegalArgumentException("null argument in addAttribute");
        }

        if (node.getNodeType() != ATTRIBUTE) {
            throw new IllegalArgumentException("Only attribute can be added as an attribute");
        }

        if (_firstChildNode == null) {
            _firstChildNode = node;
        } else {
            if (_firstChildNode.getNodeType() == ATTRIBUTE) {
                //if we reach an attribute then we add the node as its sibling
                _firstChildNode.appendSibling(node);
            } else if (_firstChildNode.getNodeType() == NAMESPACE) {
                //if we reach an namespace the attributre should be added to
                //the first child of the namespace
                _firstChildNode.addAttribute(node);
            } else {
                //unplug the current firstNode to add a new one
                node.addChild(_firstChildNode);
                _firstChildNode = node;
            }
        }
    } //addAttribute

    /**
     * Appends an namespace to the current node.
     * @param node the attribute to add.
     */
    public void addNamespace(AnyNode node){
        if (node == null) {
            throw new IllegalArgumentException("null argument in addNamespace");
        }

        if (node.getNodeType() != NAMESPACE) {
            throw new IllegalArgumentException("Only namespace can be added as an namespace");
        }

        if (_firstChildNode == null) {
            _firstChildNode = node;
        } else {
            if (_firstChildNode.getNodeType() == NAMESPACE) {
                //if we reach an namepace then we add the node as its sibling
                _firstChildNode.appendSibling(node);
            } else if (_firstChildNode.getNodeType() == ATTRIBUTE) {
                //if we reach an attribute the attributre should be added to
                //the first child of the attribute
                _firstChildNode.addNamespace(node);
            } else {
                //unplug the current firstNode to add a new one
                node.addChild(_firstChildNode);
                _firstChildNode = node;
            }
        }
    } //addNamespace

    /**
     * Returns the first attribute of the current ELEMENT node
     * or null.
     * The next attribute,if any,is the sibling of the returned node.
     */
    public AnyNode getFirstAttribute() {
        if (this.getNodeType() != ELEMENT) {
            String err = "This node type can not contain attributes";
            throw new UnsupportedOperationException(err);
        }

        boolean found = false;
        AnyNode tempNode = this.getFirstChildNode();
        while (tempNode != null && !found) {
            short type = tempNode.getNodeType();
            //if the child is not an attribute or a namespace
            //this element does not have any attribute
            if (type == ELEMENT || type == COMMENT || type == TEXT || type == PI) {
                tempNode = null;
            } else if (type == NAMESPACE) {
                tempNode = tempNode.getFirstChildNode();
            } else {
                found = true;
            }
        }
        return tempNode;
    }

    /**
     * Returns the first namespace of the current ELEMENT node or null. The next
     * attribute if any is the sibling of the returned node.
     * @return the first namespace of the current ELEMENT node or null.
     */
    public AnyNode getFirstNamespace() {
        if (this.getNodeType() != ELEMENT) {
            String err = "This node type can not contain namespaces";
            throw new UnsupportedOperationException(err);
        }

        AnyNode tempNode = this.getFirstChildNode();
        boolean found = false;
        while (tempNode != null && !found) {
            short type = tempNode.getNodeType();
            //if the child is not an attribute or a namespace
            //this element does not have any namespace
            if (type == ELEMENT || type == COMMENT || type == TEXT || type == PI) {
                tempNode = null;
            } else if (type == ATTRIBUTE) {
                tempNode = tempNode.getFirstChildNode();
            } else {
                found = true;
            }
        }
        return tempNode;
    }

    /**
     * Returns the first Child node of this node. A 'child' can be either an
     * ELEMENT node, a COMMENT node, a TEXT node or a PROCESSING INSTRUCTION.
     *
     * @return the first child of this node
     */
    public AnyNode getFirstChild() {
        //an ATTRIBUTE or a NAMESPACE can not
        //have children
        if (this.getNodeType() == ATTRIBUTE || this.getNodeType() == NAMESPACE) {
            return null;
        }

        //loop througth the first two (in the worst case) nodes
        //and then return the firstChild if any
        AnyNode tempNode = this.getFirstChildNode();
        boolean found = false;
        while (tempNode != null && !found) {
            short type = tempNode.getNodeType();
            if (type == ELEMENT || type == COMMENT || type == TEXT || type == PI) {
                found = true;
            } else if (type == ATTRIBUTE || type == NAMESPACE) {
                tempNode = tempNode.getFirstChildNode();
            }
        }
        return tempNode;
    }

    /**
     * Returns the next sibling of the current node. When the AnyNode is an
     * ATTRIBUTE, it will return the next ATTRIBUTE node. When the AnyNode is a
     * NAMESPACE, it will return the next NAMESPACE node.
     *
     * @return the next sibling of the current node
     */
    public AnyNode getNextSibling() {
        return _nextSiblingNode;
    }

    /**
     * Returns the type of this node.
     * @return The type of this node
     */
    public short getNodeType() {
        return _nodeType;
    }

    /**
     * Returns the local name of the node. Returns the local name of an element
     * or attribute, the prefix of a namespace node, the target of a processing
     * instruction, or null for all other node types.
     *
     * @return The local name of the node, or null if the node has no name
     */
    public  String getLocalName(){
        return _localName;
    }

    /**
     * Returns the namespace URI of the node. Returns the namespace URI of an
     * element, attribute or namespace node, or null for all other node types.
     *
     * @return The namespace URI of the node, or null if the node has no
     *         namespace URI
     */
    public String getNamespaceURI() {
        return _uri;
    }

    /**
     * Returns the string value of the node. The string value of a text node or
     * an attribute node is its text value. The string value of an element or a
     * root node is the concatenation of the string value of all its child
     * nodes. The string value of a namespace node is its namespace URI. The
     * string value of a processing instruction is the instruction, and the
     * string value of a comment is the comment text.
     *
     * @return The string value of the node
     */
    public String getStringValue() {
        switch(_nodeType) {
            case ATTRIBUTE:
            case TEXT:
                return _value;
            case NAMESPACE:
                return _uri;
                //not yet supported
            case PI:
                return "";
            case COMMENT:
                return _value;
            case ELEMENT:
                StringBuffer result = new StringBuffer(4096);
                AnyNode tempNode = this.getNextSibling();
                while (tempNode != null && tempNode.getNodeType() == TEXT) {
                    result.append(tempNode.getStringValue());
                    tempNode = tempNode.getNextSibling();
                }

                tempNode = this.getFirstChild();
                while (tempNode != null) {
                    result.append(tempNode.getStringValue());
                    tempNode = tempNode.getNextSibling();
                }
                tempNode = null;
                return result.toString();
            default:
                return null;
        }
    }

    /**
     * Returns the namespace prefix associated with the namespace URI of this
     * node. Returns null if no prefix. is defined for this namespace URI.
     * Returns an empty string if the default prefix is associated with this
     * namespace URI.
     *
     * @return The namespace prefix, or null
     */
    public String getNamespacePrefix() {
        return _prefix;
    }

    /**
     * Returns the String representation of this AnyNode. The String
     * representation is a xml well-formed fragment corresponding to the
     * representation of this node.
     * @return the String representation of this AnyNode.
     */
    public String toString() {
        Serializer serializer = new BackwardCompatibilityContext().getSerializer();
        if (serializer == null) {
            throw new RuntimeException("Unable to obtain serializer");
        }

        StringWriter writer = new StringWriter();

        serializer.setOutputCharStream(writer);

        try {
            AnyNode2SAX.fireEvents(this, serializer.asDocumentHandler());
        } catch (java.io.IOException ioe) {
            return privateToString();
        } catch (org.xml.sax.SAXException saxe) {
            throw new RuntimeException(saxe.getMessage());
        }

        return writer.toString();
    }

    private String privateToString() {
        StringBuffer sb = new StringBuffer(4096);

        if (_elements == null) {
            _elements = new Stack();
        }

        //check the Stack too see if we have
        //already proceed the node
        if (_elements.search(this) == -1) {
            _elements.push(this);

            if (this.getNodeType() == ELEMENT) {
                //open the tag
                sb.append("<");
                String prefix = getNamespacePrefix();
                if (prefix != null) {
                    sb.append(prefix+":");
                }
                prefix = null;
                sb.append(getLocalName());

                //append the attributes
                AnyNode tempNode = this.getFirstAttribute();
                while (tempNode != null) {
                    sb.append(" ");
                    sb.append(tempNode.getLocalName());
                    sb.append("='"+tempNode.getStringValue()+"'");
                    tempNode = tempNode.getNextSibling();
                }

                //append the namespaces
                tempNode = this.getFirstNamespace();
                while (tempNode != null) {
                    sb.append(" ");
                    sb.append(XMLNS_PREFIX);
                    prefix = tempNode.getNamespacePrefix();
                    if (prefix != null && prefix.length() != 0) {
                        sb.append(":"+prefix);
                    }
                    sb.append("='"+tempNode.getNamespaceURI()+"'");
                    tempNode = tempNode.getNextSibling();
                }//namespaceNode

                tempNode = this.getFirstChild();
                if (tempNode != null) {
                    sb.append(">");
                    while (tempNode != null) {
                        sb.append(tempNode.privateToString());
                        tempNode = tempNode.getNextSibling();
                    }
                    //close the tag
                    sb.append("</"+getLocalName()+">");
                } else {
                    sb.append("/>");
                }
            } else {
                sb.append(this.getStringValue());
            }
            return sb.toString();
        }
        return sb.toString();
    }//toString()

    /**
     * Appends a sibling AnyNode to the current node. The node to append will be
     * added at the end of the sibling branch.
     *
     * @param node
     *            the node to add
     */
    protected void appendSibling(AnyNode node) {
        if (node == null) {
            throw new IllegalArgumentException();
        }

        if ( ((node.getNodeType() == ATTRIBUTE) || (node.getNodeType()== NAMESPACE)) &&
              (this.getNodeType() != node.getNodeType())) {
            String err = "a NAMESPACE or an ATTRIBUTE can only be add as a sibling to a node of the same type";
            throw new UnsupportedOperationException(err);
        }

        if (_nextSiblingNode == null) {
            //if we already have a TEXT node -> merge
            if ((node.getNodeType() == TEXT) && (this.getNodeType() == TEXT)) {
                mergeTextNode(this,node);
            } else {
                _nextSiblingNode = node;
            }
        } else {
            _nextSiblingNode.appendSibling(node);
        }
    }

    /**
     * Returns the first child node in the tree.
     * @return the first child node in the tree.
     */
    protected AnyNode getFirstChildNode() {
        return _firstChildNode;
    }

    /**
     * Adds the text value of a TEXT node to another
     * TEXT node.
     * @param node1 the AnyNode that receives the text value
     * @param node2 the AnyNode that needs to be merges with node1.
     */
    private void mergeTextNode(AnyNode node1, AnyNode node2) {
        if (node1.getNodeType() != node2.getNodeType()) {
            return;
        }
        if (node1.getNodeType() != AnyNode.TEXT) {
            return;
        }

        StringBuffer temp = new StringBuffer(node1.getStringValue());
        temp.append(node2.getStringValue());
        node1._value = temp.toString();
        node2 = null;
    }

}
TOP

Related Classes of org.exolab.castor.types.AnyNode

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.