package org.dbxml.core;

/*
 * 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: Collection.java,v 1.168 2001/09/04 11:13:32 kstaken Exp $
 */

import org.dbxml.xml.*;
import org.dbxml.xml.dom.*;
import org.dbxml.core.objects.*;
import org.dbxml.core.data.*;
import org.dbxml.core.filer.*;
import org.dbxml.core.indexer.*;
import org.dbxml.core.query.*;
import org.dbxml.server.*;
import org.dbxml.server.util.*;

import org.w3c.dom.*;

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

/**
 * Collection represents a collection of Documents maintains links to the
 * Filer storage implementation, the Indexes, and any XMLObjects that may
 * be associated with the Collection.
 */

public class Collection extends CollectionManager implements Named, DBObject, Configurable {
   private static final String NAME = "name";
   private static final String FILER = "filer";
   private static final String CLASS = "class";
   private static final String LOCATION = "location";
   private static final String INDEXES = "indexes";
   private static final String COMPRESSED = "compressed";
   private static final String CACHE = "cache";
   private static final String XMLOBJECTS = "xmlobjects";
   private static final String DOCUMENTS = "documents";
   private static final String SYMBOLS = "symbols";
   private static final String CLASSNAME = "dbxml-class";
   
   private static final String[] EmptyStrings = new String[0];
   
   private static int host_id;
   static {
      try {
         InetAddress a = InetAddress.getLocalHost();
         byte[] b = a.getAddress();
         host_id = 0;
         host_id += b[0];
         host_id += (b[1] << 8);
         host_id += (b[2] << 16);
         host_id += (b[3] << 24);
         host_id = Math.abs(host_id);
      }
      catch ( Exception e ) {
      }
   };

   private Collection parent = null;

   // Object ID Stuff
   private int collection_id = 0;
   private long document_id = System.currentTimeMillis();
   private Object oidMutex = new Object();
   private String oidTemplate = null;
 
   protected String name;
   private String canonicalName;
   private File collectionRoot;
   
   private Filer filer = null;

   private boolean compressed = false;
   private SymbolTable symbols = null;
   private boolean internalSymbols = false;

   private IndexManager indexManager;
   private XMLObjectManager xmlObjects;
   private DocumentCache documentCache;

   protected Collection() {
   }

   public Collection(Collection collection) {
      this();
      parent = collection;
   }

   public void setConfig(Configuration config) throws dbXMLException {
      this.config = config;

      name = config.getAttribute(NAME);
      compressed = config.getBooleanAttribute(COMPRESSED, true);

      if ( parent != null ) {
         setCanonicalName(parent.getCanonicalName()+'/'+name);
         setCollectionRoot(new File(parent.getCollectionRoot(), name));
      }

      if ( config.getBooleanAttribute(CACHE, true) )
         documentCache = getDatabase().getDocumentCache();

      // If no Filer is defined, skip Symbols and Indexes
      Configuration filerConfig = config.getChild(FILER);
      if ( filerConfig != null ) {
         
         // Symbol Table Setup
         Configuration symConfig = config.getChild(SYMBOLS);
         internalSymbols = (symConfig != null);
         if ( internalSymbols ) {
            try {
               symbols = new SymbolTable(symConfig.getElement());
            }
            catch ( Exception e ) {
               org.dbxml.Debug.printStackTrace(e);
            }
         }
         else {
            try {
               symbols = getSystemCollection().loadSymbols(this);
            }
            catch ( Exception e ) {
               org.dbxml.Debug.printStackTrace(e);
            }
         }
         
         String className = filerConfig.getAttribute(CLASS);
         try {
            filer = (Filer)Class.forName(className).newInstance();
            filer.setCollection(this);
            filer.setConfig(filerConfig);
            if ( !filer.exists() )
               filer.create();
            filer.open();
         }
         catch ( Exception e ) {
            org.dbxml.Debug.println("Filer '"+className+"' not available");
         }
         
         // Index Manager
         try {
            indexManager = new IndexManager(this);
            Configuration idxConfig = config.getChild(INDEXES, true);
            indexManager.setConfig(idxConfig);
         }
         catch ( Exception e ) {
            org.dbxml.Debug.printStackTrace(e);
         }
      }

      // XMLObject Manager
      xmlObjects = new XMLObjectManager(this);
      try {
         Configuration objConfig = config.getChild(XMLOBJECTS, true);
         xmlObjects.setConfig(objConfig);
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
      }
      
      super.setConfig(config);
   }

