You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by el...@apache.org on 2012/02/02 13:38:42 UTC

svn commit: r1239581 [6/9] - in /directory/apacheds/trunk/jdbm2: ./ src/ src/etc/ src/examples/ src/main/ src/main/java/ src/main/java/jdbm/ src/main/java/jdbm/btree/ src/main/java/jdbm/helper/ src/main/java/jdbm/htree/ src/main/java/jdbm/recman/ src/s...

Added: directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/BaseRecordManager.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/BaseRecordManager.java?rev=1239581&view=auto
==============================================================================
--- directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/BaseRecordManager.java (added)
+++ directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/BaseRecordManager.java Thu Feb  2 12:38:39 2012
@@ -0,0 +1,769 @@
+/**
+ * JDBM LICENSE v1.00
+ *
+ * Redistribution and use of this software and associated documentation
+ * ("Software"), with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain copyright
+ *    statements and notices.  Redistributions must also contain a
+ *    copy of this document.
+ *
+ * 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 name "JDBM" must not be used to endorse or promote
+ *    products derived from this Software without prior written
+ *    permission of Cees de Groot.  For written permission,
+ *    please contact cg@cdegroot.com.
+ *
+ * 4. Products derived from this Software may not be called "JDBM"
+ *    nor may "JDBM" appear in their names without prior written
+ *    permission of Cees de Groot.
+ *
+ * 5. Due credit should be given to the JDBM Project
+ *    (http://jdbm.sourceforge.net/).
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
+ * ``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
+ * CEES DE GROOT OR ANY 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.
+ *
+ * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
+ * Copyright 2000-2001 (C) Alex Boisvert. All Rights Reserved.
+ * Contributions are Copyright (C) 2000 by their associated contributors.
+ *
+ * $Id: BaseRecordManager.java,v 1.8 2005/06/25 23:12:32 doomdark Exp $
+ */
+
+package jdbm.recman;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import jdbm.RecordManager;
+import jdbm.helper.DefaultSerializer;
+import jdbm.helper.Serializer;
+
+import org.apache.directory.server.i18n.I18n;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *  This class manages records, which are uninterpreted blobs of data. The
+ *  set of operations is simple and straightforward: you communicate with
+ *  the class using long "rowids" and byte[] data blocks. Rowids are returned
+ *  on inserts and you can stash them away someplace safe to be able to get
+ *  back to them. Data blocks can be as long as you wish, and may have
+ *  lengths different from the original when updating.
+ *  <p>
+ *  Operations are synchronized, so that only one of them will happen
+ *  concurrently even if you hammer away from multiple threads. Operations
+ *  are made atomic by keeping a transaction log which is recovered after
+ *  a crash, so the operations specified by this interface all have ACID
+ *  properties.
+ *  <p>
+ *  You identify a file by just the name. The package attaches <tt>.db</tt>
+ *  for the database file, and <tt>.lg</tt> for the transaction log. The
+ *  transaction log is synchronized regularly and then restarted, so don't
+ *  worry if you see the size going up and down.
+ *
+ * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
+ * @author <a href="cg@cdegroot.com">Cees de Groot</a>
+ */
+public final class BaseRecordManager implements RecordManager
+{
+    /** A logger for this class */
+    private static final Logger LOG = LoggerFactory.getLogger( BaseRecordManager.class.getSimpleName() );
+    
+    /** Underlying record recordFile. */
+    private RecordFile recordFile;
+
+    /** Physical row identifier manager. */
+    private PhysicalRowIdManager physMgr;
+
+    /** Logical to Physical row identifier manager. */
+    private LogicalRowIdManager logMgr;
+
+    /** Page manager. */
+    private PageManager pageMgr;
+
+    /** Reserved slot for name directory. */
+    public static final int NAME_DIRECTORY_ROOT = 0;
+
+    /** Static debugging flag */
+    public static final boolean DEBUG = false;
+
+    /**
+     * Directory of named JDBMHashtables.  This directory is a persistent
+     * directory, stored as a Hashtable.  It can be retrieved by using
+     * the NAME_DIRECTORY_ROOT.
+     */
+    private Map<String,Long> nameDirectory;
+    
+    private static enum IOType
+    {
+        READ_IO,
+        WRITE_IO
+    }
+
+    /** TODO add asserts to check internal consistency */
+    private static class LockElement
+    {
+        private int readers;
+        private int waiters;
+        private boolean writer;
+
+        private Lock lock = new ReentrantLock();
+        private Condition cv = lock.newCondition();
+
+
+        public boolean anyReaders()
+        {
+            return readers > 0;
+        }
+
+
+        public boolean anyWaiters()
+        {
+            return waiters > 0;
+        }
+
+
+        public boolean beingWritten()
+        {
+            return writer;
+        }
+
+
+        public boolean anyUser()
+        {
+            return ( readers > 0 || waiters > 0 || writer );
+        }
+
+
+        public void bumpReaders()
+        {
+            readers++;
+        }
+
+
+        public void decrementReaders()
+        {
+            readers--;
+        }
+
+
+        public void bumpWaiters()
+        {
+            waiters++;
+        }
+
+
+        public void decrementWaiters()
+        {
+            waiters--;
+        }
+
+
+        public void setWritten()
+        {
+            writer = true;
+        }
+
+
+        public void unsetWritten()
+        {
+            writer = false;
+        }
+
+
+        public Lock getLock()
+        {
+            return lock;
+        }
+
+
+        public Condition getNoConflictingIOCondition()
+        {
+            return cv;
+        }
+    }
+
+    
+    /**
+     * Map used to synchronize reads and writes on the same logical
+     * recid.
+     */
+    private final ConcurrentHashMap<Long, LockElement> lockElements;
+
+
+    /**
+     * Creates a record manager for the indicated file
+     *
+     * @throws IOException when the file cannot be opened or is not
+     *         a valid file content-wise.
+     */
+    public BaseRecordManager( String filename ) throws IOException
+    {
+        recordFile = new RecordFile( filename );
+        pageMgr = new PageManager( recordFile );
+        physMgr = new PhysicalRowIdManager( pageMgr );
+        logMgr = new LogicalRowIdManager( pageMgr );
+        lockElements = new ConcurrentHashMap<Long, LockElement>();
+    }
+
+
+    /**
+     * Get the underlying Transaction Manager
+     */
+    public TransactionManager getTransactionManager() throws IOException
+    {
+        checkIfClosed();
+        
+        return recordFile.getTxnMgr();
+    }
+
+
+    /**
+     * Switches off transactions for the record manager. This means
+     * that a) a transaction log is not kept, and b) writes aren't
+     * synch'ed after every update. This is useful when batch inserting
+     * into a new database.
+     *  <p>
+     *  Only call this method directly after opening the file, otherwise
+     *  the results will be undefined.
+     */
+    public void disableTransactions()
+    {
+        checkIfClosed();
+        recordFile.disableTransactions();
+    }
+
+    
+    /**
+     * Closes the record manager.
+     *
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public void close() throws IOException
+    {
+        checkIfClosed();
+
+        pageMgr.close();
+        pageMgr = null;
+
+        recordFile.close();
+        recordFile = null;
+    }
+
+
+    /**
+     * Inserts a new record using standard java object serialization.
+     *
+     * @param obj the object for the new record.
+     * @return the rowid for the new record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public long insert( Object obj ) throws IOException
+    {
+        return insert( obj, DefaultSerializer.INSTANCE );
+    }
+
+    
+    /**
+     * Inserts a new record using a custom serializer.
+     *
+     * @param obj the object for the new record.
+     * @param serializer a custom serializer
+     * @return the rowid for the new record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public long insert( Object obj, Serializer serializer ) throws IOException
+    {
+        byte[]    data;
+        long      recid;
+        Location  physRowId;
+        
+        checkIfClosed();
+
+        data = serializer.serialize( obj );
+        physRowId = physMgr.insert( data, 0, data.length );
+        recid = logMgr.insert( physRowId ).toLong();
+     
+        LOG.debug( "BaseRecordManager.insert() recid {} length {}", recid, data.length ) ;
+        
+        return recid;
+    }
+
+    
+    /**
+     * Deletes a record.
+     *
+     * @param recid the rowid for the record that should be deleted.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public void delete( long recid ) throws IOException
+    {
+        LockElement element;
+        checkIfClosed();
+        
+        if ( recid <= 0 ) 
+        {
+            throw new IllegalArgumentException( I18n.err( I18n.ERR_536, recid ) );
+        }
+
+        LOG.debug( "BaseRecordManager.delete() recid {}", recid ) ;
+
+        
+        element = beginIO( recid, IOType.WRITE_IO );
+        
+        try
+        {
+            Location logRowId = new Location( recid );
+            Location physRowId = logMgr.fetch( logRowId );
+            physMgr.delete( physRowId );
+            logMgr.delete( logRowId );
+        }
+        finally
+        {
+            this.endIO( recid, element, IOType.WRITE_IO );
+        }
+    }
+
+
+    /**
+     * Updates a record using standard java object serialization.
+     *
+     * @param recid the recid for the record that is to be updated.
+     * @param obj the new object for the record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public void update( long recid, Object obj ) throws IOException
+    {
+        update( recid, obj, DefaultSerializer.INSTANCE );
+    }
+    
+
+    /**
+     * Updates a record using a custom serializer.
+     *
+     * @param recid the recid for the record that is to be updated.
+     * @param obj the new object for the record.
+     * @param serializer a custom serializer
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public void update( long recid, Object obj, Serializer serializer ) throws IOException
+    {
+        LockElement element;
+        
+        checkIfClosed();
+
+        if ( recid <= 0 ) 
+        {
+            throw new IllegalArgumentException( I18n.err( I18n.ERR_536, recid ) );
+        }
+
+        element = this.beginIO( recid, IOType.WRITE_IO );
+         
+        try
+        {
+            Location logRecid = new Location( recid );
+            Location physRecid = logMgr.fetch( logRecid );
+
+            byte[] data = serializer.serialize( obj );
+            
+            LOG.debug( "BaseRecordManager.update() recid {} length {}", recid, data.length ) ;
+            
+            Location newRecid = physMgr.update( physRecid, data, 0, data.length );
+            
+            if ( ! newRecid.equals( physRecid ) ) 
+            {
+                logMgr.update( logRecid, newRecid );
+            }
+         }
+         finally
+         {
+             endIO( recid, element, IOType.WRITE_IO );
+         } 
+    }
+    
+
+    /**
+     * Fetches a record using standard java object serialization.
+     *
+     * @param recid the recid for the record that must be fetched.
+     * @return the object contained in the record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public Object fetch( long recid ) throws IOException
+    {
+        return fetch( recid, DefaultSerializer.INSTANCE );
+    }
+
+    
+    /**
+     * Fetches a record using a custom serializer.
+     *
+     * @param recid the recid for the record that must be fetched.
+     * @param serializer a custom serializer
+     * @return the object contained in the record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public Object fetch( long recid, Serializer serializer ) throws IOException
+    {
+        Object result;
+        LockElement element;
+        
+        checkIfClosed();
+        
+        if ( recid <= 0 ) 
+        {
+            throw new IllegalArgumentException( I18n.err( I18n.ERR_536, recid ) );
+        }
+        
+        element = this.beginIO(recid, IOType.READ_IO);
+        
+        try
+        {
+            byte[] data; 
+            
+            Location location = logMgr.fetch( new Location( recid ) ) ;
+            data = physMgr.fetch( location );
+            
+            LOG.debug( "BaseRecordManager.fetch() recid {} length {}", recid, data.length ) ;
+            
+            result = serializer.deserialize( data );
+        }
+        finally
+        {
+            endIO(recid, element, IOType.READ_IO);
+        }
+        
+        return result;
+    }
+
+
+    /**
+     * Returns the number of slots available for "root" rowids. These slots
+     * can be used to store special rowids, like rowids that point to
+     * other rowids. Root rowids are useful for bootstrapping access to
+     * a set of data.
+     */
+    public int getRootCount()
+    {
+        return FileHeader.NROOTS;
+    }
+
+    
+    /**
+     *  Returns the indicated root rowid.
+     *
+     *  @see #getRootCount
+     */
+    public long getRoot( int id ) throws IOException
+    {
+        checkIfClosed();
+
+        return pageMgr.getFileHeader().getRoot( id );
+    }
+
+
+    /**
+     *  Sets the indicated root rowid.
+     *
+     *  @see #getRootCount
+     */
+    public void setRoot( int id, long rowid ) throws IOException
+    {
+        checkIfClosed();
+
+        pageMgr.getFileHeader().setRoot( id, rowid );
+    }
+
+
+    /**
+     * Obtain the record id of a named object. Returns 0 if named object
+     * doesn't exist.
+     */
+    public long getNamedObject( String name ) throws IOException
+    {
+        checkIfClosed();
+
+        Map<String,Long> nameDirectory = getNameDirectory();
+        Long recid = nameDirectory.get( name );
+
+        if ( recid == null ) 
+        {
+            return 0;
+        }
+        
+        return recid;
+    }
+    
+
+    /**
+     * Set the record id of a named object.
+     */
+    public void setNamedObject( String name, long recid ) throws IOException
+    {
+        checkIfClosed();
+
+        if ( recid == 0 ) 
+        {
+            // remove from hashtable
+            getNameDirectory().remove( name );
+        } 
+        else 
+        {
+            getNameDirectory().put( name, recid );
+        }
+        
+        saveNameDirectory( );
+    }
+
+
+    /**
+     * Commit (make persistent) all changes since beginning of transaction.
+     */
+    public void commit() throws IOException
+    {
+        checkIfClosed();
+
+        pageMgr.commit();
+    }
+
+
+    /**
+     * Rollback (cancel) all changes since beginning of transaction.
+     */
+    public void rollback() throws IOException
+    {
+        checkIfClosed();
+
+        pageMgr.rollback();
+    }
+
+
+    /**
+     * Load name directory
+     */
+    @SuppressWarnings("unchecked")
+    private Map<String,Long> getNameDirectory() throws IOException
+    {
+        // retrieve directory of named hashtable
+        long nameDirectory_recid = getRoot( NAME_DIRECTORY_ROOT );
+        
+        if ( nameDirectory_recid == 0 ) 
+        {
+            nameDirectory = new HashMap<String, Long>();
+            nameDirectory_recid = insert( nameDirectory );
+            setRoot( NAME_DIRECTORY_ROOT, nameDirectory_recid );
+        } 
+        else 
+        {
+            nameDirectory = ( Map<String, Long> ) fetch( nameDirectory_recid );
+        }
+        
+        return nameDirectory;
+    }
+
+
+    private void saveNameDirectory( ) throws IOException
+    {
+        long recid = getRoot( NAME_DIRECTORY_ROOT );
+        
+        if ( recid == 0 ) 
+        {
+            throw new IOException( I18n.err( I18n.ERR_537 ) );
+        }
+        
+        update( recid, nameDirectory );
+    }
+
+
+    /**
+     * Check if RecordManager has been closed.  If so, throw an IllegalStateException.
+     */
+    private void checkIfClosed() throws IllegalStateException
+    {
+        if ( recordFile == null ) 
+        {
+            throw new IllegalStateException( I18n.err( I18n.ERR_538 ) );
+        }
+    }
+    
+
+    /**
+     * Used to serialize reads/write on a given logical rowid. Checks if there is a 
+     * ongoing conflicting IO to the same logical rowid and waits for the ongoing
+     * write if there is one. 
+     *
+     * @param recid the logical rowid for which the fetch will be done.
+     * @param io type of the IO
+     * @return lock element representing the logical lock gotten
+     */
+    private LockElement beginIO( Long recid, IOType io )
+    {
+        boolean lockVerified = false;
+        LockElement element = null;
+
+        // loop until we successfully verify that there is no concurrent writer
+/*
+        element = lockElements.get( recid );
+        
+        do
+        {
+            if ( element == null )
+            {
+                element = new LockElement();
+
+                if ( io == IOType.READ_IO )
+                {
+                    element.bumpReaders();
+                }
+                else
+                {
+                    element.setWritten();
+                }
+
+                LockElement existingElement = lockElements.putIfAbsent( recid, element );
+
+                if ( existingElement == null )
+                {
+                    lockVerified = true;
+                }
+                else
+                {
+                    element = existingElement;
+                }
+            }
+            else
+            {
+                Lock lock = element.getLock();
+                lock.lock();
+                
+                if ( element.anyUser() )
+                {
+                    if ( this.conflictingIOPredicate( io, element ) )
+                    {
+                        element.bumpWaiters();
+                        
+                        do
+                        {
+                            element.getNoConflictingIOCondition()
+                                .awaitUninterruptibly();
+                        }
+                        while ( this.conflictingIOPredicate( io, element ) );
+
+                        element.decrementWaiters();
+                    }
+
+                    // no conflicting IO anymore..done
+                    if ( io == IOType.READ_IO )
+                    {
+                        element.bumpReaders();
+                    }
+                    else
+                    {
+                        element.setWritten();
+                    }
+                    
+                    lockVerified = true;
+                }
+                else
+                {
+                    if ( io == IOType.READ_IO )
+                    {
+                        element.bumpReaders();
+                    }
+                    else
+                    {
+                        element.setWritten();
+                    }
+
+                    LockElement existingElement = lockElements.get( recid );
+
+                    if ( element != existingElement )
+                    {
+                        element = existingElement;
+                    }
+                    else
+                    {
+                        lockVerified = true; // done
+                    }
+                }
+                
+                lock.unlock();
+            }
+        }
+        while ( !lockVerified );
+*/
+        return element;
+    }
+
+
+    /**
+     * Ends the IO by releasing the logical lock on the given recid
+     * 
+     * @param recid logical recid for which the IO is being ended
+     * @param element logical lock to be released
+     * @param io type of the io
+     */
+    private void endIO( Long recid, LockElement element, IOType io )
+    {
+        /*
+        Lock lock = element.getLock();
+        lock.lock();
+
+        if ( io == IOType.READ_IO )
+        {
+            element.decrementReaders();
+        }
+        else
+        {
+            element.unsetWritten();
+        }
+
+        if ( element.anyWaiters() )
+        {
+            element.getNoConflictingIOCondition().notifyAll();
+        }
+
+        if ( !element.anyUser() )
+        {
+            lockElements.remove( recid );
+        }
+
+        lock.unlock();
+        */
+    }
+
+
+    private boolean conflictingIOPredicate( IOType io, LockElement element )
+    {
+        if ( io == IOType.READ_IO )
+        { 
+            return element.beingWritten();
+        }
+        else
+        {
+            return ( element.anyReaders() || element.beingWritten() );
+        }
+    }
+}

