/*
 * 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.commons.logging.Log;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;

import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import java.util.ArrayList;
import java.util.List;

/**
 * An implemenation of the abstract Node.
 * <p/>
 * This class should not expose functionality that is not part of
 * {@link javax.xml.soap.Node}. Client code should use <code>javax.xml.soap.Node</code> instead of this class.
 * <p/>
 * When creating a DOM2 tree the objects maintained by the tree are <code>org.w3c.dom.Node</code> objects
 * and not <code>javax.xml.soap.Node</code> objects.
 * <p/>
 * This implementation schields the client from the the underlying DOM2 tree, returning <code>javax.xml.soap.Node</code>
 * objects.
 *
 * @author Thomas Diesler (thomas.diesler@jboss.org)
 */
public class NodeImpl implements javax.xml.soap.Node
{

   // provide logging
   private static Log log = LogFactory.getLog(NodeImpl.class.getName());

   // The parent of this Node
   protected SOAPElementImpl soapParent;
   // This org.w3c.dom.Node
   protected org.w3c.dom.Node domNode;
   // A list af soap node children
   private List soapChildren = new ArrayList();

   /**
    * Construct the Node for a given org.w3c.dom.Node
    */
   NodeImpl(org.w3c.dom.Node node)
   {
      this.domNode = node;
   }

   // javax.xml.soap.Node *********************************************************************************************

   /**
    * Removes this Node object from the tree.
    */
   public void detachNode()
   {
      assertSOAPParent();

      org.w3c.dom.Node parent = domNode.getParentNode();
      if (parent != null)
      {

         parent.removeChild(domNode);
         ((NodeImpl)soapParent).soapChildren.remove(this);

         // remove the Node from the children list maintained by SOAPElementAxisImpl
         if (soapParent instanceof SOAPElementAxisImpl)
         {
            ((SOAPElementAxisImpl)soapParent).removeChild(this);
         }

         soapParent = null;
      }
   }

   /**
    * Returns the parent node of this Node object.
    * This method can throw an UnsupportedOperationException if the tree is not kept in memory.
    *
    * @return the SOAPElement object that is the parent of this Node object or null if this Node object is root
    */
   public SOAPElement getParentElement()
   {
      return soapParent;
   }

   /**
    * Sets the parent of this Node object to the given SOAPElement object.
    *
    * @param parent the SOAPElement object to be set as the parent of this Node object
    * @throws javax.xml.soap.SOAPException if there is a problem in setting the parent to the given node
    */
   public void setParentElement(SOAPElement parent) throws SOAPException
   {

      // detach from the old parent
      if (soapParent != null)
         detachNode();

      soapParent = (SOAPElementImpl)parent;
   }

   /**
    * Returns the value of this node if this is a Text node or the value of the immediate child of this node otherwise.
    * <p/>
    * If there is an immediate child of this Node that it is a Text node then it's value will be returned.
    * If there is more than one Text node then the value of the first Text Node will be returned.
    * Otherwise null is returned.
    *
    * @return a String with the text of this node if this is a Text node or the text contained by the first immediate
    *         child of this Node object that is a Text object if such a child exists; null otherwise.
    */
   public String getValue()
   {

      // The Text node should overwrite this.
      // When we get here this is not a text node
      if (this instanceof javax.xml.soap.Text)
         throw new IllegalStateException("javax.xml.soap.Text should take care of this");

      org.w3c.dom.Node child = (org.w3c.dom.Node)getFirstChild();
      if (child instanceof org.w3c.dom.Text)
         return ((org.w3c.dom.Text)child).getNodeValue();

      return null;
   }

   /**
    * 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.
    * <p/>
    * 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 A value string
    * @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)
   {

      // The Text node should overwrite this.
      // When we get here this is not a text node
      if (this instanceof javax.xml.soap.Text)
         throw new IllegalStateException("javax.xml.soap.Text should take care of this");

      org.w3c.dom.Node child = (org.w3c.dom.Node)getFirstChild();
      if (child instanceof org.w3c.dom.Text)
         ((org.w3c.dom.Text)child).setNodeValue(value);

      if (child == null)
      {
         child = domNode.getOwnerDocument().createTextNode(value);
         appendChild(new TextImpl(child));
      }
   }

   /**
    * Notifies the implementation that this Node 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.
    * Calling the method recycleNode implies that the method detachNode has been called previously.
    */
   public void recycleNode()
   {

   }

   // org.w3c.dom.Node *******************************************************************************************

   public String getNodeName()
   {
      return domNode.getNodeName();
   }

   public String getNodeValue() throws DOMException
   {
      return domNode.getNodeValue();
   }

   public void setNodeValue(String nodeValue) throws DOMException
   {
      domNode.setNodeValue(nodeValue);
   }

   public short getNodeType()
   {
      return domNode.getNodeType();
   }

   public org.w3c.dom.Node getParentNode()
   {
      assertSOAPParent();
      return domNode.getParentNode();
   }

   public NodeList getChildNodes()
   {
      return domNode.getChildNodes();
   }

