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: DocumentImpl.java,v 1.25 2001/09/22 23:41:30 tom Exp $
 */

import org.dbxml.core.*;
import org.dbxml.core.Collection;
import org.dbxml.xml.*;
import org.dbxml.xml.dom.traversal.*;
import org.dbxml.server.*;
import org.dbxml.server.util.*;

import org.w3c.dom.*;
import org.w3c.dom.traversal.*;

import java.util.*;
import java.io.*;

/**
 * DocumentImpl
 */

public final class DocumentImpl extends ContainerNodeImpl implements CompressedDocument, DBDocument, DocumentTraversal {
   private DocumentType docType = null;
   private String version = null;
   private String actualEncoding = null;
   private String encoding = null;
   private boolean standalone = false;
   private boolean strictErrorChecking = false;
   private SymbolTable symbols = null;
   private boolean readOnly = false;
   private boolean authoring = false;

   public DocumentImpl() {
      super(null, true);
      //authoring = true;
   }

   public DocumentImpl(byte[] data, int pos, int len) {
      super(null, data, pos, len);
   }

   public DocumentImpl(byte[] data) {
      this(data, 0, data.length);
   }

   public DocumentImpl(byte[] data, SymbolTable symbols, NodeSource source) {
      this(data);
      this.symbols = symbols;
      this.source = source;
   }

   public DocumentImpl(Document doc) throws dbXMLException {
      super(null, true);
      
      boolean compress = true;

      if ( doc instanceof CompressedDocument ) {
         CompressedDocument c = (CompressedDocument)doc;
         symbols = c.getSymbols();
         if ( !c.isDirty() ) {
            data = c.getDataBytes();
            pos = c.getDataPos();
            len = c.getDataLen();
            compress = false;
         }
      }

      if ( compress ) {
         if ( symbols == null )
            symbols = new SymbolTable();
         data = DOMCompressor.Compress(doc, symbols);
         pos = 0;
         len = data.length;
      }

      if ( doc instanceof DBDocument ) {
         DBDocument d = (DBDocument)doc;
         source = d.getSource();
      }
   }

   public boolean isReadOnly() {
      return readOnly;
   }

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

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

   public boolean isCaching() {
      String cache = DBDocument.CACHE;

      int size = childNodes.getLength();
      for ( int i = 0; i < size; i++ ) {
         Node n = childNodes.item(i);
         if ( n.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE
           && n.getNodeName().equals(CACHE_CONTROL) ) {
            cache = n.getNodeValue().trim();
            break;
         }
      }

      return (cache != null && cache.equals(CACHE));
   }

   public void setCaching(boolean caching) {
      String cache = DBDocument.CACHE;

      int size = childNodes.getLength();
      for ( int i = 0; i < size; i++ ) {
         Node n = childNodes.item(i);
         if ( n.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE
           && n.getNodeName().equals(CACHE_CONTROL) ) {
              n.setNodeValue(caching ? CACHE : NOCACHE);
            return;
         }
      }

      ProcessingInstruction pi = createProcessingInstruction(CACHE_CONTROL, caching ? CACHE : NOCACHE);
      insertBefore(pi, getDocumentElement());
   }

   public boolean isAuthoring() {
      return authoring;
   }

   public void setAuthoring(boolean authoring) {
      this.authoring = authoring;
   }

   public SymbolTable getSymbols() {
      return symbols;
   }

   public void setSymbols(SymbolTable symbols) {
      this.symbols = symbols;
   }

   public void expandSource() {
      ElementImpl e = (ElementImpl)getDocumentElement();
      if ( e != null )
         e.expandSource();
   }
   
   public Node getNodeAtPos(int pos) {
      return null; // TODO: This
   }

   public short getNodeType() {
      return Node.DOCUMENT_NODE;
   }

   public String getNodeName() {
      return "#document";
   }

   /**
    * The Document Type Declaration (see <code>DocumentType</code>) associated
    * with  this document. For HTML documents as well as XML documents without
    * a document type declaration this returns <code>null</code>. The DOM Level
    *  1 does not support editing the Document Type Declaration, therefore
    * <code>docType</code> cannot be altered in any way.
    */
   public DocumentType getDoctype() {
      return docType;
   }

   public void setDoctype(DocumentType docType) {
      this.docType = docType;
   }