Added: directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/BlockIo.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/BlockIo.java?rev=1239581&view=auto
==============================================================================
--- directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/BlockIo.java (added)
+++ directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/BlockIo.java Thu Feb  2 12:38:39 2012
@@ -0,0 +1,429 @@
+/**
+ * JDBM LICENSE v1.00
+ *
+ * Redistribution and use of this software and associated documentation
+ * ("Software"), with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain copyright
+ *    statements and notices.  Redistributions must also contain a
+ *    copy of this document.
+ *
+ * 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 name "JDBM" must not be used to endorse or promote
+ *    products derived from this Software without prior written
+ *    permission of Cees de Groot.  For written permission,
+ *    please contact cg@cdegroot.com.
+ *
+ * 4. Products derived from this Software may not be called "JDBM"
+ *    nor may "JDBM" appear in their names without prior written
+ *    permission of Cees de Groot.
+ *
+ * 5. Due credit should be given to the JDBM Project
+ *    (http://jdbm.sourceforge.net/).
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
+ * ``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
+ * CEES DE GROOT OR ANY 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.
+ *
+ * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
+ * Contributions are Copyright (C) 2000 by their associated contributors.
+ *
+ * $Id: BlockIo.java,v 1.2 2002/08/06 05:18:36 boisvert Exp $
+ */
+package jdbm.recman;
+
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+import org.apache.directory.server.i18n.I18n;
+
+
+/**
+ * This class wraps a page-sized byte array and provides methods to read and 
+ * write data to and from it. The readers and writers are just the ones that 
+ * the rest of the toolkit needs, nothing else. Values written are compatible 
+ * with java.io routines.
+ * 
+ * This block is never accessed directly, so it does not have to be thread-safe.
+ *
+ * @see java.io.DataInput
+ * @see java.io.DataOutput
+ */
+public final class BlockIo implements java.io.Externalizable 
+{
+    public final static long serialVersionUID = 2L;
+
+    /** The block Identifier */
+    private long blockId;
+
+    /** The row data contained in this block */
+    private byte[] data;
+    
+    /** A view on the BlockIo */
+    private BlockView view = null;
+    
+    /** A flag set when this block has been modified */
+    private boolean dirty = false;
+    
+    /** The number of pending transaction on this block */
+    private AtomicInteger transactionCount = new AtomicInteger( 0 );
+
+    
+    /**
+     * Default constructor for serialization
+     */
+    public BlockIo() 
+    {
+        // empty
+    }
+
+    
+    /**
+     * Constructs a new BlockIo instance.
+     * 
+     * @param blockId The identifier for this block
+     * @param data The data to store
+     */
+    /*No qualifier*/ BlockIo( long blockId, byte[] data ) 
+    {
+        // remove me for production version
+        if ( blockId < 0 )
+        {
+            throw new Error( I18n.err( I18n.ERR_539_BAD_BLOCK_ID, blockId ) );
+        }
+        
+        this.blockId = blockId;
+        this.data = data;
+    }
+
+    
+    /**
+     * @return the underlying array
+     */
+    /*No qualifier*/ byte[] getData() 
+    {
+        return data;
+    }
+
+    
+    /**
+     * Sets the block number. Should only be called by RecordFile.
+     * 
+     * @param The block identifier
+     */
+    /*No qualifier*/ void setBlockId( long blockId ) 
+    {
+        if ( isInTransaction() )
+        {
+            throw new Error( I18n.err( I18n.ERR_540 ) );
+        }
+        
+        if ( blockId < 0 )
+        {
+            throw new Error( I18n.err( I18n.ERR_539_BAD_BLOCK_ID, blockId ) );
+        }
+            
+        this.blockId = blockId;
+    }
+
+    
+    /**
+     * @return the block number.
+     */
+    /*No qualifier*/ long getBlockId() 
+    {
+        return blockId;
+    }
+
+    
+    /**
+     * @return the current view of the block.
+     */
+    public BlockView getView() 
+    {
+        return view;
+    }
+
+    
+    /**
+     * Sets the current view of the block.
+     * 
+     * @param view the current view
+     */
+    public void setView( BlockView view ) 
+    {
+        this.view = view;
+    }
+
+    
+    /**
+     * Sets the dirty flag
+     */
+    /*No qualifier*/ void setDirty() 
+    {
+        dirty = true;
+    }
+
+    
+    /**
+     * Clears the dirty flag
+     */
+    /*No qualifier*/ void setClean() 
+    {
+        dirty = false;
+    }
+
+    
+    /**
+     * Returns true if the dirty flag is set.
+     */
+    /*No qualifier*/ boolean isDirty() 
+    {
+        return dirty;
+    }
+
+    
+    /**
+     * Returns true if the block is still dirty with respect to the 
+     * transaction log.
+     */
+    /*No qualifier*/ boolean isInTransaction() 
+    {
+        return transactionCount.get() != 0;
+    }
+
+
+    /**
+     * Increments transaction count for this block, to signal that this
+     * block is in the log but not yet in the data recordFile. The method also
+     * takes a snapshot so that the data may be modified in new transactions.
+     */
+    /*No qualifier*/ void incrementTransactionCount() 
+    {
+        transactionCount.getAndIncrement();
+    }
+
+    
+    /**
+     * Decrements transaction count for this block, to signal that this
+     * block has been written from the log to the data recordFile.
+     */
+    /*No qualifier*/ void decrementTransactionCount() 
+    {
+        if ( transactionCount.decrementAndGet() < 0 )
+        {
+            throw new Error( I18n.err( I18n.ERR_541, getBlockId() ) );
+        }
+    }
+    
+
+    /**
+     * Reads a byte from the indicated position
+     * 
+     * @param pos the position at which we will read the byte
+     * @return the read byte
+     */
+    public byte readByte( int pos ) 
+    {
+        return data[pos];
+    }
+    
+
+    /**
+     * Writes a byte to the indicated position
+     * 
+     * @param pos The position where we want to write the value to
+     * @param value the byte value we want to write into the BlockIo
+     */
+    public void writeByte( int pos, byte value ) 
+    {
+        data[pos] = value;
+        dirty = true;
+    }
+
+    
+    /**
+     * Reads a short from the indicated position
+     * 
+     * @param pos the position at which we will read the short
+     * @return the read short
+     */
+    public short readShort( int pos ) 
+    {
+        return ( short )
+            ( ( ( data[pos+0] & 0xff ) << 8 ) |
+             ( ( data[pos+1] & 0xff ) << 0 ) );
+    }
+
+    
+    /**
+     * Writes a short to the indicated position
+     * 
+     * @param pos The position where we want to write the value to
+     * @param value the short value we want to write into the BlockIo
+     */
+    public void writeShort( int pos, short value ) 
+    {
+        data[pos+0] = ( byte ) ( 0xff & ( value >> 8 ) );
+        data[pos+1] = ( byte ) ( 0xff & ( value >> 0 ) );
+        dirty = true;
+    }
+
+    
+    /**
+     * Reads an int from the indicated position
+     * 
+     * @param pos the position at which we will read the int
+     * @return the read int
+     */
+    public int readInt( int pos ) 
+    {
+        return
+            ( data[pos+0] << 24) |
+            ( ( data[pos+1] & 0xff ) << 16) |
+            ( ( data[pos+2] & 0xff ) <<  8) |
+            ( ( data[pos+3] & 0xff ) <<  0 );
+    }
+
+    
+    /**
+     * Writes an int to the indicated position
+     * 
+     * @param pos The position where we want to write the value to
+     * @param value the int value we want to write into the BlockIo
+     */
+    public void writeInt( int pos, int value ) 
+    {
+        data[pos+0] = ( byte ) ( 0xff & ( value >> 24 ) );
+        data[pos+1] = ( byte ) ( 0xff & ( value >> 16 ) );
+        data[pos+2] = ( byte ) ( 0xff & ( value >>  8 ) );
+        data[pos+3] = ( byte ) ( 0xff & ( value >>  0 ) );
+        dirty = true;
+    }
+
+    
+    /**
+     * Reads a long from the indicated position
+     * 
+     * @param pos the position at which we will read the long
+     * @return the read long
+     */
+    public long readLong( int pos )
+    {
+        return
+            ( ( long )( (long)data[pos+0] << 56 ) |
+                        ( (long)( data[pos+1] & 0xff ) << 48 ) |
+                        ( (long)( data[pos+2] & 0xff ) << 40 ) |
+                        ( (long)( data[pos+3] & 0xff ) << 32 ) |
+                        ( (long)( data[pos+4] & 0xff ) << 24 ) |
+                        ( (long)( data[pos+5] & 0xff ) << 16 ) |
+                        ( (long)( data[pos+6] & 0xff ) <<  8 ) |
+                        ( (long)( data[pos+7] & 0xff ) ) );
+    }
+
+    
+    /**
+     * Writes a long to the indicated position
+     * 
+     * @param pos The position where we want to write the value to
+     * @param value the long value we want to write into the BlockIo
+     */
+    public void writeLong(int pos, long value) {
+        data[pos+0] = (byte)(0xff & (value >> 56));
+        data[pos+1] = (byte)(0xff & (value >> 48));
+        data[pos+2] = (byte)(0xff & (value >> 40));
+        data[pos+3] = (byte)(0xff & (value >> 32));
+        data[pos+4] = (byte)(0xff & (value >> 24));
+        data[pos+5] = (byte)(0xff & (value >> 16));
+        data[pos+6] = (byte)(0xff & (value >>  8));
+        data[pos+7] = (byte)(0xff & (value >>  0));
+        dirty = true;
+    }
+
+    
+    /**
+     * {@inheritDoc}
+     */
+    public String toString() 
+    {
+        if ( view != null )
+        {
+            return view.toString();
+        }
+        
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append( "BlockIO ( " );
+        
+        // The blockID
+        sb.append( blockId ).append( ", " );
+        
+        // Is it dirty ?
+        if ( dirty )
+        {
+            sb.append( "dirty, " );
+        }
+        else
+        {
+            sb.append( "clean, " );
+        }
+        
+        // The view
+        if ( view != null )
+        {
+            sb.append( view.getClass().getSimpleName() ).append( ", " );
+        }
+        else
+        {
+            sb.append( "no view, " );
+        }
+        
+        // The transaction count
+        sb.append( "tx: " ).append( transactionCount.get() );
+
+        sb.append( " )" );
+        
+        return sb.toString();
+    }
+
+    
+    /**
+     * implement externalizable interface
+     */
+    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException 
+    {
+        blockId = in.readLong();
+        int length = in.readInt();
+        data = new byte[length];
+        in.readFully(data);
+    }
+
+    
+    /**
+     * implement externalizable interface
+     */
+    public void writeExternal( ObjectOutput out ) throws IOException 
+    {
+        out.writeLong( blockId );
+        out.writeInt( data.length );
+        out.write( data );
+    }
+}

