package com.dbxml.db.core;

/*
 * 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: Collection.java,v 1.6 2004/07/30 14:04:29 bradford Exp $
 */

import com.dbxml.db.core.data.*;

import com.dbxml.db.core.extension.ExtensionManager;
import com.dbxml.db.core.filer.Filer;
import com.dbxml.db.core.indexer.IndexManager;
import com.dbxml.db.core.query.QueryEngine;
import com.dbxml.db.core.query.ResultSet;
import com.dbxml.db.core.security.Access;
import com.dbxml.db.core.security.SecurityException;
import com.dbxml.db.core.transaction.Transaction;
import com.dbxml.db.core.trigger.CancelOperationException;
import com.dbxml.db.core.trigger.TriggerManager;
import com.dbxml.db.server.labrador.CollectionProxy;
import com.dbxml.labrador.ID;
import com.dbxml.labrador.objects.ObjectResolver;
import com.dbxml.util.Configurable;
import com.dbxml.util.Configuration;
import com.dbxml.util.dbXMLException;
import com.dbxml.xml.NamespaceMap;
import com.dbxml.xml.SymbolTable;
import com.dbxml.xml.dtsm.CompressedTable;
import com.dbxml.xml.dtsm.DTSMException;
import com.dbxml.xml.dtsm.DTSMHelper;
import com.dbxml.xml.dtsm.DocumentTable;
import java.io.File;
import java.net.InetAddress;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

/**
 * Collection represents a collection of Documents. It maintains
 * links to the filer storage implementation, the indexes, and any
 * extensions or triggers that may be associated with the collection.
 * The Collection class is the focal point of the direct API. Nearly
 * all methods for accessing and managing documents, indexes,
 * triggers, and extensions can be found in this class.
 */

public class Collection extends CollectionManager implements Configurable {
   private static final Key[] EmptyKeys = new Key[0];

   private static final String NAME = "name";
   private static final String FILER = "filer";
   private static final String CLASS = "class";
   private static final String INDEXES = "indexes";
   private static final String CACHE = "cache";
   private static final String EXTENSIONS = "extensions";
   private static final String TRIGGERS = "triggers";
   private static final String SYMBOLS = "symbols";

   private static final String TYPE = "type";
   private static final String COMPRESSED = "compressed";
   private static final String TEXT = "text";
   private static final String BINARY = "binary";

   private static final int ITYPE_COMPRESSED = 0;
   private static final int ITYPE_TEXT = 1;
   private static final int ITYPE_BINARY = 2;

   public static final int TYPE_DOCUMENTS = 0;
   public static final int TYPE_RECORDS = 1;

   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;

   // Object ID Stuff
   private int collection_id;
   private long document_id = System.currentTimeMillis();
   private Object oidMutex = new Object();
   private String oidTemplate;

   protected String name;
   private String canonicalName;
   private File collectionRoot;

   private Filer filer;

   private int type = ITYPE_COMPRESSED;
   private SymbolTable symbols;
   private boolean internalSymbols;

   private IndexManager indexes;
   private ExtensionManager extensions;
   private TriggerManager triggers;
   private Cache cache;

   private CollectionProxy proxy;

   protected Collection() {
   }

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

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

      name = config.getAttribute(NAME);

      if ( parent != null ) {
         if ( parent instanceof Database )
            setCanonicalName('/' + name);
         else
            setCanonicalName(parent.getCanonicalName() + '/' + name);
         setCollectionRoot(new File(parent.getCollectionRoot(), name));
      }
      String sType = config.getAttribute(TYPE, COMPRESSED);
      if ( sType.equalsIgnoreCase(COMPRESSED) )
         type = ITYPE_COMPRESSED;
      else if ( sType.equalsIgnoreCase(TEXT) )
         type = ITYPE_TEXT;
      else if ( sType.equalsIgnoreCase(BINARY) )
         type = ITYPE_BINARY;
      else {
         System.err.println("Invalid Type '" + sType + "' - Falling back to " + COMPRESSED);
         type = ITYPE_COMPRESSED;
      }

