package org.dbxml.xml.dom;

/*
 * dbXML License, Version 1.0
 *
 *
 * Copyright (c) 1999-2001 The dbXML Group, L.L.C.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 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 end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by
 *        The dbXML Group (http://www.dbxml.com/)."
 *    Alternately, this acknowledgment may appear in the software
 *    itself, if and wherever such third-party acknowledgments normally
 *    appear.
 *
 * 4. The names "dbXML" and "The dbXML Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    info@dbxml.com.
 *
 * 5. Products derived from this software may not be called "dbXML",
 *    nor may "dbXML" appear in their name, without prior written
 *    permission of The dbXML Group.
 *
 * THIS SOFTWARE IS PROVIDED ``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 THE DBXML GROUP 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.
 *
 * $Id: ContainerNodeImpl.java,v 1.24 2001/08/05 07:23:15 tom Exp $
 */

import org.dbxml.xml.*;
import org.dbxml.server.util.*;

import org.w3c.dom.*;
import org.w3c.dom.traversal.*;
import java.io.*;
import java.util.*;

/**
 * ContainerNodeImpl performs most of the child-rearing behavior of the
 * Element and Document implementations.
 */

public abstract class ContainerNodeImpl extends NodeImpl {
   protected NodeListImpl childNodes = new NodeListImpl(this);

   public ContainerNodeImpl() {
   }
   
   public ContainerNodeImpl(NodeImpl parentNode, byte[] data, int pos, int len) {
      super(parentNode, data, pos, len);
   }

   public ContainerNodeImpl(NodeImpl parentNode, boolean dirty) {
      super(parentNode, dirty);
   }

   protected boolean isNodeTypeValid(short type) {
      return true;
   }

   protected final Node getPreviousSibling(Node node) {
      checkLoaded();
      int pos = childNodes.indexOf(node)-1;
      return pos >= 0 ? childNodes.item(pos)
                      : null;
   }

   protected final Node getNextSibling(Node node) {
      checkLoaded();
      int pos = childNodes.indexOf(node)+1;
      return pos < childNodes.getLength() ? childNodes.item(pos)
                                          : null;
   }

   protected void checkLoaded() {
      if ( loaded )
         return;
      else
         loaded = true;

      try {
         if ( data != null ) {
            DocumentImpl doc = (DocumentImpl)getOwnerDocument();
            loadChildren(doc.getSymbols());
         }
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
      }
   }

   protected final void loadChildren(SymbolTable st) throws IOException {
      ByteArrayInput bis = new ByteArrayInput(data, pos, len);
      XMLCompressedInput in = new XMLCompressedInput(bis, st);

      if ( getNodeType() == Node.ELEMENT_NODE ) {
         // Have to skip the attributes
         in.readSignature();
         in.readContentSize();
         in.readShort();             // Element Symbol
         int attrCount = in.readAttributeCount();
         for ( int i = 0 ; i < attrCount; i++ ) {
            in.readShort();          // Attribute Symbol
            in.skip(in.readShort()); // Attribute Length
         }
      }
      else
         in.readInt();

      List links = new ArrayList();
      boolean doAuto = false;

      while ( bis.available() > 0 ) {
         int pos = bis.getPos();
         byte signature = in.readSignature();
         int len = in.readContentSize();
         if ( len == 0 )
            len = 1;

         switch ( in.getNodeType() ) {

            case Node.ELEMENT_NODE:
               ElementImpl elem = new ElementImpl(this, data, pos, len);
               childNodes.add(elem);
               if ( elem.hasAutoLink() ) {
                  doAuto = true;
                  links.add(elem);
               }
               break;

            case Node.TEXT_NODE:
               childNodes.add(new TextImpl(this, data, pos, len));
               break;

            case Node.CDATA_SECTION_NODE:
               childNodes.add(new CDATASectionImpl(this, data, pos, len));
               break;

            case Node.ENTITY_REFERENCE_NODE:
               childNodes.add(new EntityReferenceImpl(this, data, pos, len));
               break;

            case Node.ENTITY_NODE:
               childNodes.add(new EntityImpl(this, data, pos, len));
               break;

            case Node.PROCESSING_INSTRUCTION_NODE:
               childNodes.add(new ProcessingInstructionImpl(this, data, pos, len));
               break;

            case Node.COMMENT_NODE:
               childNodes.add(new CommentImpl(this, data, pos, len));
               break;

            case Node.NOTATION_NODE:
               childNodes.add(new NotationImpl(this, data, pos, len));
               break;
         }

         bis.setPos(pos);
         bis.skip(len);
      }

      if ( doAuto ) {
         Iterator enum = links.iterator();
         while ( enum.hasNext() ) {
            ElementImpl elem = (ElementImpl)enum.next();
            if ( childNodes.contains(elem) )
               elem.checkLoaded();
         }
      }
   }

   /**
    *  This is a convenience method to allow easy determination of whether a
    * node has any children.
    * @return  <code>true</code> if the node has any children,
    *   <code>false</code> if the node has no children.
    */
   public final boolean hasChildNodes() {
      checkLoaded();
      return childNodes.getLength() > 0;
   }