Added: directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/BlockView.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/BlockView.java?rev=1239581&view=auto
==============================================================================
--- directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/BlockView.java (added)
+++ directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/BlockView.java Thu Feb  2 12:38:39 2012
@@ -0,0 +1,59 @@
+/**
+ * JDBM LICENSE v1.00
+ *
+ * Redistribution and use of this software and associated documentation
+ * ("Software"), with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain copyright
+ *    statements and notices.  Redistributions must also contain a
+ *    copy of this document.
+ *
+ * 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 name "JDBM" must not be used to endorse or promote
+ *    products derived from this Software without prior written
+ *    permission of Cees de Groot.  For written permission,
+ *    please contact cg@cdegroot.com.
+ *
+ * 4. Products derived from this Software may not be called "JDBM"
+ *    nor may "JDBM" appear in their names without prior written
+ *    permission of Cees de Groot. 
+ *
+ * 5. Due credit should be given to the JDBM Project
+ *    (http://jdbm.sourceforge.net/).
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
+ * ``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
+ * CEES DE GROOT OR ANY 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.
+ *
+ * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
+ * Contributions are Copyright (C) 2000 by their associated contributors.
+ *
+ * $Id: BlockView.java,v 1.2 2005/06/25 23:12:32 doomdark Exp $
+ */
+package jdbm.recman;
+
+
+
+/**
+ *  This is a marker interface that is implemented by classes that
+ *  interpret blocks of data by pretending to be an overlay.
+ *
+ *  @see BlockIo#setView
+ */
+public interface BlockView
+{
+}