      // If no Filer is defined, skip Symbols and Indexes
      Configuration filerConfig = config.getChild(FILER);
      if ( filerConfig != null ) {
         if ( config.getBooleanAttribute(CACHE, true) )
            cache = getDatabase().getCache();

         // Symbol Table Setup
         Configuration symConfig = config.getChild(SYMBOLS);
         internalSymbols = (symConfig != null);
         if ( internalSymbols ) {
            try {
               symbols = new SymbolTable(symConfig.getElement());
            }
            catch ( Exception e ) {
               e.printStackTrace(System.err);
            }
         }
         else {
            try {
               symbols = getSystemCollection().loadSymbols(this);
            }
            catch ( Exception e ) {
               e.printStackTrace(System.err);
            }
         }

         String className = filerConfig.getAttribute(CLASS);
         try {
            filer = (Filer)ClassResolver.get(className).newInstance();
            filer.setCollection(this);
            filer.setConfig(filerConfig);
            if ( !filer.exists() )
               filer.create();
            filer.open();
         }
         catch ( Exception e ) {
            System.err.println("Filer '" + className + "' not available");
         }

         // Index Manager
         try {
            indexes = new IndexManager(this);
            Configuration idxConfig = config.getChild(INDEXES, true);
            indexes.setConfig(idxConfig);
         }
         catch ( Exception e ) {
            e.printStackTrace(System.err);
         }

         // Trigger Manager
         triggers = new TriggerManager(this);
         try {
            Configuration triggerConfig = config.getChild(TRIGGERS, true);
            triggers.setConfig(triggerConfig);
         }
         catch ( Exception e ) {
            e.printStackTrace(System.err);
         }
      }

      // Extension Manager
      extensions = new ExtensionManager(this);
      try {
         Configuration objConfig = config.getChild(EXTENSIONS, true);
         extensions.setConfig(objConfig);
      }
      catch ( Exception e ) {
         e.printStackTrace(System.err);
      }

      proxy = new CollectionProxy(this);
      ObjectResolver.register(new ID(getCanonicalName()), proxy);

      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
    * @throws DBException If a Database Exception occurs
    */
   public final Collection getParentCollection() throws DBException {
      if ( parent != null )
         return parent;
      else
         throw new DBException(FaultCodes.COL_COLLECTION_NOT_FOUND, "Collection '"+getName()+"' has no parent");
   }

   public final boolean dropCollection(Collection collection) throws DBException {
      boolean success = super.dropCollection(collection);
      getDatabase().flushConfig();
      return success;
   }

   public final Collection createCollection(String path, Configuration config) throws DBException {
      Collection col = super.createCollection(path, config);
      getDatabase().flushConfig();
      return col;
   }

   // Convenience Methods

   public Database getDatabase() {
      return parent.getDatabase();
   }

   public SystemCollection getSystemCollection() throws DBException {
      return getDatabase().getSystemCollection();
   }

   /**
    * getQueryEngine returns the Database's Query Engine
    *
    * @return The Query Engine
    * @throws DBException If a Database Exception occurs
    */
   public QueryEngine getQueryEngine() throws DBException {
      return getDatabase().getQueryEngine();
   }

   private void checkAccess(int permissions) throws SecurityException {
      getDatabase().getSecurityManager().access(getCanonicalName(), permissions);
   }

   private void checkFiler(int faultCode) throws DBException {
      if ( filer == null )
         throw new DBException(faultCode, "This Collection '" + name + "' cannot store Documents");
   }

   private void checkType(int faultCode, boolean docs) throws DBException {
      if ( docs && type == ITYPE_BINARY ) {
         String desc;
         if ( docs )
            desc = "Documents";
         else
            desc = "Binary Records";

         throw new DBException(faultCode, "This Collection '" + name + "' cannot store " + desc);
      }
   }

   /**
    * getCollectionType returns the Collection type.  This value can either
    * be TYPE_VALUES or TYPE_DOCUMENTS.
    *
    * @return The Collection type
    */
   public int getCollectionType() {
      if ( type == Collection.ITYPE_BINARY )
         return TYPE_RECORDS;
      else
         return TYPE_DOCUMENTS;
   }

   /**
    * return the IndexManager being used by this Collection.
    *
    * @return The IndexManager
    * @throws DBException If a Database Exception occurs
    */
   public final IndexManager getIndexManager() throws DBException {
      checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
      return indexes;
   }