   public final NodeList getChildNodes() {
      checkLoaded();
      return childNodes;
   }

   /**
    * The first child of this node. If there is no such node, this returns
    * <code>null</code>.
    */
   public final Node getFirstChild() {
      checkLoaded();
      if ( childNodes.size() > 0 )
         return childNodes.item(0);
      else
         return null;
   }

   /**
    * The last child of this node. If there is no such node, this returns
    * <code>null</code>.
    */
   public final Node getLastChild() {
      checkLoaded();
      if ( childNodes.size() > 0 )
         return childNodes.item(childNodes.getLength()-1);
      else
         return null;
   }

   /**
    * Replaces the child node <code>oldChild</code> with <code>newChild</code>
    * in the list of children, and returns the <code>oldChild</code> node. If
    * the <code>newChild</code> is already in the tree, it is first removed.
    * @param newChild The new node to put in the child list.
    * @param oldChild The node being replaced in the list.
    * @return The node replaced.
    * @exception DOMException
    *   HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not
    *   allow children of the type of the <code>newChild</code> node, or it
    *   the node to put in is one of this node's ancestors.
    *   <br>WRONG_DOCUMENT_ERR: Raised if <code>newChild</code> was created
    *   from a different document than the one that created this node.
    *   <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
    *   <br>NOT_FOUND_ERR: Raised if <code>oldChild</code> is not a child of
    *   this node.
    */
   public final synchronized Node replaceChild(Node newChild, Node oldChild) throws DOMException {
      checkLoaded();
      checkReadOnly();
      if ( !isNodeTypeValid(newChild.getNodeType()) )
         throw EX_HIERARCHY_REQUEST;
      int idx = childNodes.indexOf(oldChild);
      if ( idx >= 0 ) {
         if ( newChild.getNodeType() == DOCUMENT_FRAGMENT_NODE ) {
            childNodes.remove(idx);
            NodeList nl = ((DocumentFragment)newChild).getChildNodes();
            for ( int i = 0; i < nl.getLength(); i++ ) {
               NodeImpl impl = (NodeImpl)nl.item(i);
               impl.setParentNode(this);
               childNodes.add(idx+i, impl);
            }
         }
         else {
            NodeImpl impl = (NodeImpl)newChild;
            impl.setParentNode(this);
            childNodes.set(idx, impl);
         }
      }
      setDirty();
      return oldChild;
   }

   /**
    * Inserts the node <code>newChild</code> before the existing child node
    * <code>refChild</code>. If <code>refChild</code> is <code>null</code>,
    * insert <code>newChild</code> at the end of the list of children.
    * <br>If <code>newChild</code> is a <code>DocumentFragment</code> object,
    * all of its children are inserted, in the same order, before
    * <code>refChild</code>. If the <code>newChild</code> is already in the
    * tree, it is first removed.
    * @param newChild The node to insert.
    * @param refChild The reference node, i.e., the node before which the new
    *   node must be inserted.
    * @return The node being inserted.
    * @exception DOMException
    *   HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not
    *   allow children of the type of the <code>newChild</code> node, or if
    *   the node to insert is one of this node's ancestors.
    *   <br>WRONG_DOCUMENT_ERR: Raised if <code>newChild</code> was created
    *   from a different document than the one that created this node.
    *   <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
    *   <br>NOT_FOUND_ERR: Raised if <code>refChild</code> is not a child of
    *   this node.
    */
   public final synchronized Node insertBefore(Node newChild, Node refChild) throws DOMException {
      checkLoaded();
      checkReadOnly();
      int idx = childNodes.indexOf(refChild);
      if ( idx >= 0 ) {
         if ( !isNodeTypeValid(newChild.getNodeType()) )
             throw EX_HIERARCHY_REQUEST;
         if ( newChild.getNodeType() == DOCUMENT_FRAGMENT_NODE ) {
            NodeList nl = ((DocumentFragment)newChild).getChildNodes();
            for ( int i = 0; i < nl.getLength(); i++ ) {
               NodeImpl impl = (NodeImpl)nl.item(i);
               impl.setParentNode(this);
               childNodes.add(idx+i, impl);
            }
         }
         else {
            NodeImpl impl = (NodeImpl)newChild;
            impl.setParentNode(this);
            childNodes.add(idx, impl);
         }
      }
      setDirty();
      return newChild;
   }

   /**
    * Removes the child node indicated by <code>oldChild</code> from the list
    * of children, and returns it.
    * @param oldChild The node being removed.
    * @return The node removed.
    * @exception DOMException
    *   NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
    *   <br>NOT_FOUND_ERR: Raised if <code>oldChild</code> is not a child of
    *   this node.
    */
   public final synchronized Node removeChild(Node oldChild) throws DOMException {
      checkLoaded();
      checkReadOnly();
      if ( !childNodes.remove(oldChild) )
         throw EX_NOT_FOUND;
      setDirty();
      return oldChild;
   }