   public org.w3c.dom.Node getFirstChild()
   {
      return domNode.getFirstChild();
   }

   public org.w3c.dom.Node getLastChild()
   {
      return domNode.getLastChild();
   }

   public org.w3c.dom.Node getPreviousSibling()
   {
      return domNode.getPreviousSibling();
   }

   public org.w3c.dom.Node getNextSibling()
   {
      return domNode.getNextSibling();
   }

   public NamedNodeMap getAttributes()
   {
      return domNode.getAttributes();
   }

   public Document getOwnerDocument()
   {
      return domNode.getOwnerDocument();
   }

   public org.w3c.dom.Node insertBefore(org.w3c.dom.Node newChild, org.w3c.dom.Node refChild) throws DOMException
   {
      assertSOAPNode(newChild);
      assertSOAPNode(refChild);

      int index = soapChildren.indexOf(refChild);
      if (index < 0)
         throw new IllegalArgumentException("Cannot find refChild in list of javax.xml.soap.Node children");

      NodeImpl soapNewNode = (NodeImpl)newChild;
      NodeImpl soapRefNode = (NodeImpl)refChild;
      domNode.insertBefore(soapNewNode.domNode, soapRefNode.domNode);
      soapChildren.add(index, soapNewNode);

      return newChild;
   }

   public org.w3c.dom.Node replaceChild(org.w3c.dom.Node newChild, org.w3c.dom.Node oldChild) throws DOMException
   {
      assertSOAPNode(newChild);
      assertSOAPNode(oldChild);

      int index = soapChildren.indexOf(oldChild);
      if (index < 0)
         throw new DOMException(DOMException.NOT_FOUND_ERR, "Cannot find oldChild in list of javax.xml.soap.Node children");

      NodeImpl soapNewNode = (NodeImpl)newChild;
      NodeImpl soapOldNode = (NodeImpl)oldChild;
      domNode.replaceChild(soapNewNode.domNode, soapOldNode.domNode);
      soapChildren.remove(index);
      soapChildren.add(index, soapNewNode);

      return newChild;
   }

   public org.w3c.dom.Node removeChild(org.w3c.dom.Node oldChild) throws DOMException
   {
      assertSOAPNode(oldChild);

      int index = soapChildren.indexOf(oldChild);
      if (index < 0)
         throw new DOMException(DOMException.NOT_FOUND_ERR, "Cannot find oldChild in list of javax.xml.soap.Node children");

      NodeImpl soapOldNode = (NodeImpl)oldChild;
      domNode.removeChild(soapOldNode.domNode);
      soapChildren.remove(index);

      return oldChild;
   }

   public org.w3c.dom.Node appendChild(org.w3c.dom.Node newChild) throws DOMException
   {
      assertSOAPNode(newChild);

      if ((this instanceof SOAPElementImpl) == false)
         throw new DOMException(DOMException.INVALID_ACCESS_ERR, "Cannot append child to this node: " + this);

      NodeImpl soapNode = (NodeImpl)newChild;
      domNode.appendChild(soapNode.domNode);

      soapNode.soapParent = (SOAPElementImpl)this;
      soapChildren.add(soapNode);

      return newChild;
   }

   public boolean hasChildNodes()
   {
      return domNode.hasChildNodes();
   }

   public org.w3c.dom.Node cloneNode(boolean deep)
   {

      NodeImpl soapNode = new NodeImpl(domNode.cloneNode(deep));
      if (deep == true)
      {
         for (int i = 0; i < soapChildren.size(); i++)
         {
            NodeImpl node = (NodeImpl)soapChildren.get(i);
            soapNode.soapChildren.add(node.cloneNode(deep));
         }
      }

      return soapNode;
   }

   public void normalize()
   {
      domNode.normalize();
   }

   public boolean isSupported(String feature, String version)
   {
      return domNode.isSupported(feature, version);
   }

   public String getNamespaceURI()
   {
      return domNode.getNamespaceURI();
   }

   public String getPrefix()
   {
      return domNode.getPrefix();
   }

   public void setPrefix(String prefix) throws DOMException
   {
      domNode.setPrefix(prefix);
   }

   public String getLocalName()
   {
      return domNode.getLocalName();
   }

   public boolean hasAttributes()
   {
      return domNode.hasAttributes();
   }

   public int hashCode()
   {
      return domNode.hashCode();
   }

   public String toString()
   {
      return super.toString() + "[" + domNode.toString() + "]";
   }

   private void assertSOAPNode(org.w3c.dom.Node node)
   {
      if ((node instanceof NodeImpl) == false)
         throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Operation only supported for javax.xml.soap.Node, this is a " + node);
   }

   private void assertSOAPParent()
   {
      org.w3c.dom.Node domParent = domNode.getParentNode();
      if (domParent != null && soapParent == null)
         throw new IllegalStateException("Inconsistent node, has a DOM parent but no SOAP parent: " + this);
      if (domParent == null && soapParent != null)
         throw new IllegalStateException("Inconsistent node, has a SOAP parent but no DOM parent: " + this);
   }
}