   public final String getName() {
      return name;
   }

   public final File getCollectionRoot() {
      return collectionRoot;
   }

   protected final void setCollectionRoot(File collectionRoot) {
      this.collectionRoot = collectionRoot;
      if ( !collectionRoot.exists() )
         collectionRoot.mkdirs();
   }
   
   /**
    * getParentCollection returns the parent Collection of this
    * Collection.
    *
    * @return The parent Collection (or null)
    */
   public final Collection getParentCollection() throws DBException {
      return parent;
   }

   // Convenience Methods

   /**
    * getDatabase returns the Database owner for this Collection.
    *
    * @return The Database
    */
   public Database getDatabase() {
      return parent.getDatabase();
   }

   /**
    * getSystemCollection returns the System Collection.
    *
    * @return The System Collection
    */
   public SystemCollection getSystemCollection() throws DBException {
      return getDatabase().getSystemCollection();
   }

   /**
    * getQueryEngine returns the Database's Query Engine
    *
    * @return The Query Engine
    */
   public QueryEngine getQueryEngine() throws DBException {
      return getDatabase().getQueryEngine();
   }
   
   private void checkFiler(int faultCode) throws DBException {
      if ( filer == null )
         throw new DBException(faultCode, "This Collection '"+name+"' cannot store Documents");
   }

   /**
    * getIndexer retrieves an Indexer by name.
    *
    * @param name The Indexer name
    * @return The Indexer (or null)
    */
   public final Indexer getIndexer(String name) throws DBException {
      checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
      return indexManager.get(name);
   }

   /**
    * listIndexers returns a list of the currently registered Indexers
    * as an array of String.
    *
    * @return The Indexer list
    */
   public final String[] listIndexers() throws DBException {
      checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
      return indexManager.list();
   }

   /**
    * dropIndexer physically removes the specified Indexer and any
    * associated system resources that the Indexer uses.
    *
    * @param index The Indexer to drop
    * @return Whether or not the Indexer was dropped
    */
   public final boolean dropIndexer(Indexer index) throws DBException {
      checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
      
      if ( index == null )
         throw new DBException(FaultCodes.IDX_INDEX_NOT_FOUND, "Index Value Null");
      
      return indexManager.drop(index.getName());
   }

   /**
    * createIndexer creates a new Indexer object and any associated
    * system resources that the Indexer will need.
    *
    * @param config The Indexer's configuration
    * @return The newly created Indexer
    */
   public final Indexer createIndexer(Configuration config) throws DBException {
      checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
      return indexManager.create(config);
   }

   /**
    * getFiler returns the low-level Filer instances underlying the
    * Collection instance.
    *
    * @return The requested Filer
    */
   public final Filer getFiler() {
      return filer;
   }

   /**
    * return the IndexManager being used by this Collection.
    *
    * @return The IndexManager
    */
   public final IndexManager getIndexManager() throws DBException {
      checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
      return indexManager;
   }

   
   /**
    * getXMLObjectManager returns the Object's XMLObjectManager.
    *
    * @return The XMLObjectManager
    */
   public final XMLObjectManager getXMLObjectManager() throws DBException {
      return xmlObjects;
   }
   
   /**
    * getXMLObject retrieves an XMLObject by name.
    *
    * @param name The XMLObject's name
    * @return The XMLObject (or null)
    */
   public final XMLObject getXMLObject(String name) throws DBException {
      return xmlObjects.get(name);
   }

   /**
    * listXMLObjects returns a list of the currently registered XMLObjects
    * as an array of String.
    *
    * @return The XMLObject list
    */
   public String[] listXMLObjects() throws DBException {
      return xmlObjects.list();
   }