   /**
    * Adds the node <code>newChild</code> to the end of the list of children of
    * this node. If the <code>newChild</code> is already in the tree, it is
    * first removed.
    * @param newChild The node to add.If it is a  <code>DocumentFragment</code>
    *   object, the entire contents of the document fragment are moved into
    *   the child list of this node
    * @return The node added.
    * @exception DOMException
    *   HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not
    *   allow children of the type of the <code>newChild</code> node, or if
    *   the node to append is one of this node's ancestors.
    *   <br>WRONG_DOCUMENT_ERR: Raised if <code>newChild</code> was created
    *   from a different document than the one that created this node.
    *   <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
    */
   public final synchronized Node appendChild(Node newChild) throws DOMException {
      checkLoaded();
      checkReadOnly();
      if ( !isNodeTypeValid(newChild.getNodeType()) )
         throw EX_HIERARCHY_REQUEST;
      if ( newChild.getNodeType() == DOCUMENT_FRAGMENT_NODE ) {
         NodeList nl = ((DocumentFragment)newChild).getChildNodes();
         for ( int i = 0; i < nl.getLength(); i++ ) {
            NodeImpl impl = (NodeImpl)nl.item(i);
            impl.setParentNode(this);
            childNodes.add(impl);
         }
      }
      else {
         NodeImpl impl = (NodeImpl)newChild;
         impl.setParentNode(this);
         childNodes.add(impl);
      }
      setDirty();
      return newChild;
   }

    /**
     *  Puts all <code>Text</code> nodes in the full depth of the sub-tree
     * underneath this <code>Node</code> , including attribute nodes, into a
     * "normal" form where only markup (e.g., tags, comments, processing
     * instructions, CDATA sections, and entity references) separates
     * <code>Text</code> nodes, i.e., there are neither adjacent
     * <code>Text</code> nodes nor empty <code>Text</code> nodes. This can be
     * used to ensure that the DOM view  of a document is the same as if it
     * were saved and re-loaded, and is useful when operations (such as
     * XPointer lookups) that depend on a particular document tree structure
     * are to be used. In cases where the document contains
     * <code>CDATASections</code> , the normalize operation alone may not be
     * sufficient, since XPointers do not differentiate between
     * <code>Text</code> nodes and <code>CDATASection</code> nodes.
     * @since DOM Level 2
     */
   public final synchronized void normalize() {
      checkLoaded();
      checkReadOnly();
      List set = new ArrayList();

      NodeListImpl newList = new NodeListImpl(this);
      boolean modified = false;

      int size = childNodes.size();
      for ( int i = 0; i < size; i++ ) {
         Node add = null;
         Node n = (Node)childNodes.get(i);
         short type = n.getNodeType();

         switch ( type ) {
            case Node.TEXT_NODE:
               set.add(n);
               break;
            case Node.ELEMENT_NODE:
               n.normalize();
            default:
               add = n;
         }

         if ( !set.isEmpty() && ( type != Node.TEXT_NODE || i == size-1 ) ) {
            Text s = (Text)set.get(0);
            int len = set.size();
            for ( int j = 1 ; j < len; j++ ) {
               modified = true;
               Text a = (Text)set.get(j);
               s.appendData(a.getData());
            }
            newList.add(s);
            set.clear();
         }
         if ( add != null )
            newList.add(add);
      }

      if ( modified ) {
         childNodes = newList;
         setDirty();
      }
   }

   /**
    * Returns a <code>NodeList</code> of all descendant elements with a given
    * tag name, in the order in which they would be encountered in a preorder
    * traversal of the <code>Element</code> tree.
    * @param name The name of the tag to match on. The special value "*"
    *   matches all tags.
    * @return A list of matching <code>Element</code> nodes.
    */
   public final NodeList getElementsByTagName(final String name) {
      checkLoaded();
      NodeListImpl list = new NodeListImpl(this);

      NodeFilter filter = new NodeFilter() {
         public short acceptNode(Node node) {
            if ( node.getNodeName().equals(name) )
               return (short)NodeFilter.FILTER_ACCEPT;
            else
               return (short)NodeFilter.FILTER_SKIP;
         }
      };

      NodeIterator enum = ((DocumentTraversal)getOwnerDocument()).createNodeIterator(this, NodeFilter.SHOW_ELEMENT, filter, false);
      Node node = null;
      do {
         node = enum.nextNode();
         if ( node != null )
            list.add(node);
      }
      while ( node != null );
      return list;
   }

   public final NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) {
      checkLoaded();
      NodeListImpl list = new NodeListImpl(this);

      NodeFilter filter = new NodeFilter() {
         public short acceptNode(Node node) {
            if ( node.getLocalName().equals(localName)
              && node.getNamespaceURI().equals(namespaceURI) )
               return (short)NodeFilter.FILTER_ACCEPT;
            else
               return (short)NodeFilter.FILTER_SKIP;
         }
      };

      NodeIterator enum = ((DocumentTraversal)getOwnerDocument()).createNodeIterator(this, NodeFilter.SHOW_ELEMENT, filter, false);
      Node node = null;
      do {
         node = enum.nextNode();
         if ( node != null )
            list.add(node);
      }
      while ( node != null );
      return list;
   }

   public final Element getElementById(String elementId) {
      return null;
   }
}