Added: directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/CacheRecordManager.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/CacheRecordManager.java?rev=1239581&view=auto
==============================================================================
--- directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/CacheRecordManager.java (added)
+++ directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/CacheRecordManager.java Thu Feb  2 12:38:39 2012
@@ -0,0 +1,468 @@
+/**
+ * JDBM LICENSE v1.00
+ *
+ * Redistribution and use of this software and associated documentation
+ * ("Software"), with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain copyright
+ *    statements and notices.  Redistributions must also contain a
+ *    copy of this document.
+ *
+ * 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 name "JDBM" must not be used to endorse or promote
+ *    products derived from this Software without prior written
+ *    permission of Cees de Groot.  For written permission,
+ *    please contact cg@cdegroot.com.
+ *
+ * 4. Products derived from this Software may not be called "JDBM"
+ *    nor may "JDBM" appear in their names without prior written
+ *    permission of Cees de Groot.
+ *
+ * 5. Due credit should be given to the JDBM Project
+ *    (http://jdbm.sourceforge.net/).
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
+ * ``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
+ * CEES DE GROOT OR ANY 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.
+ *
+ * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
+ * Copyright 2000-2001 (C) Alex Boisvert. All Rights Reserved.
+ * Contributions are Copyright (C) 2000 by their associated contributors.
+ *
+ * $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
+ */
+package jdbm.recman;
+
+
+import jdbm.RecordManager;
+import jdbm.helper.CacheEvictionException;
+import jdbm.helper.CachePolicy;
+import jdbm.helper.CachePolicyListener;
+import jdbm.helper.DefaultSerializer;
+import jdbm.helper.Serializer;
+import jdbm.helper.WrappedRuntimeException;
+
+import java.io.IOException;
+import java.util.Enumeration;
+
+import org.apache.directory.server.i18n.I18n;
+
+
+/**
+ *  A RecordManager wrapping and caching another RecordManager.
+ *
+ * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
+ * @author <a href="cg@cdegroot.com">Cees de Groot</a>
+ */
+public class CacheRecordManager implements RecordManager
+{
+    /** Wrapped RecordManager */
+    protected RecordManager recordManager;
+
+    /** Cache for underlying RecordManager */
+    protected CachePolicy<Long, CacheEntry> cache;
+
+
+    /**
+     * Construct a CacheRecordManager wrapping another RecordManager and
+     * using a given cache policy.
+     *
+     * @param recordManager Wrapped RecordManager
+     * @param cache Cache policy
+     */
+    public CacheRecordManager( RecordManager recordManager, CachePolicy<Long,CacheEntry> cache )
+    {
+        if ( recordManager == null ) 
+        {
+            throw new IllegalArgumentException( I18n.err( I18n.ERR_517 ) );
+        }
+        
+        if ( cache == null ) 
+        {
+            throw new IllegalArgumentException( I18n.err( I18n.ERR_542 ) );
+        }
+
+        this.recordManager = recordManager;
+        this.cache = cache;
+        this.cache.addListener( new CacheListener() );
+    }
+
+    
+    /**
+     * Get the underlying Record Manager.
+     *
+     * @return underlying RecordManager or null if CacheRecordManager has
+     *         been closed. 
+     */
+    public RecordManager getRecordManager()
+    {
+        return recordManager;
+    }
+
+    
+    /**
+     * Get the underlying cache policy
+     *
+     * @return underlying CachePolicy or null if CacheRecordManager has
+     *         been closed. 
+     */
+    public CachePolicy<Long,CacheEntry> getCachePolicy()
+    {
+        return cache;
+    }
+
+    
+    /**
+     * Inserts a new record using a custom serializer.
+     *
+     * @param obj the object for the new record.
+     * @return the rowid for the new record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public long insert( Object obj ) throws IOException
+    {
+        return insert( obj, DefaultSerializer.INSTANCE );
+    }
+        
+        
+    /**
+     * Inserts a new record using a custom serializer.
+     *
+     * @param obj the object for the new record.
+     * @param serializer a custom serializer
+     * @return the rowid for the new record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public synchronized long insert( Object obj, Serializer serializer ) throws IOException
+    {
+        checkIfClosed();
+
+        long recid = recordManager.insert( obj, serializer );
+        
+        try 
+        {
+            cache.put( recid, new CacheEntry( recid, obj, serializer, false ) );
+        } 
+        catch ( CacheEvictionException except ) 
+        {
+            throw new WrappedRuntimeException( except );
+        }
+        
+        return recid;
+    }
+
+
+    /**
+     * Deletes a record.
+     *
+     * @param recid the rowid for the record that should be deleted.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public synchronized void delete( long recid ) throws IOException
+    {
+        checkIfClosed();
+
+        // Remove the entry from the underlying storage
+        recordManager.delete( recid );
+        
+        // And now update the cache
+        cache.remove( recid );
+    }
+
+
+    /**
+     * Updates a record using standard Java serialization.
+     *
+     * @param recid the recid for the record that is to be updated.
+     * @param obj the new object for the record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public void update( long recid, Object obj ) throws IOException
+    {
+        update( recid, obj, DefaultSerializer.INSTANCE );
+    }
+    
+
+    /**
+     * Updates a record using a custom serializer.
+     *
+     * @param recid the recid for the record that is to be updated.
+     * @param obj the new object for the record.
+     * @param serializer a custom serializer
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public synchronized void update( long recid, Object obj, Serializer serializer ) throws IOException
+    {
+        checkIfClosed();
+
+        try {
+            CacheEntry entry = cache.get( recid );
+            
+            if ( entry != null ) 
+            {
+                // reuse existing cache entry
+                entry.obj = obj;
+                entry.serializer = serializer;
+                entry.isDirty = true;
+            } 
+            else 
+            {
+                cache.put( recid, new CacheEntry( recid, obj, serializer, true ) );
+            }
+        } 
+        catch ( CacheEvictionException except ) 
+        {
+            throw new IOException( except.getLocalizedMessage() );
+        }
+    }
+
+
+    /**
+     * Fetches a record using standard Java serialization.
+     *
+     * @param recid the recid for the record that must be fetched.
+     * @return the object contained in the record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public Object fetch( long recid ) throws IOException
+    {
+        return fetch( recid, DefaultSerializer.INSTANCE );
+    }
+
+        
+    /**
+     * Fetches a record using a custom serializer.
+     *
+     * @param recid the recid for the record that must be fetched.
+     * @param serializer a custom serializer
+     * @return the object contained in the record.
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public synchronized Object fetch( long recid, Serializer serializer ) throws IOException
+    {
+        checkIfClosed();
+
+        CacheEntry entry = cache.get( recid );
+        
+        if ( entry == null ) 
+        {
+            entry = new CacheEntry( recid, null, serializer, false );
+            entry.obj = recordManager.fetch( recid, serializer );
+            
+            try 
+            {
+                cache.put( recid, entry );
+            } 
+            catch ( CacheEvictionException except ) 
+            {
+                throw new WrappedRuntimeException( except );
+            }
+        }
+        
+        if ( entry.obj instanceof byte[] )
+        {
+            byte[] copy = new byte[ ( ( byte[] ) entry.obj ).length ];
+            System.arraycopy( entry.obj, 0, copy, 0, ( ( byte[] ) entry.obj ).length );
+            return copy;
+        }
+        
+        return entry.obj;
+    }
+
+
+    /**
+     * Closes the record manager.
+     *
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public synchronized void close() throws IOException
+    {
+        checkIfClosed();
+
+        updateCacheEntries();
+        recordManager.close();
+        recordManager = null;
+        cache = null;
+    }
+
+
+    /**
+     * Returns the number of slots available for "root" rowids. These slots
+     * can be used to store special rowids, like rowids that point to
+     * other rowids. Root rowids are useful for bootstrapping access to
+     * a set of data.
+     */
+    public synchronized int getRootCount()
+    {
+        checkIfClosed();
+
+        return recordManager.getRootCount();
+    }
+
+
+    /**
+     * Returns the indicated root rowid.
+     *
+     * @see #getRootCount
+     */
+    public synchronized long getRoot( int id ) throws IOException
+    {
+        checkIfClosed();
+
+        return recordManager.getRoot( id );
+    }
+
+
+    /**
+     * Sets the indicated root rowid.
+     *
+     * @see #getRootCount
+     */
+    public synchronized void setRoot( int id, long rowid ) throws IOException
+    {
+        checkIfClosed();
+
+        recordManager.setRoot( id, rowid );
+    }
+
+
+    /**
+     * Commit (make persistent) all changes since beginning of transaction.
+     */
+    public synchronized void commit() throws IOException
+    {
+        checkIfClosed();
+        updateCacheEntries();
+        recordManager.commit();
+    }
+
+
+    /**
+     * Rollback (cancel) all changes since beginning of transaction.
+     */
+    public synchronized void rollback() throws IOException
+    {
+        checkIfClosed();
+
+        recordManager.rollback();
+
+        // discard all cache entries since we don't know which entries
+        // where part of the transaction
+        cache.removeAll();
+    }
+
+
+    /**
+     * Obtain the record id of a named object. Returns 0 if named object
+     * doesn't exist.
+     */
+    public synchronized long getNamedObject( String name ) throws IOException
+    {
+        checkIfClosed();
+
+        return recordManager.getNamedObject( name );
+    }
+
+
+    /**
+     * Set the record id of a named object.
+     */
+    public synchronized void setNamedObject( String name, long recid ) throws IOException
+    {
+        checkIfClosed();
+
+        recordManager.setNamedObject( name, recid );
+    }
+
+
+    /**
+     * Check if RecordManager has been closed.  If so, throw an IllegalStateException
+     */
+    private void checkIfClosed() throws IllegalStateException
+    {
+        if ( recordManager == null ) 
+        {
+            throw new IllegalStateException( I18n.err( I18n.ERR_538 ) );
+        }
+    }
+
+    
+    /**
+     * Update all dirty cache objects to the underlying RecordManager.
+     */
+    protected void updateCacheEntries() throws IOException
+    {
+        Enumeration<CacheEntry> enume = cache.elements();
+        
+        while ( enume.hasMoreElements() ) 
+        {
+            CacheEntry entry = enume.nextElement();
+            
+            if ( entry.isDirty ) 
+            {
+                recordManager.update( entry.recid, entry.obj, entry.serializer );
+                entry.isDirty = false;
+            }
+        }
+    }
+
+    /**
+     * A class to store a cached entry. 
+     */
+    private static class CacheEntry
+    {
+        long recid;
+        Object obj;
+        Serializer serializer;
+        boolean isDirty;
+        
+        CacheEntry( long recid, Object obj, Serializer serializer, boolean isDirty )
+        {
+            this.recid = recid;
+            this.obj = obj;
+            this.serializer = serializer;
+            this.isDirty = isDirty;
+        }
+        
+    } // class CacheEntry
+
+    
+    private class CacheListener implements CachePolicyListener<CacheEntry>
+    {
+        
+        /** 
+         * Notification that cache is evicting an object
+         *
+         * @param obj object evicted from cache
+         */
+        public void cacheObjectEvicted( CacheEntry obj ) throws CacheEvictionException
+        {
+            CacheEntry entry = obj;
+            if ( entry.isDirty ) 
+            {
+                try 
+                {
+                    recordManager.update( entry.recid, entry.obj, entry.serializer );
+                } 
+                catch ( IOException except ) 
+                {
+                    throw new CacheEvictionException( except );
+                }
+            }
+        }
+    }
+}

