package com.dbxml.xml.dtsm;

/*
 * dbXML - Native XML Database
 * Copyright (C) 1999-2004  The dbXML Group, L.L.C.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: TableBuilder.java,v 1.3 2004/02/12 00:17:58 bradford Exp $
 */

import com.dbxml.db.core.data.Value;
import com.dbxml.xml.QName;
import com.dbxml.xml.SymbolTable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * TableBuilder
 */

public final class TableBuilder {
   private BuiltDocumentTable table = new BuiltDocumentTable();
   private SymbolTable symbols;
   private List entries = new ArrayList();
   private List values = new ArrayList();
   private Map map = new HashMap();
   private int nextValID;
   private int dataSize;

   public TableBuilder() {
   }

   public TableBuilder(SymbolTable symbols) {
      this.symbols = symbols;
   }

   /**
    * getSymbolTable returns the SymbolTable that is associated
    * with this TableBuilder.
    *
    * @return the SymbolTable
    */
   public SymbolTable getSymbolTable() {
      return symbols;
   }

   /**
    * setSymbolTable sets the SymbolTable that will be associated
    * with this TableBuilder.
    *
    * @param symbols the SymbolTable
    */
   public void setSymbolTable(SymbolTable symbols) {
      this.symbols = symbols;
   }

   /**
    * addEntry adds an entry to the table with no associated
    * Symbol ID or Value ID.
    *
    * @see com.dbxml.xml.dtsm.Constants
    *
    * @param typeID The Type ID
    */
   public void addEntry(byte typeID) {
      entries.add(new DocumentTableEntry(typeID, Constants.NO_SYMBOL_ID));
   }

   /**
    * addEntry adds an entry to the DocumentTable and returns
    * the Symbol ID that the qname maps to.  The Symbol ID
    * can be used to generate end entries more rapidly.
    *
    * @see com.dbxml.xml.dtsm.Constants
    *
    * @param typeID The Type ID
    * @param qname The QName
    * @return The Symbol ID
    */
   public int addEntry(byte typeID, QName qname) {
      int id = symbols.getSymbol(qname, true);
      entries.add(new DocumentTableEntry(typeID, id));
      return id;
   }

   /**
    * addEntry adds an entry to the DocumentTable.
    *
    * @see com.dbxml.xml.dtsm.Constants
    *
    * @param typeID The Type ID
    * @return symbolID The Symbol ID
    */
   public void addEntry(byte typeID, int symbolID) {
      entries.add(new DocumentTableEntry(typeID, symbolID));
   }

   /**
    * addEntry adds an entry to the DocumentTable and
    * return the Value ID that the data produces.
    *
    * @see com.dbxml.xml.dtsm.Constants
    *
    * @param typeID The Type ID
    * @param data The data to store
    */
   public void addEntry(byte typeID, byte[] data) {
      ValueWrapper w = new ValueWrapper(data);
      ValueWrapper c = (ValueWrapper)map.get(w);
      if ( c == null ) {
         c = w;
         c.valID = nextValID++;
         map.put(c, c);

         Value v = new Value(c.data);
         ValueTableEntry e = new ValueTableEntry(dataSize, c.data.length);
         values.add(new ValueInfo(v, e));
         dataSize += c.data.length;
      }
      entries.add(new DocumentTableEntry(typeID, c.valID));
   }

   /**
    * getDataSize returns the total number of bytes that are being
    * stored as unique Values.
    *
    * @return the total data size
    */
   public int getDataSize() {
      return dataSize;
   }

   /**
    * buildDocumentTable instructs the TableBuilder to take all of
    * the information it has accumulated, and to produce a
    * DocumentTable.
    *
    * @return the DocumentTable
    */
   public DocumentTable buildDocumentTable() {
      return table;
   }


   /**
    * BuiltDocumentTable
    */

   private class BuiltDocumentTable implements DocumentTable {
      public SymbolTable getSymbolTable() {
         return symbols;
      }

      public int getEntryCount() {
         return entries.size();
      }

      public DocumentTableEntry getEntry(int idx) {
         return (DocumentTableEntry)entries.get(idx);
      }

      public int getValueCount() {
         return values.size();
      }

      public ValueTableEntry getValueEntry(int idx) {
         return ((ValueInfo)values.get(idx)).entry;
      }

      public Value getValue(int idx) {
         return ((ValueInfo)values.get(idx)).value;
      }
   }


   /**
    * ValueWrapper
    */

   private class ValueWrapper {
      public byte[] data;
      public int valID;
      public int hash;

      public ValueWrapper(byte[] data) {
         this.data = data;
         calculateHash();
      }

      public ValueWrapper(byte[] data, int valID) {
         this.data = data;
         this.valID = valID;
         calculateHash();
      }

      private void calculateHash() {
         hash = 0;
         for ( int i = 0; i < data.length; i++ ) {
            hash = (hash << 5) ^ data[i];
            hash = hash % 1234567891;
         }
         hash = Math.abs(hash);
      }

      public int hashCode() {
         return hash;
      }

      public boolean equals(Object o) {
         ValueWrapper w = (ValueWrapper)o;
         if ( hash != w.hash || data.length != w.data.length )
            return false;
         for ( int i = 0; i < data.length; i++ )
            if ( data[i] != w.data[i] )
               return false;
         return true;
      }
   }


   /**
    * ValueInfo
    */

   private class ValueInfo {
      public Value value;
      public ValueTableEntry entry;

      public ValueInfo(Value value, ValueTableEntry entry) {
         this.value = value;
         this.entry = entry;
      }
   }
}
