package org.dbxml.core.filer;

/*
 * 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: BTreeFiler.java,v 1.19 2001/07/22 21:35:44 tom Exp $
 */

import org.dbxml.server.util.*;
import org.dbxml.server.*;
import org.dbxml.core.*;
import org.dbxml.core.Collection;
import org.dbxml.core.data.*;

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

/**
 * BTreeFiler is a Filer implementation based on the BTree class.
 */

public final class BTreeFiler extends BTree implements Configurable, Filer {
   protected static final byte RECORD = 20;

   private static final String PAGESIZE = "pagesize";
   private static final String MAXKEYSIZE = "maxkeysize";
   private static final String MAXKEYLOCKS = "maxkeylocks";

   private Collection collection;
   private Configuration config;
   private LockManager keyLocks;
   private BTreeFilerHeader fileHeader;

   public BTreeFiler() {
      super();
      fileHeader = (BTreeFilerHeader)getFileHeader();
   }

   public void setConfig(Configuration config) {
      this.config = config;
      keyLocks = new LockManager(config.getIntAttribute(MAXKEYLOCKS, 16));
   }

   public Configuration getConfig() {
      return config;
   }

   public void setLocation(String location) {
      setFile(new File(collection.getCollectionRoot(), location+".tbl"));
   }

   public String getName() {
      return "BTreeFiler";
   }

   public boolean open() throws DBException {
      if ( super.open() ) {
         // These are the only properties that can be changed after creation
         fileHeader.setMaxKeySize(config.getShortAttribute(MAXKEYSIZE, fileHeader.getMaxKeySize()));
         return true;
      }
      else
         return false;
   }

   public boolean create() throws DBException {
      fileHeader.setPageSize(config.getIntAttribute(PAGESIZE, fileHeader.getPageSize()));
      fileHeader.setMaxKeySize(config.getShortAttribute(MAXKEYSIZE, fileHeader.getMaxKeySize()));
      return super.create();
   }

   public void setCollection(Collection collection) {
      this.collection = collection;
      setLocation(collection.getName());
   }

   public Record readRecord(Key key) throws DBException {
      checkOpened();
      keyLocks.acquireSharedLock(key);
      try {
         long pos = findValue(key);
         Page startPage = getPage(pos);
         Value v = readValue(startPage);
         BTreeFilerPageHeader sph = (BTreeFilerPageHeader)startPage.getPageHeader();
         
         HashMap meta = new HashMap(2);
         meta.put(Record.CREATED, new Long(sph.getCreated()));
         meta.put(Record.MODIFIED, new Long(sph.getModified()));
         
         return new Record(key, v, meta);
      }
      catch ( BTreeNotFoundException b ) {
      }
      catch ( BTreeException b ) {
         throw b;
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
      }
      finally {
         keyLocks.releaseSharedLock(key);
      }
      return null;
   }

   public boolean writeRecord(Key key, Value value) throws DBException {
      checkOpened();
      keyLocks.acquireExclusiveLock(key);
      try {
         Page p;
         try {
            long pos = findValue(key);
            p = getPage(pos);
         }
         catch ( BTreeNotFoundException b ) {
            p = getFreePage();
            addValue(key, p.getPageNum());
            fileHeader.incRecordCount();
         }
         BTreeFilerPageHeader ph = (BTreeFilerPageHeader)p.getPageHeader();

         long t = System.currentTimeMillis();
         if ( ph.getStatus() == UNUSED )
            ph.setCreated(t);
         
         ph.setModified(t);
         ph.setStatus(RECORD);

         writeValue(p, value);
         fileHeader.write();
      }
      catch ( DBException d ) {
         throw d;
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
      }
      finally {
         keyLocks.releaseExclusiveLock(key);
      }
      return true;
   }

   public boolean deleteRecord(Key key) throws DBException {
      checkOpened();
      keyLocks.acquireExclusiveLock(key);
      try {
         long pos = findValue(key);
         Page p = getPage(pos);

         removeValue(key);
         unlinkPages(p.getPageNum());
         
         fileHeader.decRecordCount();
         fileHeader.write();

         return true;
      }
      catch ( BTreeNotFoundException b ) {
      }
      catch ( BTreeException b ) {
         throw b;
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
      }
      finally {
         keyLocks.releaseExclusiveLock(key);
      }
      return false;
   }