Added: directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/DataPage.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/DataPage.java?rev=1239581&view=auto
==============================================================================
--- directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/DataPage.java (added)
+++ directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/DataPage.java Thu Feb  2 12:38:39 2012
@@ -0,0 +1,145 @@
+/**
+ * JDBM LICENSE v1.00
+ *
+ * Redistribution and use of this software and associated documentation
+ * ("Software"), with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain copyright
+ *    statements and notices.  Redistributions must also contain a
+ *    copy of this document.
+ *
+ * 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 name "JDBM" must not be used to endorse or promote
+ *    products derived from this Software without prior written
+ *    permission of Cees de Groot.  For written permission,
+ *    please contact cg@cdegroot.com.
+ *
+ * 4. Products derived from this Software may not be called "JDBM"
+ *    nor may "JDBM" appear in their names without prior written
+ *    permission of Cees de Groot. 
+ *
+ * 5. Due credit should be given to the JDBM Project
+ *    (http://jdbm.sourceforge.net/).
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
+ * ``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
+ * CEES DE GROOT OR ANY 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.
+ *
+ * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
+ * Contributions are Copyright (C) 2000 by their associated contributors.
+ *
+ * $Id: DataPage.java,v 1.1 2000/05/06 00:00:31 boisvert Exp $
+ */
+package jdbm.recman;
+
+
+
+import org.apache.directory.server.i18n.I18n;
+
+
+/**
+ * Class describing a page that holds data.
+ */
+final class DataPage extends PageHeader 
+{
+    // offsets
+    /** first short in the file after the page header info: 18 byte offset */
+    private static final short O_FIRST = PageHeader.SIZE; // short firstrowid
+    
+    /** start of the data in this block: 20 byte offset */
+    static final short O_DATA = ( short ) ( O_FIRST + Magic.SZ_SHORT );
+    
+    /** total amount of data in this page/block: BLOCK_SIZE - 20 bytes */
+    static final short DATA_PER_PAGE = ( short ) ( RecordFile.BLOCK_SIZE - O_DATA );
+
+    
+    /**
+     * Constructs a data page view from the indicated block.
+     */
+    DataPage( BlockIo block ) 
+    {
+        super( block );
+    }
+
+    
+    /**
+     * Factory method to create or return a data page for the indicated block.
+     */
+    static DataPage getDataPageView( BlockIo blockIo ) 
+    {
+        BlockView view = blockIo.getView();
+        
+        if ( ( view != null ) && ( view instanceof DataPage ) )
+        {
+            return ( DataPage ) view;
+        }
+        else
+        { 
+            return new DataPage( blockIo );
+        }
+    }
+    
+
+    /** 
+     * @return the first rowid's offset 
+     */
+    short getFirst() 
+    {
+        return blockIo.readShort( O_FIRST );
+    }
+    
+    
+    /** 
+     * Sets the first rowid's offset 
+     */
+    void setFirst( short value ) 
+    {
+        paranoiaMagicOk();
+        
+        if ( value > 0 && value < O_DATA )
+        {
+            throw new Error( I18n.err( I18n.ERR_543, value ) );
+        }
+  
+        blockIo.writeShort( O_FIRST, value );
+    }
+    
+    
+    /**
+     * {@inheritDoc}
+     */
+    public String toString() 
+    {
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append( "DataPage ( " );
+        
+        // The blockIO
+        sb.append( super.toString() ).append( ", " );
+        
+        // The first rowId
+        sb.append( "first rowId: " ).append( getFirst() ).append( ", " );
+        
+        // The data per page 
+        sb.append( "[p:" ).append( getPrev() ).append( ", " );
+        
+        // The next page
+        sb.append( "n:" ).append( getNext() ).append( "] )" );
+        
+        return sb.toString();
+    }
+}

