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: DOMCompressor.java,v 1.7 2001/08/05 07:23:16 tom Exp $
 */

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

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

/**
 * DOMCompressor is an OutputStream extension that provides functions for
 * writing DOM types to a dbXML Compressed XML Stream.
 */

public final class DOMCompressor extends XMLCompressedOutput {

   public DOMCompressor(OutputStream os, SymbolTable st) {
      super(os, st);
   }

   /**
    * writeNode writes a Node to the compressed output stream.  This method
    * is recursive and will write all children of the specific Node.
    *
    * @param node The Node to write
    * @throws IOException If the write failed
    */
   public void writeNode(Node node) throws IOException {
      if ( node instanceof NodeImpl ) {
         NodeImpl impl = (NodeImpl)node;
         if ( !impl.dirty && impl.data != null ) {
            write(impl.data, impl.pos, impl.len);
            flush();
            return;
         }
      }

      // Have to do it manually
      switch ( node.getNodeType() ) {

         case Node.ELEMENT_NODE: {
            String nsURI = node.getNamespaceURI();
            short symbolID;
            if ( nsURI != null )
               symbolID = st.getSymbol(node.getNodeName(), nsURI, true);
            else
               symbolID = st.getSymbol(node.getNodeName(), true);
            byte signature = (byte)(Signatures.Elem << 0x6);
            int attrLen = node.getAttributes().getLength();
            int attrSize = getSizeType(attrLen);
            if ( attrLen > 0 )
               signature |= (byte)(Signatures.True << 0x5);
            if ( node.hasChildNodes() )
               signature |= (byte)(Signatures.True << 0x4);
            signature |= (byte)(attrSize);
            byte[] children = buildChildren(node);
            int valLen = children.length;
            int sizeType = getSizeType(valLen+11);
            signature |= (byte)(sizeType << 0x2);
            writeByte(signature);
            valLen += (getSizeSize(sizeType) + 3);
            writeSize(sizeType, valLen);
            writeShort(symbolID);
            write(children);
            break;
         }

         case Node.ATTRIBUTE_NODE: {
            Attr attr = (Attr)node;
            String elem = attr.getOwnerElement().getNodeName();
            String nsURI = attr.getNamespaceURI();
            short symbolID;
            if ( nsURI != null )
               symbolID = st.getSymbol(attr.getName(), nsURI, true);
            else
               symbolID = st.getSymbol(attr.getName(), true);
            String value = attr.getValue();
            writeShort(symbolID);
            writeShort((short)value.length());
            write(value.getBytes());
            break;
         }

         case Node.TEXT_NODE: {
            String value = node.getNodeValue();
            int valLen = value.length();
            int sizeType = getSizeType(valLen+5);
            byte signature = (byte)(Signatures.Char << 0x6);
            signature |= (byte)(sizeType << 0x2);
            writeByte(signature);
            valLen += (getSizeSize(sizeType) + 1);
            writeSize(sizeType, valLen);
            write(value.getBytes());
            break;
         }

         case Node.CDATA_SECTION_NODE: {
            String value = node.getNodeValue();
            int valLen = value.length();
            byte signature = (byte)(Signatures.Decl << 0x6);
            signature |= (byte)(Signatures.DeclCData << 0x2);
            writeByte(signature);
            valLen += 5;
            writeInt(valLen);
            write(value.getBytes());
            break;
         }

         case Node.ENTITY_REFERENCE_NODE: {
            String value = node.getNodeName();
            byte signature = (byte)(Signatures.Char << 0x6);
            signature |= (byte)(Signatures.CharEntity << 0x4);
            short symbol = 0;
            int encoding = 0;
            if ( value.equals("&amp;") )
               signature |= (byte)(Signatures.EntAmp);
            else if ( value.equals("&lt;") )
               signature |= (byte)(Signatures.EntLt);
            else if ( value.equals("&gt;") )
               signature |= (byte)(Signatures.EntGt);
            else if ( value.equals("&quot;") )
               signature |= (byte)(Signatures.EntQuot);
            else if ( value.equals("&apos;") )
               signature |= (byte)(Signatures.EntApos);
            else if ( value.startsWith("&#x") ) {
               encoding = 1;
               signature |= (byte)(Signatures.EntUnicode);
               // Convert the Unicode to a short
            }
            else {
               encoding = 2;
               symbol = st.getSymbol(value, true);
               signature |= (byte)(Signatures.EntDefined);
            }
            writeByte(signature);
            if ( encoding > 0 )
               writeShort(symbol);
            break;
         }

         case Node.ENTITY_NODE: {
            break;
         }

         case Node.PROCESSING_INSTRUCTION_NODE: {
            String value = node.getNodeName()+" "+node.getNodeValue();
            int valLen = value.length();
            byte signature = (byte)(Signatures.Proc << 0x6);
            writeByte(signature);
            valLen += 5;
            writeInt(valLen);
            write(value.getBytes());
            break;
         }

         case Node.COMMENT_NODE: {
            String value = node.getNodeValue();
            int valLen = value.length();
            byte signature = (byte)(Signatures.Decl << 0x6);
            signature |= (byte)(Signatures.DeclComment << 0x2);
            writeByte(signature);
            valLen += 5;
            writeInt(valLen);
            write(value.getBytes());
            break;
         }

         case Node.DOCUMENT_NODE: {
            byte[] children = buildChildren(node);
            writeInt(children.length);
            write(children);
            break;
         }

         case Node.DOCUMENT_TYPE_NODE: {
            // Errr?
            break;
         }

         case Node.DOCUMENT_FRAGMENT_NODE: {
            byte[] children = buildChildren(node);
            writeInt(children.length);
            write(children);
            break;
         }

         case Node.NOTATION_NODE: {
            byte signature = (byte)(Signatures.Decl << 0x6);
            break;
         }

      }
      flush();
   }