   /**
    * dropXMLObject physically removes the specified XMLObject and any
    * associated system resources that the XMLObject uses.
    *
    * @param xmlObject The XMLObject to drop
    * @return Whether or not the XMLObject was dropped
    */
   public final boolean dropXMLObject(XMLObject xmlObject) throws DBException {
      if ( xmlObject == null )
         throw new DBException(FaultCodes.OBJ_OBJECT_NOT_FOUND, "Object Value Null");
      
      return xmlObjects.drop(xmlObject.getName());
   }

   /**
    * createXMLObject creates a new XMLObject object and any associated
    * system resources that the XMLObject will need.
    *
    * @param config The XMLObject's configuration
    * @return The newly created XMLObject
    */
   public final XMLObject createXMLObject(Configuration config) throws DBException {
      return xmlObjects.create(config);
   }

   /**
    * getSymbols returns the SymbolTable in use by this
    * Collection.
    *
    * @return The Symbol Table
    */
   public final SymbolTable getSymbols() throws DBException {
      return symbols;
   }

   /**
    * Turns an XML string into a parsed document and caches it.
    *
    * @param key The key to use when caching
    * @param xml The string to parse
    * @return A parsed DOM document or null if failure
    */
   private Document parseDocument(Key key, String xml) throws DBException {
      Document doc = null;
      try {
         doc = DOMParser.toDocument(xml);

         // Have to move it to dbXML DOM for XMLObject AutoLinking
         byte[] b = DOMCompressor.Compress(doc, symbols);
         doc = new DocumentImpl(b, symbols, new NodeSource(this, key));

         if ( documentCache != null )
            documentCache.putDocument(this, key, b);
      }
      catch ( Exception e ) {
         throw new DBException(FaultCodes.COL_DOCUMENT_MALFORMED, "Unable to parse Document");
      }
      return doc;
   }

   /**
    * getCanonicalName returns the canonical name for this Object.
    * <br>
    * ex: /local/test/ocs
    *
    * @return The canonical name
    */
   public final String getCanonicalName() {
      return canonicalName;
   }

   protected final void setCanonicalName(String canonicalName) {
      this.canonicalName = canonicalName;
      
      // Calculate The OID Template
      collection_id = Math.abs(canonicalName.hashCode());
      StringBuffer sb = new StringBuffer("00000000000000000000000000000000");
      String host = Integer.toString(host_id, 16);
      String collection = Integer.toString(collection_id, 16);
      sb.insert(8-host.length(), host);
      sb.insert(16-collection.length(), collection);
      sb.setLength(32);
      oidTemplate = sb.toString();
   }
   
   /**
    * getCanonicalDocumentName returns the canonical name for the specified
    * Key in relation to this Collection.
    * <br>
    * ex: /local/test/ocs/ytd
    *
    * @param key The Key
    * @return The canonical name
    */
   public final String getCanonicalDocumentName(Key key) {
      StringBuffer sb = new StringBuffer();
      sb.append(canonicalName);
      sb.append('/');
      sb.append(key.toString());
      return sb.toString();
   }

   public final boolean open() throws DBException {
      return true;
   }

   public boolean isOpened() throws DBException {
      return true;
   }

   public boolean exists() throws DBException {
      return true;
   }

   public boolean close() throws DBException {
      return true;
   }

   public boolean create() throws DBException {
      return true;
   }

   public boolean drop() throws DBException {
      if ( this == getDatabase() )
         throw new DBException(FaultCodes.DBE_CANNOT_DROP, "You Cannot Drop The Database");

      // Drop Child Collections
      String[] cols = listCollections();
      for ( int i = 0; i < cols.length; i++ )
         dropCollection(getCollection(cols[i]));
      
      // Drop XMLObjects
      String[] objs = listXMLObjects();
      for ( int i = 0; i < objs.length; i++ )
         dropXMLObject(getXMLObject(objs[i]));

      if ( filer != null ) {
         // Drop Indexers
         String[] idx = indexManager.list();
         for ( int i = 0; i < idx.length; i++ )
            dropIndexer(getIndexer(idx[i]));
         
         // Now Drop The Filer
         filer.drop();
      }
      
      getCollectionRoot().delete();
      return true;
   }

   
   // All Document Handling Code Follows