   /**
    * getExtensionManager returns the Collection's ExtensionManager.
    *
    * @return The ExtensionManager
    * @throws DBException If a Database Exception occurs
    */
   public final ExtensionManager getExtensionManager() throws DBException {
      return extensions;
   }

   /**
    * getTriggerManager returns the Collection's TriggerManager.
    *
    * @return The TriggerManager
    * @throws DBException If a Database Exception occurs
    */
   public final TriggerManager getTriggerManager() throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      return triggers;
   }

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

   public final String getCanonicalName() {
      return canonicalName;
   }

   protected final void setCanonicalName(String canonicalName) {
      this.canonicalName = canonicalName;
   }

   /**
    * 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 boolean drop() throws DBException {
      if ( this == getDatabase() )
         throw new DBException(FaultCodes.DBE_CANNOT_DROP, "You Cannot Drop The Database");

      if ( proxy != null ) {
         ObjectResolver.unregister(new ID(getCanonicalName()));
         proxy = null;
      }

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

      // Drop Extensions
      String[] objs = extensions.list();
      for ( int i = 0; i < objs.length; i++ )
         extensions.drop(objs[i]);

      if ( indexes != null ) {
         // Drop Indexers
         String[] idx = indexes.list();
         for ( int i = 0; i < idx.length; i++ )
            indexes.drop(idx[i]);
      }

      if ( filer != null )
         filer.drop();

      getCollectionRoot().delete();
      getDatabase().flushConfig();

      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() {
      synchronized ( oidMutex ) {
         if ( oidTemplate == null ) {
            // 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();
         }

         long ct = System.currentTimeMillis();
         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());
   }

   /**
    * createKey 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(Transaction tx, Key key, DocumentTable document) throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      checkType(FaultCodes.COL_CANNOT_STORE, true);
      DocumentTable oldDoc = getDocument(tx, key, false);

      try {
         if ( oldDoc != null )
            document = (DocumentTable)triggers.fireBeforeUpdate(tx, key, oldDoc, document);
         else
            document = (DocumentTable)triggers.fireBeforeInsert(tx, key, document);
      }
      catch ( CancelOperationException e ) {
         return;
      }

      Value value = null;
      if ( type == ITYPE_COMPRESSED ) {
         try {
            // If the inserted Document doesn't use this Collection's
            // SymbolTable, then it will have to be copied into a
            // conformant TableBuilder.  This is slower than doing it
            // correctly in the first place, but it isn't too bad.
            CompressedTable ct;
            if ( document.getSymbolTable() != symbols )
               ct = DTSMHelper.compressTable(DTSMHelper.copyTable(document, symbols));
            else
               ct = DTSMHelper.compressTable(document);

            value = new Value(ct.getData());
            document = ct;
         }
         catch ( Exception e ) {
            throw new DBException(FaultCodes.COL_CANNOT_STORE, "Error storing Document '" + key + "'", e);
         }
      }
      else {
         try {
            value = new Value(DTSMHelper.tableToText(document));
         }
         catch ( DTSMException e ) {
            throw new DBException(FaultCodes.COL_CANNOT_STORE, "Error storing Document '" + key + "'", e);
         }
      }

      flushSymbolTable();

      if ( oldDoc != null )
         indexes.removeDocument(tx, key, oldDoc);
      indexes.addDocument(tx, key, document);

      filer.writeRecord(tx, key, value);

      // Cache Stuff
      if ( cache != null )
         cache.put(this, key, document);

      if ( oldDoc != null )
         triggers.fireAfterUpdate(tx, key, oldDoc, document);
      else
         triggers.fireAfterInsert(tx, key, document);
   }

   /**
    * insertDocument inserts a new Document into a dbXML Collection.
    *
    * @param tx The controlling Transaction
    * @param document The Document
    * @return The new Object Identifier
    * @throws DBException If a Database Exception occurs
    */
   public final Key insertDocument(Transaction tx, DocumentTable document) throws DBException {
      checkAccess(Access.WRITE);
      Key key = createNewOID();
      putDocument(tx, key, document);
      return key;
   }

   /**
    * setDocument overwrites/updates an existing Document in a
    * dbXML Collection.
    *
    * @param tx The controlling Transaction
    * @param docKey The Document Key
    * @param document The Document
    * @throws DBException If a Database Exception occurs
    */
   public final void setDocument(Transaction tx, Object docKey, DocumentTable document) throws DBException {
      checkAccess(Access.WRITE);
      putDocument(tx, createNewKey(docKey), document);
   }

   /**
    * remove removes an object from the Collection based on its Key,
    * regardless of its type.
    *
    * @param tx The controlling Transaction
    * @param key The Object's Key
    * @throws DBException If a Database Exception occurs
    */
   public final void remove(Transaction tx, Object key) throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      checkAccess(Access.WRITE);

      Key objKey = createNewKey(key);

      Object oldObj = null;
      if ( type == ITYPE_BINARY ) {
         Record oldRec = getRecord(tx, objKey, false);
         if ( oldRec != null )
            oldObj = oldRec.getValue().getData();
      }
      else
         oldObj = getDocument(tx, objKey, false);

      if ( oldObj == null )
         throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND, "Object Does Not Exist");

      try {
         triggers.fireBeforeDelete(tx, objKey, oldObj);
      }
      catch ( CancelOperationException e ) {
         return;
      }

      if ( type != ITYPE_BINARY )
         indexes.removeDocument(tx, objKey, (DocumentTable)oldObj);

      if ( cache != null )
         cache.remove(this, objKey);

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

      triggers.fireAfterDelete(tx, objKey, oldObj);
   }

   private final DocumentTable getDocument(Transaction tx, Object docKey, boolean useTriggers) throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      checkType(FaultCodes.COL_CANNOT_RETRIEVE, true);

      Key key = createNewKey(docKey);

      if ( useTriggers ) {
         try {
            triggers.fireBeforeGet(tx, key);
         }
         catch ( CancelOperationException e ) {
            return null;
         }
      }

      DocumentTable doc = null;
      if ( cache != null )
         doc = cache.getDocument(this, key);

      if ( doc == null ) {
         Record record = filer.readRecord(tx, key);
         if ( record == null )
            return null;

         Value value = record.getValue();

         try {
            if ( type == ITYPE_COMPRESSED )
               doc = new CompressedTable(value.getData(), symbols);
            else
               doc = DTSMHelper.bytesToTable(value.getData(), symbols);
         }
         catch ( DTSMException e ) {
            throw new DBException(FaultCodes.COL_CANNOT_RETRIEVE, "Error retrieving Document '" + key + "'", e);
         }

         if ( cache != null )
            cache.put(this, key, doc);

         flushSymbolTable();
      }

      if ( useTriggers ) {
         try {
            triggers.fireAfterGet(tx, key, doc);
         }
         catch ( CancelOperationException e ) {
            return null;
         }
      }

      return doc;
   }

   /**
    * getDocument retrieves a Document by Key.
    *
    * @param tx The controlling Transaction
    * @param docKey The Document Key
    * @return The Document
    * @throws DBException If a Database Exception occurs
    */
   public final DocumentTable getDocument(Transaction tx, Object docKey) throws DBException {
      checkAccess(Access.READ);
      return getDocument(tx, docKey, true);
   }

   /**
    * getRecordMetaData returns metadata about the Record identified
    * by the provided Key.
    *
    * @param tx The controlling Transaction
    * @param key The Record's Key
    * @return The Record's metadata
    */
   public RecordMetaData getRecordMetaData(Transaction tx, Object docKey) throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      checkAccess(Access.READ);

      Key key = createNewKey(docKey);
      return filer.getRecordMetaData(tx, key);
   }

   /**
    * 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 tx The controlling Transaction
    * @param docKey The Document Key
    * @return The Container
    * @throws DBException If a Database Exception occurs
    */
   public final Container getContainer(Transaction tx, Object docKey) throws DBException {
      Key key = createNewKey(docKey);
      if ( type == ITYPE_BINARY ) {
         Record rec = getRecord(tx, key);
         if ( rec != null )
            return new ColContainer(tx, key, rec.getValue());
         else
            return null;
      }
      else {
         DocumentTable doc = getDocument(tx, key);
         if ( doc != null )
            return new ColContainer(tx, key, doc);
         else
            return null;
      }
   }

   private Record getRecord(Transaction tx, Object recKey, boolean useTriggers) throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      checkType(FaultCodes.COL_CANNOT_RETRIEVE, false);

      Key key = createNewKey(recKey);

      if ( useTriggers ) {
         try {
            triggers.fireBeforeGet(tx, key);
         }
         catch ( CancelOperationException e ) {
            return null;
         }
      }

      Record rec = null;
      if ( cache != null )
         rec = cache.getRecord(this, key);

      if ( rec == null ) {
         rec = filer.readRecord(tx, key);
         if ( rec == null )
            return null;

         if ( cache != null )
            cache.put(this, rec);
      }

      if ( useTriggers ) {
         try {
            triggers.fireAfterGet(tx, key, rec.getValue().getData());
         }
         catch ( CancelOperationException e ) {
            return null;
         }
      }

      return rec;
   }

   /**
    * getRecord retrieves a binary Record from the Collection.
    *
    * @param tx The controlling Transaction
    * @param recKey The Record's Key
    * @return The Record
    * @throws DBException If a Database Exception occurs
    */
   public final Record getRecord(Transaction tx, Object recKey) throws DBException {
      checkAccess(Access.READ);
      return getRecord(tx, recKey, true);
   }

   private void putRecord(Transaction tx, Record rec) throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      checkType(FaultCodes.COL_CANNOT_STORE, false);

      Key key = rec.getKey();
      Record oldRec = getRecord(tx, key, false);
      try {
         if ( oldRec != null )
            rec = (Record)triggers.fireBeforeUpdate(tx, key, oldRec, rec);
         else
            rec = (Record)triggers.fireBeforeInsert(tx, key, rec);
      }
      catch ( CancelOperationException e ) {
         return;
      }

      filer.writeRecord(tx, rec.getKey(), rec.getValue());
      if ( cache != null )
         cache.put(this, rec);

      if ( oldRec != null )
         triggers.fireAfterUpdate(tx, key, oldRec, rec);
      else
         triggers.fireAfterInsert(tx, key, rec);
   }

   /**
    * setValue stores a binary Record in the Collection.
    *
    * @param tx The controlling Transaction
    * @param rec The Record to store
    * @throws DBException If a Database Exception occurs
    */
   public final void setRecord(Transaction tx, Record rec) throws DBException {
      checkAccess(Access.WRITE);
      putRecord(tx, rec);
   }

   /**
    * setRecord stores a binary Record in the Collection.
    *
    * @param tx The controlling Transaction
    * @param recKey The Key to use
    * @param value The Value to store
    * @throws DBException If a Database Exception occurs
    */
   public final void setRecord(Transaction tx, Object recKey, Value value) throws DBException {
      checkAccess(Access.WRITE);
      Key key = createNewKey(recKey);
      putRecord(tx, new Record(key, value));
   }

   /**
    * insertRecord stores a binary Record in the Collection and
    * returns a newly generated Key.
    *
    * @param tx The controlling Transaction
    * @param value The Value to store
    * @return The new Object Identifier
    * @throws DBException If a Database Exception occurs
    */
   public final Key insertRecord(Transaction tx, Value value) throws DBException {
      checkAccess(Access.WRITE);
      Key key = createNewOID();
      putRecord(tx, new Record(key, value));
      return key;
   }

   /**
    * queryCollection performs a query against the current collection
    * using the specified style and query String.
    *
    * @param tx The controlling Transaction
    * @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 ResultSet
    * @throws DBException If a Database Exception occurs
    */
   public final ResultSet queryCollection(Transaction tx, String style, String query, NamespaceMap nsMap) throws DBException {
      checkFiler(FaultCodes.QRY_STYLE_NOT_FOUND);
      checkAccess(Access.READ);

      return getQueryEngine().query(tx, this, style, query, nsMap, null);
   }

   /**
    * queryDocument performs a query against a single Document using
    * the specified style, query string, and Document ID.
    *
    * @param tx The controlling Transaction
    * @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 ResultSet
    * @throws DBException If a Database Exception occurs
    */
   public final ResultSet queryDocument(Transaction tx, String style, String query, NamespaceMap nsMap, Object key) throws DBException {
      checkFiler(FaultCodes.QRY_STYLE_NOT_FOUND);
      checkAccess(Access.READ);

      Key[] k = null;
      if ( key instanceof Key[] )
         k = (Key[])key;
      else
         k = new Key[]{createNewKey(key)};
      return getQueryEngine().query(tx, this, style, query, nsMap, k);
   }

   /**
    * getContainerSet returns the set of Documents being maintained
    * by this Collection.
    *
    * @param tx The controlling Transaction
    * @return The ContainerSet
    * @throws DBException If a Database Exception occurs
    */
   public final ContainerSet getContainerSet(Transaction tx) throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      checkType(FaultCodes.COL_CANNOT_RETRIEVE, true);
      checkAccess(Access.READ);

      return new ColContainerSet(tx, listKeys(tx));
   }

   /**
    * listKeys returns a list of all object keys stored by this
    * collection.
    *
    * @param tx The controlling Transaction
    * @return the list of object keys
    * @throws DBException If a Database Exception occurs
    */
   public final Key[] listKeys(Transaction tx) throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      checkAccess(Access.READ);

      RecordSet set = filer.getRecordSet(tx);
      Set temp = new TreeSet();

      while ( set.hasMoreRecords() ) {
         Key key = set.getNextKey();
         temp.add(key);
      }

      return (Key[])temp.toArray(EmptyKeys);
   }

   /**
    * getKeyCount returns the count of objects being maintained
    * by this Collection.
    *
    * @param tx The controlling Transaction
    * @return The object count
    * @throws DBException If a Database Exception occurs
    */
   public final long getKeyCount(Transaction tx) throws DBException {
      checkFiler(FaultCodes.COL_NO_FILER);
      checkAccess(Access.READ);

      return filer.getRecordCount(tx);
   }

   /**
    * dispose closes the Collection's associated Filer and Indexes.
    * It also recursively calls dispose on the Collection's child
    * Collections.
    */
   public void dispose() {
      if ( proxy != null ) {
         ObjectResolver.unregister(new ID(getCanonicalName()));
         proxy = null;
      }

      Iterator iter = collections.values().iterator();
      while ( iter.hasNext() ) {
         Collection col = (Collection)iter.next();
         col.dispose();
      }

      if ( indexes != null )
         indexes.closeAll();

      if ( filer != null ) {
         try {
            filer.close();
         }
         catch ( DBException e ) {
            e.printStackTrace(System.err);
         }
      }
   }


   /**
    * ColContainerSet
    */

   private class ColContainerSet implements ContainerSet {
      private Transaction tx;
      private Key[] keySet;
      private int i;

      public ColContainerSet(Transaction tx, Key[] keySet) {
         this.tx = tx;
         this.keySet = keySet;
      }

      public boolean hasMoreContainers() throws DBException {
         return i < keySet.length;
      }

      public Container getNextContainer() throws DBException {
         if ( hasMoreContainers() )
            return getContainer(tx, keySet[i++]);
         else
            return null;
      }
   }


   /**
    * ColContainer
    */

   private class ColContainer implements Container {
      private Transaction tx;
      private Key key;
      private Object obj;

      public ColContainer(Transaction tx, Key key, DocumentTable document) {
         this.tx = tx;
         this.key = key;
         this.obj = document;
      }

      public ColContainer(Transaction tx, Key key, Value value) {
         this.tx = tx;
         this.key = key;
         this.obj = value;
      }

      public int getContainerType() {
         if ( type == ITYPE_BINARY )
            return TYPE_VALUE;
         else
            return TYPE_DOCUMENT;
      }

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

      public Key getKey() {
         return key;
      }

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

      public DocumentTable getDocument() throws DBException {
         checkType(FaultCodes.COL_CANNOT_RETRIEVE, true);
         return (DocumentTable)obj;
      }

      public Value getValue() throws DBException {
         checkType(FaultCodes.COL_CANNOT_RETRIEVE, false);
         return (Value)obj;
      }

      public void reset() throws DBException {
         if ( type == ITYPE_BINARY )
            obj = getRecord(tx, key).getValue();
         else
            obj = Collection.this.getDocument(tx, key);
      }

      public void setDocument(DocumentTable doc) throws DBException {
         checkType(FaultCodes.COL_CANNOT_STORE, true);
         Collection.this.setDocument(tx, key, doc);
         obj = doc;
      }

      public void setValue(Value value) throws DBException {
         checkType(FaultCodes.COL_CANNOT_STORE, false);
         setRecord(tx, key, value);
         obj = value;
      }

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