   /**
    * buildChildren builds a byte array containing the child definitions
    * for the specified Node.  Because dbXML's compressed system is a
    * depth-first system that requires the length of nested data sets in
    * order to allow quick traversal, this process must build the byte
    * arrays in memory before including them in their parents.
    * <br>
    * If a Node has already been graphed and has not been modified, it's
    * byte array portion is simply copied into the stream without
    * requiring a regraph. This allows for quickly streaming out a modified
    * DOM tree, but will  take some time in streaming a brand new DOM tree.
    *
    * @param node The Node whose children to build
    * @return The child byte array
    * @throws IOException If the build failed
    */
   private byte[] buildChildren(Node node) throws IOException {
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      BufferedOutputStream buf = new BufferedOutputStream(bos, 4096);
      DOMCompressor out = new DOMCompressor(buf, st);
      if ( node.getNodeType() == Node.ELEMENT_NODE ) {
         NamedNodeMap attrs = node.getAttributes();
         int len = attrs.getLength();
         int sizeType = getSizeType(len);
         out.writeSize(sizeType, len);
         for ( int i = 0; i < len; i++)
            out.writeNode(attrs.item(i));
      }
      NodeList children = node.getChildNodes();
      int len = children.getLength();
      for ( int i = 0; i < len; i++ )
         out.writeNode(children.item(i));
      out.flush();
      return bos.toByteArray();
   }

   /**
    * Compress is a convenience method that compresses a Node into a byte
    * array with a single call.
    *
    * @param node The Node to compress
    * @param symbols The Symbol Table to use
    * @return The resulting byte array
    * @throws dbXMLException if an Exception occurs
    */
   public static byte[] Compress(Node node, SymbolTable symbols) throws dbXMLException {
      try {
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
         BufferedOutputStream buf = new BufferedOutputStream(bos, 4096);
         DOMCompressor xco = new DOMCompressor(buf, symbols);
         node.normalize();
         xco.writeNode(node);
         xco.flush();
         return bos.toByteArray();
      }
      catch ( Exception e ) {
         throw new dbXMLException("XML Compression Error", e);
      }
   }
}