Added: directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FileHeader.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FileHeader.java?rev=1239581&view=auto
==============================================================================
--- directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FileHeader.java (added)
+++ directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FileHeader.java Thu Feb  2 12:38:39 2012
@@ -0,0 +1,258 @@
+/**
+ * JDBM LICENSE v1.00
+ *
+ * Redistribution and use of this software and associated documentation
+ * ("Software"), with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain copyright
+ *    statements and notices.  Redistributions must also contain a
+ *    copy of this document.
+ *
+ * 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 name "JDBM" must not be used to endorse or promote
+ *    products derived from this Software without prior written
+ *    permission of Cees de Groot.  For written permission,
+ *    please contact cg@cdegroot.com.
+ *
+ * 4. Products derived from this Software may not be called "JDBM"
+ *    nor may "JDBM" appear in their names without prior written
+ *    permission of Cees de Groot. 
+ *
+ * 5. Due credit should be given to the JDBM Project
+ *    (http://jdbm.sourceforge.net/).
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
+ * ``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
+ * CEES DE GROOT OR ANY 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.
+ *
+ * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
+ * Contributions are Copyright (C) 2000 by their associated contributors.
+ *
+ * $Id: FileHeader.java,v 1.3 2005/06/25 23:12:32 doomdark Exp $
+ */
+package jdbm.recman;
+
+
+import java.io.IOException;
+
+import org.apache.directory.server.i18n.I18n;
+
+
+/**
+ * This class represents a file header. It is a 1:1 representation of
+ * the data that appears in block 0 of a file.<br/>
+ * The FileHeader is stored into a BlockIo.<br/>
+ * The BlockIo will always contain the following bytes : <br/>
+ * <ul>
+ * <li>[0..3] 0x1350 : a marker for a FILE_HEADER</li>
+ * <li>[4..11]  : The BlockIo reference to the first FREE_PAGE ID</li>
+ * <li>[12..19] : The BlockIo reference to the last FREE_PAGE ID</li>
+ * <li>[20..27] : The BlockIo reference to the first USED_PAGE ID</li>
+ * <li>[28..35] : The BlockIo reference to the last USED_PAGE ID</li>
+ * <li>[36..43] : The BlockIo reference to the first TRANSLATION_PAGE ID</li>
+ * <li>[44..51] : The BlockIo reference to the last TRANSLATION_PAGE ID</li>
+ * <li>[52..59] : The BlockIo reference to the first FREELOGIDS_PAGE ID</li>
+ * <li>[60..67] : The BlockIo reference to the last FREELOGIDS_PAGE ID</li>
+ * <li>[68..71] : The BlockIo reference to the first FREEPHYSIDS_PAGE ID</li>
+ * <li>[72..79] : The BlockIo reference to the last FREEPHYSIDS_PAGE ID</li>
+ * <li>[80..87]* : The reference to the BlockIo which is the root for the data contained in this File.
+ * We may have more than one, but no more than 1014, if the BLOCK_SIZE is 8192</li>
+ * </ul> 
+ */
+class FileHeader implements BlockView 
+{
+    /** Position of the Magic number for FileHeader */
+    private static final short O_MAGIC = 0; // short magic
+    
+    /** Position of the Lists in the blockIo */
+    private static final short O_LISTS = Magic.SZ_SHORT; // long[2*NLISTS]
+    
+    /** Position of the ROOTs in the blockIo */
+    private static final int O_ROOTS = O_LISTS + ( Magic.NLISTS * 2 * Magic.SZ_LONG );
+
+    /** The BlockIo used to store the FileHeader */
+    private BlockIo block;
+
+    /** The number of "root" rowids available in the file. */
+    static final int NROOTS = ( RecordFile.BLOCK_SIZE - O_ROOTS ) / Magic.SZ_LONG;
+
+    
+    /**
+     * Constructs a FileHeader object from a block.
+     *
+     * @param block The block that contains the file header
+     * @param isNew If true, the file header is for a new file.
+     * @throws IOException if the block is too short to keep the file
+     *         header.
+     */
+    FileHeader( BlockIo block, boolean isNew ) 
+    {
+        this.block = block;
+        
+        if ( isNew )
+        {
+            block.writeShort( O_MAGIC, Magic.FILE_HEADER );
+        }
+        else if ( block.readShort( O_MAGIC ) != Magic.FILE_HEADER )
+        {
+            throw new Error( I18n.err( I18n.ERR_544, block.readShort( O_MAGIC ) ) );
+        }
+    }
+
+
+    /** 
+     * Returns the offset of the "first" block of the indicated list 
+     */
+    private short offsetOfFirst( int list ) 
+    {
+        return ( short ) ( O_LISTS + ( 2 * Magic.SZ_LONG * list ) );
+    }
+
+    
+    /** 
+     * Returns the offset of the "last" block of the indicated list 
+     */
+    private short offsetOfLast( int list ) 
+    {
+        return ( short ) ( offsetOfFirst( list ) + Magic.SZ_LONG );
+    }
+
+    
+    /** 
+     * Returns the offset of the indicated root 
+     */
+    private short offsetOfRoot( int root ) 
+    {
+        return ( short ) ( O_ROOTS + ( root * Magic.SZ_LONG ) );
+    }
+
+    
+    /**
+     * Returns the first block of the indicated list
+     */
+    long getFirstOf( int list ) 
+    {
+        return block.readLong( offsetOfFirst( list ) );
+    }
+    
+    
+    /**
+     * Sets the first block of the indicated list
+     */
+    void setFirstOf( int list, long value ) 
+    {
+        block.writeLong( offsetOfFirst( list ), value );
+    }
+    
+    
+    /**
+     * Returns the last block of the indicated list
+     */
+    long getLastOf( int list ) 
+    {
+        return block.readLong( offsetOfLast( list ) );
+    }
+    
+    
+    /**
+     * Sets the last block of the indicated list
+     */
+    void setLastOf( int list, long value ) 
+    {
+        block.writeLong( offsetOfLast( list ), value );
+    }
+    
+
+    /**
+     *  Returns the indicated root rowid. A root rowid is a special rowid
+     *  that needs to be kept between sessions. It could conceivably be
+     *  stored in a special file, but as a large amount of space in the
+     *  block header is wasted anyway, it's more useful to store it where
+     *  it belongs.
+     *
+     *  @see #NROOTS
+     */
+    long getRoot( int root ) 
+    {
+        return block.readLong( offsetOfRoot( root ) );
+    }
+
+    
+    /**
+     *  Sets the indicated root rowid.
+     *
+     *  @see #getRoot
+     *  @see #NROOTS
+     */
+    void setRoot( int root, long rowid ) 
+    {
+        block.writeLong( offsetOfRoot( root ), rowid );
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString() 
+    {
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append( "FileHeader ( " );
+        
+        // The blockIO
+        sb.append( block ).append( ", " );
+        
+        // The free pages
+        sb.append( "free[" );
+        sb.append( block.readLong( ( short ) ( 2 + ( 2 * Magic.SZ_LONG * Magic.FREE_PAGE ) ) ) );
+        sb.append( ", " );
+        sb.append( block.readLong( ( short ) ( 2 + ( 2 * Magic.SZ_LONG * Magic.FREE_PAGE )  + Magic.SZ_LONG ) ) );
+        sb.append( "], " );
+
+        // The used pages
+        sb.append( "used[" );
+        sb.append( block.readLong( ( short ) ( 2 + ( 2 * Magic.SZ_LONG * Magic.USED_PAGE ) ) ) );
+        sb.append( ", " );
+        sb.append( block.readLong( ( short ) ( 2 + ( 2 * Magic.SZ_LONG * Magic.USED_PAGE )  + Magic.SZ_LONG ) ) );
+        sb.append( "], " );
+        
+        // The translation pages
+        sb.append( "translation[" );
+        sb.append( block.readLong( ( short ) ( 2 + ( 2 * Magic.SZ_LONG * Magic.TRANSLATION_PAGE ) ) ) );
+        sb.append( ", " );
+        sb.append( block.readLong( ( short ) ( 2 + ( 2 * Magic.SZ_LONG * Magic.TRANSLATION_PAGE )  + Magic.SZ_LONG ) ) );
+        sb.append( "], " );
+
+        // The freeLogIds pages
+        sb.append( "freeLogIds[" );
+        sb.append( block.readLong( ( short ) ( 2 + ( 2 * Magic.SZ_LONG * Magic.FREELOGIDS_PAGE ) ) ) );
+        sb.append( ", " );
+        sb.append( block.readLong( ( short ) ( 2 + ( 2 * Magic.SZ_LONG * Magic.FREELOGIDS_PAGE )  + Magic.SZ_LONG ) ) );
+        sb.append( "], " );
+
+        // The freePhysIds pages
+        sb.append( "freePhysIds[" );
+        sb.append( block.readLong( ( short ) ( 2 + ( 2 * Magic.SZ_LONG * Magic.FREEPHYSIDS_PAGE ) ) ) );
+        sb.append( ", " );
+        sb.append( block.readLong( ( short ) ( 2 + ( 2 * Magic.SZ_LONG * Magic.FREEPHYSIDS_PAGE )  + Magic.SZ_LONG ) ) );
+        sb.append( "]" );
+
+        sb.append( " )" );
+        
+        return sb.toString();
+    }
+}

Added: directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FreeLogicalRowIdPage.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FreeLogicalRowIdPage.java?rev=1239581&view=auto
==============================================================================
--- directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FreeLogicalRowIdPage.java (added)
+++ directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FreeLogicalRowIdPage.java Thu Feb  2 12:38:39 2012
@@ -0,0 +1,242 @@
+/**
+ * JDBM LICENSE v1.00
+ *
+ * Redistribution and use of this software and associated documentation
+ * ("Software"), with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain copyright
+ *    statements and notices.  Redistributions must also contain a
+ *    copy of this document.
+ *
+ * 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 name "JDBM" must not be used to endorse or promote
+ *    products derived from this Software without prior written
+ *    permission of Cees de Groot.  For written permission,
+ *    please contact cg@cdegroot.com.
+ *
+ * 4. Products derived from this Software may not be called "JDBM"
+ *    nor may "JDBM" appear in their names without prior written
+ *    permission of Cees de Groot. 
+ *
+ * 5. Due credit should be given to the JDBM Project
+ *    (http://jdbm.sourceforge.net/).
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
+ * ``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
+ * CEES DE GROOT OR ANY 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.
+ *
+ * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
+ * Contributions are Copyright (C) 2000 by their associated contributors.
+ *
+ * $Id: FreeLogicalRowIdPage.java,v 1.1 2000/05/06 00:00:31 boisvert Exp $
+ */
+package jdbm.recman;
+
+
+
+/**
+ * Class describing a page that holds logical rowids that were freed. Note
+ * that the methods have *physical* rowids in their signatures - this is
+ * because logical and physical rowids are internally the same, only their
+ * external representation (i.e. in the client API) differs.
+ */
+class FreeLogicalRowIdPage extends PageHeader 
+{
+    /** The offset for the number of free pages */
+    private static final short O_COUNT = PageHeader.SIZE; // short count
+    
+    /** Offset of the number of free row Ids */
+    static final short O_FREE = O_COUNT + Magic.SZ_SHORT;
+    
+    /** The number of elements by page */
+    static final short ELEMS_PER_PAGE = ( RecordFile.BLOCK_SIZE - O_FREE ) / PhysicalRowId.SIZE;
+
+    /** */
+    final PhysicalRowId[] slots = new PhysicalRowId[ELEMS_PER_PAGE];
+
+    
+    /**
+     * Constructs a data page view from the indicated block.
+     */
+    FreeLogicalRowIdPage( BlockIo blockIo ) 
+    {
+        super( blockIo );
+    }
+    
+
+    /**
+     * Factory method to create or return a data page for the indicated block.
+     */
+    static FreeLogicalRowIdPage getFreeLogicalRowIdPageView( BlockIo blockIo ) 
+    {
+        BlockView view = blockIo.getView();
+        
+        if ( ( view != null ) && ( view instanceof FreeLogicalRowIdPage ) )
+        {
+            return ( FreeLogicalRowIdPage ) view;
+        }
+        else
+        {
+            return new FreeLogicalRowIdPage( blockIo );
+        }
+    }
+
+    
+    /** 
+     * @return the number of free rowids 
+     */
+    short getCount() 
+    {
+        return blockIo.readShort( O_COUNT );
+    }
+    
+
+    /** 
+     * Sets the number of free rowids 
+     */
+    private void setCount( short i ) 
+    {
+        blockIo.writeShort( O_COUNT, i );
+    }
+    
+
+    /** 
+     * Frees a slot 
+     */
+    void free( int slot ) 
+    {
+        get( slot ).setBlock( 0 );
+        setCount( (short) ( getCount() - 1 ) );
+    }
+    
+
+    /** 
+     * Allocates a slot 
+     */
+    PhysicalRowId alloc( int slot ) 
+    {
+        setCount( (short) ( getCount() + 1 ) );
+        get( slot ).setBlock( -1 );
+        
+        return get( slot );
+    }
+    
+
+    /** 
+     * Returns true if a slot is allocated 
+     */
+    private boolean isAllocated( int slot ) 
+    {
+        return get( slot ).getBlock() > 0;
+    }
+    
+
+    /** 
+     * Returns true if a slot is free 
+     */
+    private boolean isFree( int slot ) 
+    {
+        return !isAllocated(slot);
+    }
+
+
+    /** 
+     * Returns the value of the indicated slot 
+     */
+    PhysicalRowId get( int slot ) 
+    {
+        if ( slots[slot] == null )
+        {
+            slots[slot] = new PhysicalRowId( blockIo, slotToOffset( slot ) );
+        }
+        
+        return slots[slot];
+    }
+    
+
+    /** 
+     * Converts slot to offset 
+     */
+    private short slotToOffset( int slot ) 
+    {
+        return (short) ( O_FREE + ( slot * PhysicalRowId.SIZE ) );
+    }
+    
+
+    /**
+     *  Returns first free slot, -1 if no slots are available
+     */
+    int getFirstFree() 
+    {
+        for ( int i = 0; i < ELEMS_PER_PAGE; i++ ) 
+        {
+            if ( isFree( i ) )
+            {
+                return i;
+            }
+        }
+        
+        return -1;
+    }
+    
+
+    /**
+     * @return The first allocated slot, -1 if no slots are available.
+     */
+    int getFirstAllocated() 
+    {
+        for ( int i = 0; i < ELEMS_PER_PAGE; i++ ) 
+        {
+            if ( isAllocated( i ) )
+            {
+                return i;
+            }
+        }
+        
+        return -1;
+    }
+    
+    
+    /**
+     * {@inheritDoc}
+     */
+    public String toString() 
+    {
+        StringBuilder sb = new StringBuilder();
+        
+        sb.append( "FreeLogRowIdPage ( " );
+        
+        // The blockIO
+        sb.append( super.toString() ).append( ", " );
+        
+        // The first rowId
+        sb.append( "count: " ).append( getCount() );
+        
+        // Dump the Physical row id
+        for ( int i = 0; i < ELEMS_PER_PAGE; i++ )
+        {
+            if ( slots[i] != null )
+            {
+                sb.append( ", [" ).append( i ).append( "]=<" ).append( slots[i].getBlock() ).append( ", " ).append( slots[i].getOffset() ).append( ">" );
+            }
+        }
+
+        sb.append( ")" );
+
+        return sb.toString();
+    }
+}

Added: directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FreeLogicalRowIdPageManager.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FreeLogicalRowIdPageManager.java?rev=1239581&view=auto
==============================================================================
--- directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FreeLogicalRowIdPageManager.java (added)
+++ directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FreeLogicalRowIdPageManager.java Thu Feb  2 12:38:39 2012
@@ -0,0 +1,171 @@
+/**
+ * JDBM LICENSE v1.00
+ *
+ * Redistribution and use of this software and associated documentation
+ * ("Software"), with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain copyright
+ *    statements and notices.  Redistributions must also contain a
+ *    copy of this document.
+ *
+ * 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 name "JDBM" must not be used to endorse or promote
+ *    products derived from this Software without prior written
+ *    permission of Cees de Groot.  For written permission,
+ *    please contact cg@cdegroot.com.
+ *
+ * 4. Products derived from this Software may not be called "JDBM"
+ *    nor may "JDBM" appear in their names without prior written
+ *    permission of Cees de Groot. 
+ *
+ * 5. Due credit should be given to the JDBM Project
+ *    (http://jdbm.sourceforge.net/).
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
+ * ``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
+ * CEES DE GROOT OR ANY 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.
+ *
+ * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
+ * Contributions are Copyright (C) 2000 by their associated contributors.
+ *
+ * $Id: FreeLogicalRowIdPageManager.java,v 1.1 2000/05/06 00:00:31 boisvert Exp $
+ */
+
+package jdbm.recman;
+
+import java.io.IOException;
+
+/**
+ *  This class manages free Logical rowid pages and provides methods
+ *  to free and allocate Logical rowids on a high level.
+ */
+final class FreeLogicalRowIdPageManager 
+{
+    /** our record recordFile */
+    private RecordFile recordFile;
+    
+    /** our page manager */
+    private PageManager pageManager;
+
+    /**
+     *  Creates a new instance using the indicated record file and
+     *  page manager.
+     */
+    FreeLogicalRowIdPageManager( PageManager pageManager) throws IOException 
+    {
+        this.pageManager = pageManager;
+        this.recordFile = pageManager.getRecordFile();
+    }
+    
+
+    /**
+     *  Returns a free Logical rowid, or null if nothing was found.
+     */
+    Location get() throws IOException 
+    {
+        // Loop through the free Logical rowid list until we find
+        // the first rowid.
+        // Create a cursor to browse the pages
+        PageCursor cursor = new PageCursor( pageManager, Magic.FREELOGIDS_PAGE );
+        
+        // Loop on the pages now
+        while ( cursor.next() != 0 ) 
+        {
+            // Get the blockIo associated with the blockId
+            BlockIo blockIo = recordFile.get( cursor.getBlockId() );
+            FreeLogicalRowIdPage fp = FreeLogicalRowIdPage.getFreeLogicalRowIdPageView( blockIo );
+            
+            // Get the first allocated FreeLogicalRowId
+            int slot = fp.getFirstAllocated();
+            
+            if ( slot != -1 ) 
+            {
+                // got one!
+                Location location = new Location( fp.get( slot ) );
+                
+                // Remove the block from the page
+                fp.free( slot );
+                
+                boolean hasMore = fp.getCount() != 0;
+                
+                // Upate the recordFile
+                recordFile.release( cursor.getBlockId(), hasMore );
+
+                if ( !hasMore ) 
+                {
+                    // page became empty - free it
+                    pageManager.free( Magic.FREELOGIDS_PAGE, cursor.getBlockId() );
+                }
+                
+                return location;
+            }
+            else 
+            {
+                // no luck, go to next page
+                recordFile.release( cursor.getBlockId(), false );
+            }
+        }
+        
+        return null;
+    }
+
+    
+    /**
+     *  Puts the indicated rowid on the free list
+     *  
+     *  @param rowId The Location where we will store the rowId
+     */
+    void put( Location rowId ) throws IOException 
+    {
+        
+        PhysicalRowId free = null;
+        
+        // Create a cursor on the FREELOGIDs list
+        PageCursor curs = new PageCursor( pageManager, Magic.FREELOGIDS_PAGE );
+        long freePage = 0;
+        
+        // Loop on all the list
+        while ( curs.next() != 0 )
+        {
+            freePage = curs.getBlockId();
+            BlockIo curBlockIo = recordFile.get( freePage );
+            FreeLogicalRowIdPage fp = FreeLogicalRowIdPage.getFreeLogicalRowIdPageView( curBlockIo );
+            int slot = fp.getFirstFree();
+            
+            if ( slot != -1 ) 
+            {
+                free = fp.alloc(slot);
+                break;
+            }
+            
+            recordFile.release( curBlockIo );
+        }
+        
+        if ( free == null ) 
+        {
+            // No more space on the free list, add a page.
+            freePage = pageManager.allocate( Magic.FREELOGIDS_PAGE );
+            BlockIo curBlockIo = recordFile.get( freePage );
+            FreeLogicalRowIdPage fp = FreeLogicalRowIdPage.getFreeLogicalRowIdPageView( curBlockIo );
+            free = fp.alloc( 0 );
+        }
+        
+        free.setBlock( rowId.getBlock() );
+        free.setOffset( rowId.getOffset() );
+        recordFile.release( freePage, true );
+    }
+}
\ No newline at end of file

Added: directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FreePhysicalRowId.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FreePhysicalRowId.java?rev=1239581&view=auto
==============================================================================
--- directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FreePhysicalRowId.java (added)
+++ directory/apacheds/trunk/jdbm2/src/main/java/jdbm/recman/FreePhysicalRowId.java Thu Feb  2 12:38:39 2012
@@ -0,0 +1,83 @@
+/**
+ * JDBM LICENSE v1.00
+ *
+ * Redistribution and use of this software and associated documentation
+ * ("Software"), with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain copyright
+ *    statements and notices.  Redistributions must also contain a
+ *    copy of this document.
+ *
+ * 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 name "JDBM" must not be used to endorse or promote
+ *    products derived from this Software without prior written
+ *    permission of Cees de Groot.  For written permission,
+ *    please contact cg@cdegroot.com.
+ *
+ * 4. Products derived from this Software may not be called "JDBM"
+ *    nor may "JDBM" appear in their names without prior written
+ *    permission of Cees de Groot. 
+ *
+ * 5. Due credit should be given to the JDBM Project
+ *    (http://jdbm.sourceforge.net/).
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
+ * ``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
+ * CEES DE GROOT OR ANY 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.
+ *
+ * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
+ * Contributions are Copyright (C) 2000 by their associated contributors.
+ *
+ * $Id: FreePhysicalRowId.java,v 1.1 2000/05/06 00:00:31 boisvert Exp $
+ */
+package jdbm.recman;
+
+
+/**
+ * This class extends the physical rowid with a size value to indicate
+ * the size of a free rowid on the free rowid list.
+ */
+final class FreePhysicalRowId extends PhysicalRowId 
+{
+    // offsets
+    private static final short O_SIZE = PhysicalRowId.SIZE; // int size
+    static final short SIZE = O_SIZE + Magic.SZ_INT;
+
+    
+    /**
+     * Constructs a physical rowid from the indicated data starting at
+     * the indicated position.
+     */
+    FreePhysicalRowId( BlockIo block, short pos ) 
+    {
+        super( block, pos );
+    }
+    
+
+    /** Returns the size */
+    int getSize() 
+    {
+        return block.readInt( pos + O_SIZE );
+    }
+    
+
+    /** Sets the size */
+    void setSize( int value ) 
+    {
+        block.writeInt( pos + O_SIZE, value );
+    }
+}