   /**
    * The <code>DOMImplementation</code> object that handles this document. A
    * DOM application may use objects from multiple  implementations.
    */
   public DOMImplementation getImplementation() {
      return DOMImplementationImpl.getInstance();
   }

   /**
    * Creates a <code>Text</code> node given the specified string.
    * @param data The data for the node.
    * @return The new <code>Text</code> object.
    */
   public Text createTextNode(String data) {
      return new TextImpl(this, data);
   }

   /**
    * This is a convenience attribute that allows direct access to the child
    * node that is the root element of  the document. For HTML documents, this
    * is the element with the tagName "HTML".
    */
   public Element getDocumentElement() {
      checkLoaded();
      Iterator enum = childNodes.iterator();
      while ( enum.hasNext() ) {
         Node node = (Node)enum.next();
         if ( node.getNodeType() == Node.ELEMENT_NODE )
            return (Element)node;
      }
      return null;
   }

   /**
    * Creates a <code>CDATASection</code> node whose value  is the specified
    * string.
    * @param data The data for the <code>CDATASection</code> contents.
    * @return The new <code>CDATASection</code> object.
    * @exception DOMException
    *   NOT_SUPPORTED_ERR: Raised if this document is an HTML document.
    */
   public CDATASection createCDATASection(String data) throws DOMException {
      return new CDATASectionImpl(this, data);
   }

   /**
    * Creates an element of the type specified. Note that the instance returned
    * implements the Element interface, so attributes can be specified
    * directly  on the returned object.
    * @param tagName The name of the element type to instantiate. For XML, this
    *   is case-sensitive. For HTML, the  <code>tagName</code> parameter may
    *   be provided in any case,  but it must be mapped to the canonical
    *   uppercase form by  the DOM implementation.
    * @return A new <code>Element</code> object.
    * @exception DOMException
    *   INVALID_CHARACTER_ERR: Raised if the specified name contains an
    *   invalid character.
    */
   public Element createElement(String tagName) throws DOMException {
      return new ElementImpl(this, tagName);
   }

   /**
    * Creates an empty <code>DocumentFragment</code> object.
    * @return A new <code>DocumentFragment</code>.
    */
   public DocumentFragment createDocumentFragment() {
      return new DocumentFragmentImpl(this);
   }

   /**
    * Creates an <code>Attr</code> of the given name. Note that the
    * <code>Attr</code> instance can then be set on an <code>Element</code>
    * using the <code>setAttribute</code> method.
    * @param name The name of the attribute.
    * @return A new <code>Attr</code> object.
    * @exception DOMException
    *   INVALID_CHARACTER_ERR: Raised if the specified name contains an
    *   invalid character.
    */
   public Attr createAttribute(String name) throws DOMException {
      return new AttrImpl(this, name);
   }

   /**
    * Creates a <code>Comment</code> node given the specified string.
    * @param data The data for the node.
    * @return The new <code>Comment</code> object.
    */
   public Comment createComment(String data) {
      return new CommentImpl(this, data);
   }

   /**
    * Creates a <code>ProcessingInstruction</code> node given the specified
    * name and data strings.
    * @param target The target part of the processing instruction.
    * @param data The data for the node.
    * @return The new <code>ProcessingInstruction</code> object.
    * @exception DOMException
    *   INVALID_CHARACTER_ERR: Raised if an invalid character is specified.
    *   <br>NOT_SUPPORTED_ERR: Raised if this document is an HTML document.
    */
   public ProcessingInstruction createProcessingInstruction(String target, String data) throws DOMException {
      return new ProcessingInstructionImpl(this, target, data);
   }

   /**
    * Creates an EntityReference object.
    * @param name The name of the entity to reference.
    * @return The new <code>EntityReference</code> object.
    * @exception DOMException
    *   INVALID_CHARACTER_ERR: Raised if the specified name contains an
    *   invalid character.
    *   <br>NOT_SUPPORTED_ERR: Raised if this document is an HTML document.
    */
   public EntityReference createEntityReference(String name) throws DOMException {
      return new EntityReferenceImpl(this, name);
   }