   public long getRecordCount() throws DBException {
      checkOpened();
      return fileHeader.getRecordCount();
   }

   public RecordSet getRecordSet() throws DBException {
      checkOpened();
      return new BTreeFilerRecordSet();
   }

   private class BTreeFilerRecordSet implements RecordSet, BTreeCallback {
      private List keys = new ArrayList();
      private Iterator enum;

      public BTreeFilerRecordSet() throws DBException {
         try {
            query(null, this);
            enum = keys.iterator();
         }
         catch ( IOException e ) {
            throw new FilerException(FaultCodes.GEN_CRITICAL_ERROR, "Error generating RecordSet");
         }
      }

      public boolean indexInfo(Value value, long pointer) {
         keys.add(new Key(value));
         return true;
      }

      public Key getNextKey() {
         return (Key)enum.next();
      }

      public Record getNextRecord() throws DBException {
         return readRecord((Key)enum.next());
      }

      public Value getNextValue() throws DBException {
         return getNextRecord().getValue();
      }

      public boolean hasMoreRecords() {
         return enum.hasNext();
      }
   }

   ////////////////////////////////////////////////////////////////////

   public FileHeader createFileHeader() {
      return new BTreeFilerHeader();
   }

   public FileHeader createFileHeader(boolean read) throws IOException {
      return new BTreeFilerHeader(read);
   }

   public FileHeader createFileHeader(long pageCount) {
      return new BTreeFilerHeader(pageCount);
   }

   public FileHeader createFileHeader(long pageCount, int pageSize) {
      return new BTreeFilerHeader(pageCount, pageSize);
   }

   public PageHeader createPageHeader() {
      return new BTreeFilerPageHeader();
   }


   /**
    * BTreeFilerHeader
    */

   private final class BTreeFilerHeader extends BTreeFileHeader {
      private long totalBytes = 0;

      public BTreeFilerHeader() {
      }

      public BTreeFilerHeader(long pageCount) {
         super(pageCount);
      }

      public BTreeFilerHeader(long pageCount, int pageSize) {
         super(pageCount, pageSize);
      }

      public BTreeFilerHeader(boolean read) throws IOException {
         super(read);
      }

      public void read(RandomAccessFile raf) throws IOException {
         super.read(raf);
         totalBytes = raf.readLong();
      }

      public void write(RandomAccessFile raf) throws IOException {
         super.write(raf);
         raf.writeLong(totalBytes);
      }

      /** The total number of bytes in use by the file */
      public void setTotalBytes(long totalBytes) {
         this.totalBytes = totalBytes;
         setDirty();
      }

      /** The total number of bytes in use by the file */
      public long getTotalBytes() {
         return totalBytes;
      }
   }

   /**
    * BTreeFilerPageHeader
    */

   private final class BTreeFilerPageHeader extends BTreePageHeader {
      private long created = 0;
      private long modified = 0;

      public BTreeFilerPageHeader() {
      }

      public BTreeFilerPageHeader(DataInputStream dis) throws IOException {
         super(dis);
      }

      public void read(DataInputStream dis) throws IOException {
         super.read(dis);

         if ( getStatus() == UNUSED )
            return;

         created = dis.readLong();
         modified = dis.readLong();
      }

      public void write(DataOutputStream dos) throws IOException {
         super.write(dos);
         dos.writeLong(created);
         dos.writeLong(modified);
      }

      public void setRecordLen(int recordLen) {
         synchronized(fileHeader) {
            fileHeader.setTotalBytes((fileHeader.totalBytes - getRecordLen()) + recordLen);
         }
         super.setRecordLen(recordLen);
      }

      /** UNIX-time when this record was created */
      public void setCreated(long created) {
         this.created = created;
         setDirty();
      }

      /** UNIX-time when this record was created */
      public long getCreated() {
         return created;
      }

      /** UNIX-time when this record was last modified */
      public void setModified(long modified) {
         this.modified = modified;
         setDirty();
      }

      /** UNIX-time when this record was last modified */
      public long getModified() {
         return modified;
      }
   }
}