   /**
    * createNewOID allocates a new Object ID to be used as a Key in the
    * Collection.
    *
    * @return The newly generated Key
    */
   public final Key createNewOID() {
      long ct = System.currentTimeMillis();

      synchronized(oidMutex) {
         if ( ct <= document_id )
            ct = document_id + 1;
         document_id = ct;
      }

      StringBuffer sb = new StringBuffer(oidTemplate);
      String document = Long.toString(document_id, 16);
      sb.insert(32-document.length(), document);
      sb.setLength(32);

      return new Key(sb.toString());
   }

   /**
    * createNewKey allocates a new Key to be used as a Key in the
    * Collection.
    *
    * @param key The Key hint
    * @return The newly generated Key
    */
   protected final Key createNewKey(Object key) {
      if ( key == null )
         return createNewOID();
      if ( key instanceof Key )
         return (Key)key;
      else
         return new Key(key.toString());
   }

   public final void flushSymbolTable() throws DBException {
      if ( symbols.isDirty() && !internalSymbols )
         getSystemCollection().saveSymbols(this, symbols);
   }
   
   private void putDocument(Key key, Document document, boolean create) throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
            
      if ( document instanceof DBDocument ) {
         // This is a shitty shitty hack... Kill immediately
         DBDocument dbDoc = (DBDocument)document;
         if ( dbDoc.getSource() == null )
            dbDoc.setSource(new NodeSource(this, key));
      }

      Value value =  null;
      if ( compressed ) {
         try {
            byte[] b = DOMCompressor.Compress(document, symbols);
            value = new Value(b);
            
            document = new DocumentImpl(b, symbols, new NodeSource(this, key));
         }
         catch ( Exception e ) {
            throw new DBException(FaultCodes.COL_CANNOT_STORE, "Error storing Document '"+key+"'");
         }
      }
      else
         value = new Value(new Variant(document).toString());

      flushSymbolTable();
      
      // Temporary until insert and update are separate
      Document oldDoc = getDocument(key);
      if ( oldDoc != null )
         indexManager.removeDocument(key, oldDoc);
      indexManager.addDocument(key, document);
            
      filer.writeRecord(key, value);
            