   public Node importNode(Node importedNode, boolean deep) {
      try {
         // If we're a dbXML DOM Node and share the same symbol table, then we're golden
         if ( importedNode instanceof NodeImpl ) {
            NodeImpl impl = (NodeImpl)importedNode;
            DocumentImpl docImpl = (DocumentImpl)impl.getOwnerDocument();
            if ( docImpl.getSymbols() != null && ( docImpl.getSymbols() == symbols ) ) {
               NodeImpl clone = (NodeImpl)impl.cloneNode(deep);
               clone.setParentNode(this);
               return clone;
            }
         }

         // Crap, we have to do a full graph copy
         Node result = null;
         switch ( importedNode.getNodeType() ) {
            case Node.ATTRIBUTE_NODE:
               Attr sattr = (Attr)importedNode;
               Attr attr = createAttribute(sattr.getName());
               attr.setValue(sattr.getValue());
               result = attr;
               break;

            case Node.CDATA_SECTION_NODE:
               result = createCDATASection(importedNode.getNodeValue());
               break;

            case Node.COMMENT_NODE:
               result = createComment(importedNode.getNodeValue());
               break;

            case Node.ELEMENT_NODE:
               Element selem = (Element)importedNode;
               Element elem = createElement(selem.getTagName());
               NamedNodeMap attrs = selem.getAttributes();
               int size = attrs.getLength();
               for ( int i = 0; i < size; i++ ) {
                  Attr a = (Attr)attrs.item(i);
                  Attr ai = createAttribute(a.getName());
                  ai.setValue(a.getValue());
                  elem.setAttributeNode(ai);
               }
               result = elem;
               break;

            case Node.ENTITY_REFERENCE_NODE:
               result = createEntityReference(importedNode.getNodeValue());
               break;

            case Node.PROCESSING_INSTRUCTION_NODE:
               result = createProcessingInstruction(importedNode.getNodeName(), importedNode.getNodeValue());
               break;

            case Node.TEXT_NODE:
               result = createTextNode(importedNode.getNodeValue());
               break;

            default:
         }

         if ( deep && result != null ) {
            NodeList list = importedNode.getChildNodes();
            int size = list.getLength();
            for ( int i = 0; i < size; i++ ) {
               Node n = list.item(i);
               result.appendChild(importNode(n, deep));
            }
         }

         return result;
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
         return null;
      }
   }

   public Element createElementNS(String namespaceURI, String qualifiedName) {
      return new ElementImpl(this, qualifiedName);
   }

   public Attr createAttributeNS(String namespaceURI, String qualifiedName) {
      return new AttrImpl(this, qualifiedName);
   }

   public NodeIterator createNodeIterator(Node root, int whatToShow, NodeFilter filter, boolean entityReferenceExpansion) throws DOMException {
      return new TreeWalkerImpl(root, whatToShow, filter, entityReferenceExpansion);
   }

   public TreeWalker createTreeWalker(Node root, int whatToShow, NodeFilter filter, boolean entityReferenceExpansion) throws DOMException {
      return new TreeWalkerImpl(root, whatToShow, filter, entityReferenceExpansion);
   }
   
   // DOM Level 3 Stuff
   
   public Node adoptNode(Node src) {
      return importNode(src, true);
   }
   
   public String getActualEncoding() {
      checkLoaded();
      return actualEncoding;
   }
   
   public void setActualEncoding(String actualEncoding) {
      checkReadOnly();
      checkLoaded();
      this.actualEncoding = actualEncoding;
   }
   
   public String getEncoding() {
      checkLoaded();
      return encoding;
   }
   
   public void setEncoding(String encoding) {
      checkReadOnly();
      checkLoaded();
      this.encoding = encoding;
   }
   
   public String getVersion() {
      checkLoaded();
      return version;
   }
   
   public void setVersion(String version) {
      checkReadOnly();
      checkLoaded();
      this.version = version;
   }
   
   public boolean getStandalone() {
      checkLoaded();
      return standalone;
   }
   
   public void setStandalone(boolean standalone) {
      checkReadOnly();
      checkLoaded();
      this.standalone = standalone;
   }
   
   public boolean getStrictErrorChecking() {
      checkLoaded();
      return strictErrorChecking;
   }
   
   public void setStrictErrorChecking(boolean strictErrorChecking) {
      checkReadOnly();
      checkLoaded();
      this.strictErrorChecking = strictErrorChecking;
   }
}


