/*
 * Copyright 2001-2004 The Apache Software Foundation.
 * 
 * Licensed 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.
 */

package org.apache.axis.message;

import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.encoding.SerializationContext;
import org.apache.axis.i18n.Messages;
import org.apache.commons.logging.Log;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;

import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;

/**
 * This is our implementation of the DOM node
 */
public class NodeImpl implements org.w3c.dom.Node, javax.xml.soap.Node,
        Serializable, Cloneable {

    protected static Log log =
            LogFactory.getLog(NodeImpl.class.getName());

    protected String name;
    protected String prefix;
    protected String namespaceURI;
    protected transient Attributes attributes = NullAttributes.singleton;

    protected Document document = null;
    protected NodeImpl parent = null;
    protected ArrayList children = null;

    // ...or as DOM
    protected CharacterData textRep = null;

    protected boolean   _isDirty = false;
    private static final String NULL_URI_NAME = "intentionalNullURI";

    /**
     * empty constructor
     */
    public NodeImpl() {
    }

    /**
     * constructor which adopts the name and NS of the char data, and its text
     * @param text
     */
    public NodeImpl(CharacterData text) {
        textRep = text;
        namespaceURI = text.getNamespaceURI();
        name = text.getLocalName();
    }

    /**
     * A code representing the type of the underlying object, as defined above.
     */
    public short getNodeType() {
        if (this.textRep != null) {
            if (textRep instanceof Comment) {
                return COMMENT_NODE;
            } else if (textRep instanceof CDATASection) {
                return CDATA_SECTION_NODE;
            } else {
                return TEXT_NODE;
            }
        } else if (false) {
            return DOCUMENT_FRAGMENT_NODE;
        } else if (false) {
            return Node.ELEMENT_NODE;
        } else { // most often but we cannot give prioeity now
            return Node.ELEMENT_NODE;
        }
    }

    /**
     * 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 structure (e.g., elements, 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.
     */
    public void normalize() {
        //TODO: Fix this for SAAJ 1.2 Implementation
    }

    /**
     * Returns whether this node (if it is an element) has any attributes.
     * 
     * @return <code>true</code> if this node has any attributes,
     *         <code>false</code> otherwise.
     * @since DOM Level 2
     */
    public boolean hasAttributes() {
        return attributes.getLength() > 0;
    }

    /**
     * Returns whether this node has any children.
     * 
     * @return <code>true</code> if this node has any children,
     *         <code>false</code> otherwise.
     */
    public boolean hasChildNodes() {
        return (children != null && !children.isEmpty());
    }

    /**
     * Returns the local part of the qualified name of this node.
     * <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>.
     * 
     * @since DOM Level 2
     */
    public String getLocalName() {
        return name;
    }

    /**
     * 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 String getNamespaceURI() {
        return (namespaceURI);
    }

    /**
     * The name of this node, depending on its type; see the table above.
     */
    public String getNodeName() {
        return (prefix != null && prefix.length() > 0) ?
                prefix + ":" + name : name;
    }

    /**
     * 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.
     * 
     * @throws org.w3c.dom.DOMException NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly.
     * @throws org.w3c.dom.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 {
        if (textRep == null) {
            return null;
        } else {
            return textRep.getData();
        }
    }

    /**
     * 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.
     * <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>.
     * 
     * @throws org.w3c.dom.DOMException INVALID_CHARACTER_ERR: Raised if the specified prefix contains an
     *                                  illegal character, per the XML 1.0 specification .
     *                                  <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
     *                                  <br>NAMESPACE_ERR: Raised if the specified <code>prefix</code> is
     *                                  malformed per the Namespaces in XML specification, 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 String getPrefix() {
        return (prefix);
    }

    /**
     * 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.
     * 
     * @throws org.w3c.dom.DOMException NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly.
     * @throws org.w3c.dom.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 new DOMException(DOMException.NO_DATA_ALLOWED_ERR,
                "Cannot use TextNode.set in " + this);
    }

    /**
     * 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.
     * <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>.
     * 
     * @throws org.w3c.dom.DOMException INVALID_CHARACTER_ERR: Raised if the specified prefix contains an
     *                                  illegal character, per the XML 1.0 specification .
     *                                  <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
     *                                  <br>NAMESPACE_ERR: Raised if the specified <code>prefix</code> is
     *                                  malformed per the Namespaces in XML specification, 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 void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    /**
     * Set the owner document
     * 
     * @param doc 
     */
    public void setOwnerDocument(Document doc) {
        document = doc;
    }

    /**
     * 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> or a <code>DocumentType</code>
     * which is not used with any <code>Document</code> yet, this is
     * <code>null</code>.
     */
    public Document getOwnerDocument() {
        if(document == null) {
            NodeImpl node = getParent();
            if (node != null) {
              return node.getOwnerDocument();
            }
        }
        return document;
    }

    /**
     * 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() {
        // make first it is editable.
        makeAttributesEditable();
        return convertAttrSAXtoDOM(attributes);
    }

    /**
     * The first child of this node. If there is no such node, this returns
     * <code>null</code>.
     */
    public Node getFirstChild() {
        if (children != null && !children.isEmpty()) {
            return (Node) children.get(0);
        } else {
            return null;
        }
    }

    /**
     * The last child of this node. If there is no such node, this returns
     * <code>null</code>.
     */
    public Node getLastChild() {
        if (children != null && !children.isEmpty()) {
            return (Node) children.get(children.size() - 1);
        } else {
            return null;
        }
    }

    /**
     * The node immediately following this node. If there is no such node,
     * this returns <code>null</code>.
     */
    public Node getNextSibling() {
        SOAPElement parent = getParentElement();
        if (parent == null) {
            return null;
        }
        Iterator iter = parent.getChildElements();
        Node nextSibling = null;
        while (iter.hasNext()) {
            if (iter.next() == this) {
                if (iter.hasNext()) {
                    return (Node) iter.next();
                } else {
                    return null;
                }
            }
        }
        return nextSibling; // should be null.
    }

    /**
     * The parent of this node. All nodes, except <code>Attr</code>,
     * <code>Document</code>, <code>DocumentFragment</code>,
     * <code>Entity</code>, and <code>Notation</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 Node getParentNode() {
        return (Node) getParent();
    }

    /**
     * The node immediately preceding this node. If there is no such node,
     * this returns <code>null</code>.
     */
    public Node getPreviousSibling() {
        SOAPElement parent = getParentElement();
        if (parent == null) {
            return null;
        }
        NodeList nl = parent.getChildNodes();
        int len = nl.getLength();
        int i = 0;
        Node previousSibling = null;
        while (i < len) {
            if (nl.item(i) == this) {
                return previousSibling;
            }
            previousSibling = nl.item(i);
            i++;
        }
        return previousSibling; // should be null.
    }

    /**
     * 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> is <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 an <code>Attribute</code>
     * directly, as opposed to be cloned as part of an <code>Element</code>
     * cloning operation, returns a specified attribute (
     * <code>specified</code> is <code>true</code>). Cloning any other type
     * of node simply returns a copy of this node.
     * <br>Note that cloning an immutable subtree results in a mutable copy,
     * but the children of an <code>EntityReference</code> clone are readonly
     * . In addition, clones of unspecified <code>Attr</code> nodes are
     * specified. And, cloning <code>Document</code>,
     * <code>DocumentType</code>, <code>Entity</code>, and
     * <code>Notation</code> nodes is implementation dependent.
     * 
     * @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 Node cloneNode(boolean deep) {
        return new NodeImpl(textRep);
    }

    /**
     * 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.
     */
    public NodeList getChildNodes() {
        if (children == null) {
            return NodeListImpl.EMPTY_NODELIST;
        } else {
            return new NodeListImpl(children);
        }
    }

    /**
     * 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 boolean isSupported(String feature, String version) {
        return false;  //TODO: Fix this for SAAJ 1.2 Implementation
    }

    /**
     * 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.
     * @throws org.w3c.dom.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 or this node
     *                                  itself.
     *                                  <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 or
     *                                  if the previous parent of the node being inserted is readonly.
     *
     */
    public Node appendChild(Node newChild) throws DOMException {
        if (newChild == null) {
            throw new DOMException
                    (DOMException.HIERARCHY_REQUEST_ERR,
                            "Can't append a null node.");
        }
        initializeChildren();
        // per DOM spec - must remove from tree. If newChild.parent == null,
        // detachNode() does nothing.  So this shouldn't hurt performace of
        // serializers.
        ((NodeImpl) newChild).detachNode();
        children.add(newChild);
        ((NodeImpl) newChild).parent = this;
        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.
     * @throws org.w3c.dom.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 Node removeChild(Node oldChild) throws DOMException {
        if (removeNodeFromChildList((NodeImpl) oldChild)) {
            setDirty();
            return oldChild;
        }
        throw new DOMException(DOMException.NOT_FOUND_ERR,
                "NodeImpl Not found");
    }

    private boolean removeNodeFromChildList(NodeImpl n) {
        boolean removed = false;
        initializeChildren();
        final Iterator itr = children.iterator();
        while (itr.hasNext()) {
            final NodeImpl node = (NodeImpl) itr.next();
            if (node == n) {
                removed = true;
                itr.remove();
            }
        }
        return removed;
    }

    /**
     * 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.
     * @throws org.w3c.dom.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 or this node
     *                                  itself.
     *                                  <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 or
     *                                  if the parent of the node being inserted 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 {
        initializeChildren();
        int position = children.indexOf(refChild);
        if (position < 0) {
            position = 0;
        }
        children.add(position, newChild);
        setDirty();
        return newChild;
    }

    /**
     * Replaces the child node <code>oldChild</code> with <code>newChild</code>
     * in the list of children, and returns the <code>oldChild</code> node.
     * <br>If <code>newChild</code> is a <code>DocumentFragment</code> object,
     * <code>oldChild</code> is replaced by all of the
     * <code>DocumentFragment</code> children, which are inserted in the
     * same order. 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.
     * @throws org.w3c.dom.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 put in is one of this node's ancestors or this node
     *                                  itself.
     *                                  <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 or the parent of
     *                                  the new node is readonly.
     *                                  <br>NOT_FOUND_ERR: Raised if <code>oldChild</code> is not a child of
     *                                  this node.
     */
    public Node replaceChild(Node newChild, Node oldChild) throws DOMException {
        initializeChildren();
        int position = children.indexOf(oldChild);
        if (position < 0) {
            throw new DOMException(DOMException.NOT_FOUND_ERR,
                    "NodeImpl Not found");
        }
        children.remove(position);
        children.add(position, newChild);
        setDirty();
        return oldChild;
    }

    /**
     * Returns the the value of the immediate child of this <code>Node</code>
     * object if a child exists and its value is text.
     * 
     * @return a <code>String</code> with the text of the immediate child of
     *         this <code>Node</code> object if (1) there is a child and
     *         (2) the child is a <code>Text</code> object;
     *         <code>null</code> otherwise
     */
    public String getValue() {
        return textRep.getNodeValue();
    }

    /**
     * Sets the parent of this <code>Node</code> object to the given
     * <code>SOAPElement</code> object.
     * 
     * @param parent the <code>SOAPElement</code> object to be set as
     *               the parent of this <code>Node</code> object
     * @throws javax.xml.soap.SOAPException if there is a problem in setting the
     *                                      parent to the given element
     * @see #getParentElement() getParentElement()
     */
    public void setParentElement(SOAPElement parent) throws SOAPException {
        if (parent == null)
            throw new IllegalArgumentException(
                    Messages.getMessage("nullParent00"));
        try {
            setParent((NodeImpl) parent);
        } catch (Throwable t) {
            throw new SOAPException(t);
        }
    }

    /**
     * Returns the parent element of this <code>Node</code> object.
     * This method can throw an <code>UnsupportedOperationException</code>
     * if the tree is not kept in memory.
     * 
     * @return the <code>SOAPElement</code> object that is the parent of
     *         this <code>Node</code> object or <code>null</code> if this
     *         <code>Node</code> object is root
     * @throws UnsupportedOperationException if the whole tree is not kept in memory
     * @see #setParentElement(javax.xml.soap.SOAPElement) setParentElement(javax.xml.soap.SOAPElement)
     */
    public SOAPElement getParentElement() {
        return (SOAPElement) getParent();
    }

    /**
     * Removes this <code>Node</code> object from the tree. Once
     * removed, this node can be garbage collected if there are no
     * application references to it.
     */
    public void detachNode() {
        setDirty();
        if (parent != null) {
            parent.removeChild(this);
            parent = null;
        }
    }

    /**
     * Notifies the implementation that this <code>Node</code>
     * object is no longer being used by the application and that the
     * implementation is free to reuse this object for nodes that may
     * be created later.
     * <P>
     * Calling the method <code>recycleNode</code> implies that the method
     * <code>detachNode</code> has been called previously.
     */
    public void recycleNode() {
        //TODO: Fix this for SAAJ 1.2 Implementation        
    }

    /**
     * If this is a Text node then this method will set its value, otherwise it
     * sets the value of the immediate (Text) child of this node. The value of
     * the immediate child of this node can be set only if, there is one child
     * node and that node is a Text node, or if there are no children in which
     * case a child Text node will be created.
     * 
     * @param value the text to set
     * @throws IllegalStateException if the node is not a Text  node and
     *                               either has more than one child node or has a child node that
     *                               is not a Text node
     */
    public void setValue(String value) {
        if (this instanceof org.apache.axis.message.Text) {
            setNodeValue(value);
        } else if (children != null) {
            if (children.size() != 1) {
                throw new IllegalStateException( "setValue() may not be called on a non-Text node with more than one child." );
            }
            javax.xml.soap.Node child = (javax.xml.soap.Node) children.get(0);
            if (!(child instanceof org.apache.axis.message.Text)) {
                throw new IllegalStateException( "setValue() may not be called on a non-Text node with a non-Text child." );
            }
            ((javax.xml.soap.Text)child).setNodeValue(value);
        } else {
            appendChild(new org.apache.axis.message.Text(value));
        }
    }

    /**
     * make the attributes editable
     * 
     * @return AttributesImpl
     */
    protected AttributesImpl makeAttributesEditable() {
        if (attributes == null || attributes instanceof NullAttributes) {
            attributes = new AttributesImpl();
        } else if (!(attributes instanceof AttributesImpl)) {
            attributes = new AttributesImpl(attributes);
        }
        return (AttributesImpl) attributes;
    }

    /**
     * The internal representation of Attributes cannot help being changed
     * It is because Attribute is not immutible Type, so if we keep out value and
     * just return it in another form, the application may chnae it, which we cannot
     * detect without some kind back track method (call back notifying the chnage.)
     * I am not sure which approach is better.
     */
    protected NamedNodeMap convertAttrSAXtoDOM(Attributes saxAttr) {
        try {
            org.w3c.dom.Document doc = org.apache.axis.utils.XMLUtils.newDocument();
            AttributesImpl saxAttrs = (AttributesImpl) saxAttr;
            NamedNodeMap domAttributes = new NamedNodeMapImpl();
            for (int i = 0; i < saxAttrs.getLength(); i++) {
                String uri = saxAttrs.getURI(i);
                String qname = saxAttrs.getQName(i);
                String value = saxAttrs.getValue(i);
                if (uri != null && uri.trim().length() > 0) {
                    // filterring out the tricky method to differentiate the null namespace
                    // -ware case
                    if (NULL_URI_NAME.equals(uri)) {
                        uri = null;
                    }
                    Attr attr = doc.createAttributeNS(uri, qname);
                    attr.setValue(value);
                    domAttributes.setNamedItemNS(attr);
                } else {
                    Attr attr = doc.createAttribute(qname);
                    attr.setValue(value);
                    domAttributes.setNamedItem(attr);
                }
            }
            return domAttributes;
        } catch (Exception ex) {
            log.error(Messages.getMessage("saxToDomFailed00"),ex);

            return null;
        }
    }

    /**
     * Initialize the children array
     */
    protected void initializeChildren() {
        if (children == null) {
            children = new ArrayList();
        }
    }

    /**
     * get the parent node
     * @return parent node
     */ 
    protected NodeImpl getParent() {
        return parent;
    }

    /**
     * Set the parent node and invoke appendChild(this) to 
     * add this node to the parent's list of children.
     * @param parent
     * @throws SOAPException
     */ 
    protected void setParent(NodeImpl parent) throws SOAPException {
        if (this.parent == parent) {
            return;
        }
        if (this.parent != null) {
            this.parent.removeChild(this);
        }
        if (parent != null) {
            parent.appendChild(this);
        }
        this.setDirty();
        this.parent = parent;
    }

    /**
     * print the contents of this node
     * @param context
     * @throws Exception
     */ 
    public void output(SerializationContext context) throws Exception {
        if (textRep == null)
            return;
        boolean oldPretty = context.getPretty();
        context.setPretty(false);
        if (textRep instanceof CDATASection) {
            context.writeString("<![CDATA[");
            context.writeString(((org.w3c.dom.Text) textRep).getData());
            context.writeString("]]>");
        } else if (textRep instanceof Comment) {
            context.writeString("<!--");
            context.writeString(((CharacterData) textRep).getData());
            context.writeString("-->");
        } else if (textRep instanceof Text) {
            context.writeSafeString(((Text) textRep).getData());
        }
        context.setPretty(oldPretty);
    }

    /**
     * get the dirty bit
     * @return
     */
    public boolean isDirty() {
        return _isDirty;
    }

    /**
     * set the dirty bit. will also set our parent as dirty, if there is one.
     * Note that clearing the dirty bit does <i>not</i> propagate upwards.
     * @param dirty new value of the dirty bit
     */
    public void setDirty(boolean dirty)
    {
        _isDirty = dirty;
        if (_isDirty && parent != null) {
            ((NodeImpl) parent).setDirty();
        }
    }

    public void setDirty()
    {
        _isDirty = true;
        if (parent != null) {
            ((NodeImpl) parent).setDirty();
        }
    }

    /* clear dirty flag recursively */
    public void reset() {
        if (children != null) {
        	for (int i=0; i<children.size(); i++) {
                ((NodeImpl) children.get(i)).reset();
            }
        }	
        this._isDirty = false;
    }
}