      // Cache Stuff
      if ( documentCache != null ) {
         if ( compressed )
            documentCache.putDocument(this, key, value.getData());
         else
            documentCache.putDocument(this, key, document);
      }
   }

   /**
    * insertDocument inserts a new Document into a dbXML Collection.
    *
    * @param document The Document
    * @return The new Object Identifier
    */
   public final Key insertDocument(Document document) throws DBException {
      Key key = createNewOID();
      putDocument(key, document, true);
      return key;
   }

   /**
    * insertDocument inserts a new Document into a dbXML Collection.
    *
    * @param docKey The document Key
    * @param value The Document
    */
   public final void insertDocument(Object docKey, Document document) throws DBException {
      putDocument(createNewKey(docKey), document, true);
   }

   /**
    * setDocument overwrites/updates an existing Document in a
    * dbXML Collection.
    *
    * @param docKey The Document Key
    * @param document The Document
    */
   public final void setDocument(Object docKey, Document document) throws DBException {
      putDocument(createNewKey(docKey), document, false);
   }

   /**
    * remove removes an object from the Collection based on its Key,
    * regardless of it's type.
    *
    * @param key The Object's Key
    */
   public final void remove(Object key) throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      
      Key objKey = createNewKey(key);

      Document oldDoc = getDocument(objKey);
      if ( oldDoc != null )
         indexManager.removeDocument(objKey, oldDoc);
      
      if ( documentCache != null )
         documentCache.removeDocument(this, objKey);

      if ( !filer.deleteRecord(objKey) )
         throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND, "Document Does Not Exist");
   }

   /**
    * getDocument retrieves a Document by Key.
    *
    * @param docKey The Document Key
    * @return The Document
    */
   public final Document getDocument(Object docKey) throws DBException {
      getDatabase().getSecurityManager().checkAccess(getCanonicalName() +
         "/", org.dbxml.core.security.Permission.READ);
      
      checkFiler(FaultCodes.COL_NO_FILER);
      
      Key key = createNewKey(docKey);

      Document doc = null;
      if ( documentCache != null )
         doc = documentCache.getDocument(this, key);
      
      if ( doc == null ) {
         Record record = filer.readRecord(key);
         if ( record == null )
            return null;

         Value value = record.getValue();

         if ( compressed ) {
            doc = new DocumentImpl(value.getData(), symbols, new NodeSource(this, key));

            if ( documentCache != null )
               documentCache.putDocument(this, key, value.getData());
         }
         else
            doc = parseDocument(key, value.toString());
         
         flushSymbolTable();
      }
      return doc;
   }

   /**
    * getContainer retrieves a Container from the Collection.  The Container
    * encapsulates all information needed in dealing with a Document outside
    * of the context of a Collection (ex: DocumentContext).
    *
    * @param docKey The Document Key
    * @return The Container
    */
   public final Container getContainer(Object docKey) throws DBException {
      Key key = createNewKey(docKey);
      Document doc = getDocument(key);
      return doc != null ? new ColContainer(key, doc)
                         : null;
   }

   /**
    * getObject instantiates and returns an XMLSerializable object based on the
    * provided Key.  dbXML takes care of instantiating the correct class, but
    * only if a class was registered with the Document in the first place.
    *
    * @param key The Document Key
    * @return an Castable XMLSerializable Instance
    */
   public final XMLSerializable getObject(Object key) throws DBException {
      String className = null;

      Document doc = getDocument(key);

      if ( doc != null ) {
         NodeList childNodes = doc.getChildNodes();
         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(CLASSNAME) ) {
               className = n.getNodeValue().trim();
               break;
            }
         }

         if ( className != null ) {
            try {
               XMLSerializable obj = (XMLSerializable)Class.forName(className).newInstance();
               obj.streamFromXML(doc.getDocumentElement());
               return obj;
            }
            catch ( Exception e ) {
               org.dbxml.Debug.printStackTrace(e);
            }
         }
      }

      return null;
   }

   private void putObject(Key key, XMLSerializable obj, boolean create) throws DBException {
      Document doc = new DocumentImpl();
      ProcessingInstruction pi = doc.createProcessingInstruction(CLASSNAME, obj.getClass().getName());
      doc.appendChild(pi);
      Element elem = obj.streamToXML(doc);
      doc.appendChild(elem);
      putDocument(key, doc, create);
   }

   /**
    * setObject sets an XMLSerializable object in the Collection based on the
    * provided Key.  dbXML takes care of associating the implementation class
    * with the XMLSerializable object.
    *
    * @param key The Key to use
    * @param obj The Object to set
    */
   public final void setObject(Object key, XMLSerializable obj) throws DBException {
      putObject(createNewKey(key), obj, false);
   }

   /**
    * insertObject inserts an XMLSerializable object into the Collection and
    * returns a newly generated Key.  dbXML takes care of associating the
    * implementation class with the XMLSerializable object.
    *
    * @param obj The Object to insert
    * @return The newly generated Key
    */
   public final Key insertObject(XMLSerializable obj) throws DBException {
      Key key = createNewOID();
      putObject(key, obj, true);
      return key;
   }

   /**
    * insertObject inserts an XMLSerializable object into the Collection based
    * on the specified Key.  dbXML takes care of associating the implementation
    * class with the XMLSerializable object.
    *
    * @param key The Key to use
    * @param obj The Object to insert
    */
   public final void insertObject(String key, XMLSerializable obj) throws DBException {
      putObject(createNewKey(key), obj, true);
   }

   /**
    * queryCollection performs a query against the current collection
    * using the specified style and query String.
    *
    * @param style The query style to use (ex: XPath)
    * @param query The query to execute
    * @param nsMap The namespace Map (if any)
    * @return The resulting NodeSet
    */
   public final NodeSet queryCollection(String style, String query, NamespaceMap nsMap) throws DBException {
      checkFiler(FaultCodes.QRY_STYLE_NOT_FOUND);
      return getQueryEngine().query(this, style, query, nsMap, null);
   }

   /**
    * queryDocument performs a query against a single Document using
    * the specified style, query string, and Document ID.
    *
    * @param style The query style to use (ex: XPath)
    * @param query The query to execute
    * @param nsMap The namespace Map (if any)
    * @param key The Document to query
    * @return The resulting NodeSet
    */
   public final NodeSet queryDocument(String style, String query, NamespaceMap nsMap, Object key) throws DBException {
      checkFiler(FaultCodes.QRY_STYLE_NOT_FOUND);
      Key[] k = null;
      if ( key instanceof Key[] )
         k = (Key[])key;
      else
         k = new Key[] { createNewKey(key) };
      return getQueryEngine().query(this, style, query, nsMap, k);
   }

   /**
    * getDocumentSet returns the set of Documents being maintained
    * by this Collection.
    *
    * @return The DocumentSet
    */
   public final DocumentSet getDocumentSet() throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      
      return new ColDocumentSet(filer.getRecordSet());
   }
   
   /**
    * listDocuments returns a list of all document keys stored by this
    * collection.
    *
    * @return the list of document keys
    */
   public final String[] listDocuments() throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      
      RecordSet set = filer.getRecordSet();
      ArrayList temp = new ArrayList();
      
      while (set.hasMoreRecords()) {
         Key key = set.getNextKey();
         temp.add(key.toString());
      }
      
      return (String[]) temp.toArray(new String[0]);
   }
   
   /**
    * getDocumentCount returns the count of Documents being maintained
    * by this Collection.
    *
    * @return The Document count
    */
   public final long getDocumentCount() throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      
      return filer.getRecordCount();
   }

   /**
    * ColDocumentSet
    */
   private class ColDocumentSet implements DocumentSet {
      private RecordSet set;
      
      public ColDocumentSet(RecordSet set) {
         this.set = set;
      }
      
      public boolean hasMoreDocuments() throws DBException {
         return set.hasMoreRecords();
      }

      public Container getNextContainer() throws DBException {
         if (set.hasMoreRecords()) {
            Record rec = set.getNextRecord();
            Key key = rec.getKey();
            Value val = rec.getValue();
            if ( val.getData() != null ) {
               try {
                  if ( compressed ) {
                     Document doc = new DocumentImpl(val.getData(), symbols, new NodeSource(Collection.this, key));
                     return new ColContainer(key, doc);
                  }
                  else
                     return new ColContainer(key, DOMParser.toDocument(val));
               }
               catch ( Exception e ) {
                  org.dbxml.Debug.printStackTrace(e);
               }
            }
         }
         return null;
      }
      
      public Document getNextDocument() throws DBException {
         Container c = getNextContainer();
         if ( c != null )
            return c.getDocument();
         else
            return null;
      }
   }

   
   /**
    * ColContainer
    */
   private class ColContainer implements Container {
      private Key key;
      private Document document;

      public ColContainer(Key key, Document document) {
         this.key = key;
         this.document = document;
      }

      public Collection getCollection() {
         return Collection.this;
      }

      public Key getKey() {
         return key;
      }

      public String getCanonicalName() throws DBException {
         return getCanonicalDocumentName(key);
      }

      public Document getDocument() {
         return document;
      }

      public Document rollback() throws DBException {
         document = Collection.this.getDocument(key);
         return document;
      }

      public void commit(Document doc) throws DBException {
         document = doc;
         commit();
      }

      public void commit() throws DBException {
         putDocument(key, document, false);
      }

      public void remove() throws DBException {
         Collection.this.remove(key);
      }
   }
}
