directory-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From elecha...@apache.org
Subject svn commit: r1435064 [4/7] - in /directory/jdbm/trunk/jdbm1: ./ 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/main/...
Date Fri, 18 Jan 2013 10:10:57 GMT
Added: directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HTree.java
URL: http://svn.apache.org/viewvc/directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HTree.java?rev=1435064&view=auto
==============================================================================
--- directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HTree.java (added)
+++ directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HTree.java Fri Jan 18 10:10:55 2013
@@ -0,0 +1,187 @@
+/**
+ * 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 (C) Copyright 2000 by their associated contributors.
+ *
+ */
+
+package jdbm.htree;
+
+import jdbm.RecordManager;
+import jdbm.helper.FastIterator;
+import java.io.IOException;
+
+/**
+ *  Persistent hashtable implementation for PageManager.
+ *  Implemented as an H*Tree structure.
+ *
+ *  WARNING!  If this instance is used in a transactional context, it
+ *            *must* be discarded after a rollback.
+ *
+ *  @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
+ */
+public class HTree
+{
+
+    /**
+     * Root hash directory.
+     */
+    private HashDirectory _root;
+
+
+    /**
+     * Private constructor
+     *
+     * @param root Root hash directory.
+     */
+    private HTree( HashDirectory root ) {
+        _root = root;
+    }
+
+
+    /**
+     * Create a persistent hashtable.
+     *
+     * @param recman Record manager used for persistence.
+     */
+    public static HTree createInstance( RecordManager recman )
+        throws IOException
+    {
+        HashDirectory  root;
+        long           recid;
+
+        root = new HashDirectory( (byte) 0 );
+        recid = recman.insert( root );
+        root.setPersistenceContext( recman, recid );
+
+        return new HTree( root );
+    }
+
+
+    /**
+     * Load a persistent hashtable
+     *
+     * @param recman RecordManager used to store the persistent hashtable
+     * @param root_recid Record id of the root directory of the HTree
+     */
+    public static HTree load( RecordManager recman, long root_recid )
+        throws IOException
+    {
+        HTree tree;
+        HashDirectory root;
+
+        root = (HashDirectory) recman.fetch( root_recid );
+        root.setPersistenceContext( recman, root_recid );
+        tree = new HTree( root );
+        return tree;
+    }
+
+
+    /**
+     * Associates the specified value with the specified key.
+     *
+     * @param key key with which the specified value is to be assocated.
+     * @param value value to be associated with the specified key.
+     */
+    public synchronized void put(Object key, Object value)
+        throws IOException
+    {
+        _root.put(key, value);
+    }
+
+
+    /**
+     * Returns the value which is associated with the given key. Returns
+     * <code>null</code> if there is not association for this key.
+     *
+     * @param key key whose associated value is to be returned
+     */
+    public synchronized Object get(Object key)
+        throws IOException
+    {
+        return _root.get(key);
+    }
+
+
+    /**
+     * Remove the value which is associated with the given key.  If the
+     * key does not exist, this method simply ignores the operation.
+     *
+     * @param key key whose associated value is to be removed
+     */
+    public synchronized void remove(Object key)
+        throws IOException
+    {
+        _root.remove(key);
+    }
+
+
+    /**
+     * Returns an enumeration of the keys contained in this
+     */
+    public synchronized FastIterator keys()
+        throws IOException
+    {
+        return _root.keys();
+    }
+
+
+    /**
+     * Returns an enumeration of the values contained in this
+     */
+    public synchronized FastIterator values()
+        throws IOException
+    {
+        return _root.values();
+    }
+
+
+    /**
+     * Get the record identifier used to load this hashtable.
+     */
+    public long getRecid()
+    {
+        return _root.getRecid();
+    }
+
+}
+

Added: directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HashBucket.java
URL: http://svn.apache.org/viewvc/directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HashBucket.java?rev=1435064&view=auto
==============================================================================
--- directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HashBucket.java (added)
+++ directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HashBucket.java Fri Jan 18 10:10:55 2013
@@ -0,0 +1,332 @@
+/**
+ * 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.
+ *
+ */
+
+package jdbm.htree;
+
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.ArrayList;
+
+import jdbm.I18n;
+
+
+/**
+ * A bucket is a placeholder for multiple (key, value) pairs.  Buckets
+ * are used to store collisions (same hash value) at all levels of an
+ * H*tree.
+ *
+ * There are two types of buckets: leaf and non-leaf.
+ *
+ * Non-leaf buckets are buckets which hold collisions which happen
+ * when the H*tree is not fully expanded.   Keys in a non-leaf buckets
+ * can have different hash codes.  Non-leaf buckets are limited to an
+ * arbitrary size.  When this limit is reached, the H*tree should create
+ * a new Directory page and distribute keys of the non-leaf buckets into
+ * the newly created Directory.
+ *
+ * A leaf bucket is a bucket which contains keys which all have
+ * the same <code>hashCode()</code>.  Leaf buckets stand at the
+ * bottom of an H*tree because the hashing algorithm cannot further
+ * discriminate between different keys based on their hash code.
+ *
+ *  @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
+ */
+final class HashBucket
+    extends HashNode
+    implements Externalizable
+{
+
+    final static long serialVersionUID = 1L;
+
+    /**
+     * The maximum number of elements (key, value) a non-leaf bucket
+     * can contain.
+     */
+    public static final int OVERFLOW_SIZE = 8;
+
+    /**
+     * Depth of this bucket.
+     */
+    private int _depth;
+
+    /**
+     * Keys in this bucket.  Keys are ordered to match their respective
+     * value in <code>_values</code>.
+     */
+    private ArrayList _keys;
+
+    /**
+     * Values in this bucket.  Values are ordered to match their respective
+     * key in <code>_keys</code>.
+     */
+    private ArrayList _values;
+
+
+    /**
+     * Public constructor for serialization.
+     */
+    public HashBucket()
+    {
+        // empty
+    }
+
+
+    /**
+     * Construct a bucket with a given depth level.  Depth level is the
+     * number of <code>HashDirectory</code> above this bucket.
+     */
+    public HashBucket( int level )
+    {
+        if ( level > HashDirectory.MAX_DEPTH + 1 )
+        {
+            throw new IllegalArgumentException( I18n.err( I18n.ERR_534, level ) );
+        }
+        _depth = level;
+        _keys = new ArrayList( OVERFLOW_SIZE );
+        _values = new ArrayList( OVERFLOW_SIZE );
+    }
+
+
+    /**
+     * Returns the number of elements contained in this bucket.
+     */
+    public int getElementCount()
+    {
+        return _keys.size();
+    }
+
+
+    /**
+     * Returns whether or not this bucket is a "leaf bucket".
+     */
+    public boolean isLeaf()
+    {
+        return ( _depth > HashDirectory.MAX_DEPTH );
+    }
+
+
+    /**
+     * Returns true if bucket can accept at least one more element.
+     */
+    public boolean hasRoom()
+    {
+        if ( isLeaf() )
+        {
+            return true; // leaf buckets are never full
+        }
+        else
+        {
+            // non-leaf bucket
+            return ( _keys.size() < OVERFLOW_SIZE );
+        }
+    }
+
+
+    /**
+     * Add an element (key, value) to this bucket.  If an existing element
+     * has the same key, it is replaced silently.
+     *
+     * @return Object which was previously associated with the given key
+     *          or <code>null</code> if no association existed.
+     */
+    public Object addElement( Object key, Object value )
+    {
+        int existing = _keys.indexOf( key );
+        if ( existing != -1 )
+        {
+            // replace existing element
+            Object before = _values.get( existing );
+            _values.set( existing, value );
+            return before;
+        }
+        else
+        {
+            // add new (key, value) pair
+            _keys.add( key );
+            _values.add( value );
+            return null;
+        }
+    }
+
+
+    /**
+     * Remove an element, given a specific key.
+     *
+     * @param key Key of the element to remove
+     *
+     * @return Removed element value, or <code>null</code> if not found
+     */
+    public Object removeElement( Object key )
+    {
+        int existing = _keys.indexOf( key );
+        if ( existing != -1 )
+        {
+            Object obj = _values.get( existing );
+            _keys.remove( existing );
+            _values.remove( existing );
+            return obj;
+        }
+        else
+        {
+            // not found
+            return null;
+        }
+    }
+
+
+    /**
+     * Returns the value associated with a given key.  If the given key
+     * is not found in this bucket, returns <code>null</code>.
+     */
+    public Object getValue( Object key )
+    {
+        int existing = _keys.indexOf( key );
+        if ( existing != -1 )
+        {
+            return _values.get( existing );
+        }
+        else
+        {
+            // key not found
+            return null;
+        }
+    }
+
+
+    /**
+     * Obtain keys contained in this buckets.  Keys are ordered to match
+     * their values, which be be obtained by calling <code>getValues()</code>.
+     *
+     * As an optimization, the Vector returned is the instance member
+     * of this class.  Please don't modify outside the scope of this class.
+     */
+    ArrayList getKeys()
+    {
+        return this._keys;
+    }
+
+
+    /**
+     * Obtain values contained in this buckets.  Values are ordered to match
+     * their keys, which be be obtained by calling <code>getKeys()</code>.
+     *
+     * As an optimization, the Vector returned is the instance member
+     * of this class.  Please don't modify outside the scope of this class.
+     */
+    ArrayList getValues()
+    {
+        return this._values;
+    }
+
+
+    /**
+     * Implement Externalizable interface.
+     */
+    public void writeExternal( ObjectOutput out )
+        throws IOException
+    {
+        out.writeInt( _depth );
+
+        int entries = _keys.size();
+        out.writeInt( entries );
+
+        // write keys
+        for ( int i = 0; i < entries; i++ )
+        {
+            out.writeObject( _keys.get( i ) );
+        }
+        // write values
+        for ( int i = 0; i < entries; i++ )
+        {
+            out.writeObject( _values.get( i ) );
+        }
+    }
+
+
+    /**
+     * Implement Externalizable interface.
+     */
+    public void readExternal( ObjectInput in )
+        throws IOException, ClassNotFoundException
+    {
+        _depth = in.readInt();
+
+        int entries = in.readInt();
+
+        // prepare array lists
+        int size = Math.max( entries, OVERFLOW_SIZE );
+        _keys = new ArrayList( size );
+        _values = new ArrayList( size );
+
+        // read keys
+        for ( int i = 0; i < entries; i++ )
+        {
+            _keys.add( in.readObject() );
+        }
+        // read values
+        for ( int i = 0; i < entries; i++ )
+        {
+            _values.add( in.readObject() );
+        }
+    }
+
+
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        buf.append( "HashBucket {depth=" );
+        buf.append( _depth );
+        buf.append( ", keys=" );
+        buf.append( _keys );
+        buf.append( ", values=" );
+        buf.append( _values );
+        buf.append( "}" );
+        return buf.toString();
+    }
+}

Added: directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HashDirectory.java
URL: http://svn.apache.org/viewvc/directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HashDirectory.java?rev=1435064&view=auto
==============================================================================
--- directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HashDirectory.java (added)
+++ directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HashDirectory.java Fri Jan 18 10:10:55 2013
@@ -0,0 +1,602 @@
+/**
+ * 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.
+ *
+ */
+
+package jdbm.htree;
+
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import jdbm.I18n;
+import jdbm.RecordManager;
+import jdbm.helper.FastIterator;
+import jdbm.helper.IterationException;
+
+
+/**
+ *  Hashtable directory page.
+ *
+ *  @author <a href="mailto:boisvert@exoffice.com">Alex Boisvert</a>
+ */
+final class HashDirectory
+    extends HashNode
+    implements Externalizable
+{
+
+    static final long serialVersionUID = 1L;
+
+    /**
+     * Maximum number of children in a directory.
+     *
+     * (Must be a power of 2 -- if you update this value, you must also
+     *  update BIT_SIZE and MAX_DEPTH.)
+     */
+    static final int MAX_CHILDREN = 256;
+
+    /**
+     * Number of significant bits per directory level.
+     */
+    static final int BIT_SIZE = 8; // log2(256) = 8
+
+    /**
+     * Maximum number of levels (zero-based)
+     *
+     * (4 * 8 bits = 32 bits, which is the size of an "int", and as
+     *  you know, hashcodes in Java are "ints")
+     */
+    static final int MAX_DEPTH = 3; // 4 levels
+
+    /**
+     * Record ids of children pages.
+     */
+    private long[] _children;
+
+    /**
+     * Depth of this directory page, zero-based
+     */
+    private byte _depth;
+
+    /**
+     * PageManager used to persist changes in directory and buckets
+     */
+    private transient RecordManager _recman;
+
+    /**
+     * This directory's record ID in the PageManager.  (transient)
+     */
+    private transient long _recid;
+
+
+    /**
+     * Public constructor used by serialization
+     */
+    public HashDirectory()
+    {
+        // empty
+    }
+
+
+    /**
+     * Construct a HashDirectory
+     *
+     * @param depth Depth of this directory page.
+     */
+    HashDirectory( byte depth )
+    {
+        _depth = depth;
+        _children = new long[MAX_CHILDREN];
+    }
+
+
+    /**
+     * Sets persistence context.  This method must be called before any
+     * persistence-related operation.
+     *
+     * @param recman RecordManager which stores this directory
+     * @param recid Record id of this directory.
+     */
+    void setPersistenceContext( RecordManager recman, long recid )
+    {
+        this._recman = recman;
+        this._recid = recid;
+    }
+
+
+    /**
+     * Get the record identifier used to load this hashtable.
+     */
+    long getRecid()
+    {
+        return _recid;
+    }
+
+
+    /**
+     * Returns whether or not this directory is empty.  A directory
+     * is empty when it no longer contains buckets or sub-directories.
+     */
+    boolean isEmpty()
+    {
+        for ( int i = 0; i < _children.length; i++ )
+        {
+            if ( _children[i] != 0 )
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    /**
+     * Returns the value which is associated with the given key. Returns
+     * <code>null</code> if there is not association for this key.
+     *
+     * @param key key whose associated value is to be returned
+     */
+    Object get( Object key )
+        throws IOException
+    {
+        int hash = hashCode( key );
+        long child_recid = _children[hash];
+        if ( child_recid == 0 )
+        {
+            // not bucket/page --> not found
+            return null;
+        }
+        else
+        {
+            HashNode node = ( HashNode ) _recman.fetch( child_recid );
+            // System.out.println("HashDirectory.get() child is : "+node);
+
+            if ( node instanceof HashDirectory )
+            {
+                // recurse into next directory level
+                HashDirectory dir = ( HashDirectory ) node;
+                dir.setPersistenceContext( _recman, child_recid );
+                return dir.get( key );
+            }
+            else
+            {
+                // node is a bucket
+                HashBucket bucket = ( HashBucket ) node;
+                return bucket.getValue( key );
+            }
+        }
+    }
+
+
+    /**
+     * Associates the specified value with the specified key.
+     *
+     * @param key key with which the specified value is to be assocated.
+     * @param value value to be associated with the specified key.
+     * @return object which was previously associated with the given key,
+     *          or <code>null</code> if no association existed.
+     */
+    Object put( Object key, Object value )
+        throws IOException
+    {
+        if ( value == null )
+        {
+            return remove( key );
+        }
+        int hash = hashCode( key );
+        long child_recid = _children[hash];
+        if ( child_recid == 0 )
+        {
+            // no bucket/page here yet, let's create a bucket
+            HashBucket bucket = new HashBucket( _depth + 1 );
+
+            // insert (key,value) pair in bucket
+            Object existing = bucket.addElement( key, value );
+
+            long b_recid = _recman.insert( bucket );
+            _children[hash] = b_recid;
+
+            _recman.update( _recid, this );
+
+            // System.out.println("Added: "+bucket);
+            return existing;
+        }
+        else
+        {
+            HashNode node = ( HashNode ) _recman.fetch( child_recid );
+
+            if ( node instanceof HashDirectory )
+            {
+                // recursive insert in next directory level
+                HashDirectory dir = ( HashDirectory ) node;
+                dir.setPersistenceContext( _recman, child_recid );
+                return dir.put( key, value );
+            }
+            else
+            {
+                // node is a bucket
+                HashBucket bucket = ( HashBucket ) node;
+                if ( bucket.hasRoom() )
+                {
+                    Object existing = bucket.addElement( key, value );
+                    _recman.update( child_recid, bucket );
+                    // System.out.println("Added: "+bucket);
+                    return existing;
+                }
+                else
+                {
+                    // overflow, so create a new directory
+                    if ( _depth == MAX_DEPTH )
+                    {
+                        throw new RuntimeException( I18n.err( I18n.ERR_535, _depth ) );
+                    }
+                    HashDirectory dir = new HashDirectory( ( byte ) ( _depth + 1 ) );
+                    long dir_recid = _recman.insert( dir );
+                    dir.setPersistenceContext( _recman, dir_recid );
+
+                    _children[hash] = dir_recid;
+                    _recman.update( _recid, this );
+
+                    // discard overflown bucket
+                    _recman.delete( child_recid );
+
+                    // migrate existing bucket elements
+                    ArrayList keys = bucket.getKeys();
+                    ArrayList values = bucket.getValues();
+                    int entries = keys.size();
+                    for ( int i = 0; i < entries; i++ )
+                    {
+                        dir.put( keys.get( i ), values.get( i ) );
+                    }
+
+                    // (finally!) insert new element
+                    return dir.put( key, value );
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Remove the value which is associated with the given key.  If the
+     * key does not exist, this method simply ignores the operation.
+     *
+     * @param key key whose associated value is to be removed
+     * @return object which was associated with the given key, or
+     *          <code>null</code> if no association existed with given key.
+     */
+    Object remove( Object key ) throws IOException
+    {
+        int hash = hashCode( key );
+        long child_recid = _children[hash];
+        if ( child_recid == 0 )
+        {
+            // not bucket/page --> not found
+            return null;
+        }
+        else
+        {
+            HashNode node = ( HashNode ) _recman.fetch( child_recid );
+            // System.out.println("HashDirectory.remove() child is : "+node);
+
+            if ( node instanceof HashDirectory )
+            {
+                // recurse into next directory level
+                HashDirectory dir = ( HashDirectory ) node;
+                dir.setPersistenceContext( _recman, child_recid );
+                Object existing = dir.remove( key );
+                if ( existing != null && dir.isEmpty() )
+                {
+                    // delete empty directory
+                    _recman.delete( child_recid );
+                    _children[hash] = 0;
+                    _recman.update( _recid, this );
+                }
+                return existing;
+            }
+            else
+            {
+                // node is a bucket
+                HashBucket bucket = ( HashBucket ) node;
+                Object existing = bucket.removeElement( key );
+                if ( existing != null )
+                {
+                    if ( bucket.getElementCount() >= 1 )
+                    {
+                        _recman.update( child_recid, bucket );
+                    }
+                    else
+                    {
+                        // delete bucket, it's empty
+                        _recman.delete( child_recid );
+                        _children[hash] = 0;
+                        _recman.update( _recid, this );
+                    }
+                }
+                return existing;
+            }
+        }
+    }
+
+
+    /**
+     * Calculates the hashcode of a key, based on the current directory
+     * depth.
+     */
+    private int hashCode( Object key )
+    {
+        int hashMask = hashMask();
+        int hash = key.hashCode();
+        hash = hash & hashMask;
+        hash = hash >>> ( ( MAX_DEPTH - _depth ) * BIT_SIZE );
+        hash = hash % MAX_CHILDREN;
+        /*
+        System.out.println("HashDirectory.hashCode() is: 0x"
+                           +Integer.toHexString(hash)
+                           +" for object hashCode() 0x"
+                           +Integer.toHexString(key.hashCode()));
+        */
+        return hash;
+    }
+
+
+    /**
+     * Calculates the hashmask of this directory.  The hashmask is the
+     * bit mask applied to a hashcode to retain only bits that are
+     * relevant to this directory level.
+     */
+    int hashMask()
+    {
+        int bits = MAX_CHILDREN - 1;
+        int hashMask = bits << ( ( MAX_DEPTH - _depth ) * BIT_SIZE );
+        /*
+        System.out.println("HashDirectory.hashMask() is: 0x"
+                           +Integer.toHexString(hashMask));
+        */
+        return hashMask;
+    }
+
+
+    /**
+     * Returns an enumeration of the keys contained in this
+     */
+    FastIterator keys()
+        throws IOException
+    {
+        return new HDIterator( true );
+    }
+
+
+    /**
+     * Returns an enumeration of the values contained in this
+     */
+    FastIterator values()
+        throws IOException
+    {
+        return new HDIterator( false );
+    }
+
+
+    /**
+     * Implement Externalizable interface
+     */
+    public void writeExternal( ObjectOutput out )
+        throws IOException
+    {
+        out.writeByte( _depth );
+        out.writeObject( _children );
+    }
+
+
+    /**
+     * Implement Externalizable interface
+     */
+    public synchronized void readExternal( ObjectInput in )
+        throws IOException, ClassNotFoundException
+    {
+        _depth = in.readByte();
+        _children = ( long[] ) in.readObject();
+    }
+
+    ////////////////////////////////////////////////////////////////////////
+    // INNER CLASS
+    ////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Utility class to enumerate keys/values in a HTree
+     */
+    public class HDIterator
+        extends FastIterator
+    {
+
+        /**
+         * True if we're iterating on keys, False if enumerating on values.
+         */
+        private boolean _iterateKeys;
+
+        /**
+         * Stacks of directories & last enumerated child position
+         */
+        private ArrayList _dirStack;
+        private ArrayList _childStack;
+
+        /**
+         * Current HashDirectory in the hierarchy
+         */
+        private HashDirectory _dir;
+
+        /**
+         * Current child position
+         */
+        private int _child;
+
+        /**
+         * Current bucket iterator
+         */
+        private Iterator _iter;
+
+
+        /**
+         * Construct an iterator on this directory.
+         *
+         * @param iterateKeys True if iteration supplies keys, False
+         *                  if iterateKeys supplies values.
+         */
+        HDIterator( boolean iterateKeys )
+            throws IOException
+        {
+            _dirStack = new ArrayList();
+            _childStack = new ArrayList();
+            _dir = HashDirectory.this;
+            _child = -1;
+            _iterateKeys = iterateKeys;
+
+            prepareNext();
+        }
+
+
+        /**
+         * Returns the next object.
+         */
+        public Object next()
+        {
+            Object next = null;
+            if ( _iter != null && _iter.hasNext() )
+            {
+                next = _iter.next();
+            }
+            else
+            {
+                try
+                {
+                    prepareNext();
+                }
+                catch ( IOException except )
+                {
+                    throw new IterationException( except );
+                }
+                if ( _iter != null && _iter.hasNext() )
+                {
+                    return next();
+                }
+            }
+            return next;
+        }
+
+
+        /**
+         * Prepare internal state so we can answer <code>hasMoreElements</code>
+         *
+         * Actually, this code prepares an Enumeration on the next
+         * Bucket to enumerate.   If no following bucket is found,
+         * the next Enumeration is set to <code>null</code>.
+         */
+        private void prepareNext() throws IOException
+        {
+            long child_recid = 0;
+
+            // find next bucket/directory to enumerate
+            do
+            {
+                _child++;
+                if ( _child >= MAX_CHILDREN )
+                {
+
+                    if ( _dirStack.isEmpty() )
+                    {
+                        // no more directory in the stack, we're finished
+                        return;
+                    }
+
+                    // try next page
+                    _dir = ( HashDirectory ) _dirStack.remove( _dirStack.size() - 1 );
+                    _child = ( ( Integer ) _childStack.remove( _childStack.size() - 1 ) ).intValue();
+                    continue;
+                }
+                child_recid = _dir._children[_child];
+            }
+            while ( child_recid == 0 );
+
+            if ( child_recid == 0 )
+            {
+                throw new Error( "child_recid cannot be 0" );
+            }
+
+            HashNode node = ( HashNode ) _recman.fetch( child_recid );
+            // System.out.println("HDEnumeration.get() child is : "+node);
+
+            if ( node instanceof HashDirectory )
+            {
+                // save current position
+                _dirStack.add( _dir );
+                _childStack.add( Integer.valueOf( _child ) );
+
+                _dir = ( HashDirectory ) node;
+                _child = -1;
+
+                // recurse into
+                _dir.setPersistenceContext( _recman, child_recid );
+                prepareNext();
+            }
+            else
+            {
+                // node is a bucket
+                HashBucket bucket = ( HashBucket ) node;
+                if ( _iterateKeys )
+                {
+                    _iter = bucket.getKeys().iterator();
+                }
+                else
+                {
+                    _iter = bucket.getValues().iterator();
+                }
+            }
+        }
+    }
+
+}

Added: directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HashNode.java
URL: http://svn.apache.org/viewvc/directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HashNode.java?rev=1435064&view=auto
==============================================================================
--- directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HashNode.java (added)
+++ directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/HashNode.java Fri Jan 18 10:10:55 2013
@@ -0,0 +1,56 @@
+/**

+ * 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.

+ *

+ */

+

+package jdbm.htree;

+

+import java.io.Serializable;

+

+/**
 *  Abstract class for Hashtable directory nodes
 *
 *  @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
 */
class HashNode implements Serializable {

+

+    // Empty, there's no common functionality.  We use this abstract

+    // class for typing only.

+

+}


Added: directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/package.html
URL: http://svn.apache.org/viewvc/directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/package.html?rev=1435064&view=auto
==============================================================================
--- directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/package.html (added)
+++ directory/jdbm/trunk/jdbm1/src/main/java/jdbm/htree/package.html Fri Jan 18 10:10:55 2013
@@ -0,0 +1,12 @@
+<!-- $Id: package.html,v 1.1 2002/05/31 06:33:20 boisvert Exp $ -->
+<html>
+  <body>
+    <p>HTree (scalable persistent hashtable) data structure implementation.</p>
+
+    <dl>
+      <dt><b>Version: </b></dt><dd>$Revision: 1.1 $ $Date: 2002/05/31 06:33:20 $</dd>
+      <dt><b>Author: </b></dt><dd><a href="mailto:boisvert@intalio.com">Alex Boisvert</a></dd>
+    </dl>
+
+  </body>
+</html>

Added: directory/jdbm/trunk/jdbm1/src/main/java/jdbm/package.html
URL: http://svn.apache.org/viewvc/directory/jdbm/trunk/jdbm1/src/main/java/jdbm/package.html?rev=1435064&view=auto
==============================================================================
--- directory/jdbm/trunk/jdbm1/src/main/java/jdbm/package.html (added)
+++ directory/jdbm/trunk/jdbm1/src/main/java/jdbm/package.html Fri Jan 18 10:10:55 2013
@@ -0,0 +1,21 @@
+<!-- $Id: package.html,v 1.1 2001/05/19 16:01:32 boisvert Exp $ -->
+<html>
+  <body>
+    <p>Simplified public API corresponding to GDBM APIs.</p>
+    
+    <h1><b>JDBM</b></h1>
+
+    <p>JDBM is a simple transactional persistent engine for Java.
+       It aims to be for Java what GDBM is for C/C++, Perl, Python, etc: 
+       a fast, simple persistence engine. </p>
+
+    <p>You can use it to store a mix of hashtables and blobs, and all updates are 
+       done in a transactionally safe manner.</p>
+
+    <dl>
+      <dt><b>Version: </b></dt><dd>$Revision: 1.1 $ $Date: 2001/05/19 16:01:32 $</dd>
+      <dt><b>Author: </b></dt><dd><a href="mailto:boisvert@intalio.com">Alex Boisvert</a></dd>
+    </dl>
+
+  </body>
+</html>

Added: directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/BaseRecordManager.java
URL: http://svn.apache.org/viewvc/directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/BaseRecordManager.java?rev=1435064&view=auto
==============================================================================
--- directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/BaseRecordManager.java (added)
+++ directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/BaseRecordManager.java Fri Jan 18 10:10:55 2013
@@ -0,0 +1,481 @@
+/**
+ * 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 jdbm.I18n;
+import jdbm.RecordManager;
+import jdbm.helper.DefaultSerializer;
+import jdbm.helper.Serializer;
+
+
+/**
+ *  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
+{
+
+    /** 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;
+
+
+    /**
+     * 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 );
+    }
+
+
+    /**
+     * Get the underlying Transaction Manager
+     */
+    public synchronized 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 synchronized void disableTransactions()
+    {
+        checkIfClosed();
+        recordFile.disableTransactions();
+    }
+
+
+    /**
+     * Closes the record manager.
+     *
+     * @throws IOException when one of the underlying I/O operations fails.
+     */
+    public synchronized 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 synchronized 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();
+
+        if ( DEBUG )
+        {
+            System.out.println( "BaseRecordManager.insert() recid " + recid + " length " + 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 synchronized void delete( long recid ) throws IOException
+    {
+        checkIfClosed();
+
+        if ( recid <= 0 )
+        {
+            throw new IllegalArgumentException( I18n.err( I18n.ERR_536, recid ) );
+        }
+
+        if ( DEBUG )
+        {
+            System.out.println( "BaseRecordManager.delete() recid " + recid );
+        }
+
+        Location logRowId = new Location( recid );
+        Location physRowId = logMgr.fetch( logRowId );
+        physMgr.delete( physRowId );
+        logMgr.delete( logRowId );
+    }
+
+
+    /**
+     * 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 synchronized void update( long recid, Object obj, Serializer serializer ) throws IOException
+    {
+        checkIfClosed();
+
+        if ( recid <= 0 )
+        {
+            throw new IllegalArgumentException( I18n.err( I18n.ERR_536, recid ) );
+        }
+
+        Location logRecid = new Location( recid );
+        Location physRecid = logMgr.fetch( logRecid );
+
+        byte[] data = serializer.serialize( obj );
+
+        if ( DEBUG )
+        {
+            System.out.println( "BaseRecordManager.update() recid " + recid + " length " + data.length );
+        }
+
+        Location newRecid = physMgr.update( physRecid, data, 0, data.length );
+
+        if ( !newRecid.equals( physRecid ) )
+        {
+            logMgr.update( logRecid, newRecid );
+        }
+    }
+
+
+    /**
+     * 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 synchronized Object fetch( long recid, Serializer serializer )
+        throws IOException
+    {
+        byte[] data;
+
+        checkIfClosed();
+
+        if ( recid <= 0 )
+        {
+            throw new IllegalArgumentException( I18n.err( I18n.ERR_536, recid ) );
+        }
+
+        data = physMgr.fetch( logMgr.fetch( new Location( recid ) ) );
+
+        if ( DEBUG )
+        {
+            System.out.println( "BaseRecordManager.fetch() recid " + recid + " length " + data.length );
+        }
+        return serializer.deserialize( data );
+    }
+
+
+    /**
+     * 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 synchronized long getRoot( int id ) throws IOException
+    {
+        checkIfClosed();
+
+        return pageMgr.getFileHeader().getRoot( id );
+    }
+
+
+    /**
+     *  Sets the indicated root rowid.
+     *
+     *  @see #getRootCount
+     */
+    public synchronized 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 synchronized void commit()
+        throws IOException
+    {
+        checkIfClosed();
+
+        pageMgr.commit();
+    }
+
+
+    /**
+     * Rollback (cancel) all changes since beginning of transaction.
+     */
+    public synchronized 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 ) );
+        }
+    }
+}

Added: directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/BlockIo.java
URL: http://svn.apache.org/viewvc/directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/BlockIo.java?rev=1435064&view=auto
==============================================================================
--- directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/BlockIo.java (added)
+++ directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/BlockIo.java Fri Jan 18 10:10:55 2013
@@ -0,0 +1,365 @@
+/**
+ * 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 jdbm.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 transient byte[] data;
+
+    private transient BlockView view = null;
+
+    /** A flag set when this block has been modified */
+    private transient boolean dirty = false;
+
+    /** The number of pending transaction on this block */
+    private transient int transactionCount = 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
+     */
+    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
+     */
+    byte[] getData()
+    {
+        return data;
+    }
+
+
+    /**
+     * Sets the block number. Should only be called by RecordFile.
+     * 
+     * @param The block identifier
+     */
+    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.
+     */
+    long getBlockId()
+    {
+        return blockId;
+    }
+
+
+    /**
+     * @return the current view of the block.
+     */
+    public BlockView getView()
+    {
+        return view;
+    }
+
+
+    /**
+     * Sets the current view of the block.
+     */
+    public void setView( BlockView view )
+    {
+        this.view = view;
+    }
+
+
+    /**
+     * Sets the dirty flag
+     */
+    void setDirty()
+    {
+        dirty = true;
+    }
+
+
+    /**
+     * Clears the dirty flag
+     */
+    void setClean()
+    {
+        dirty = false;
+    }
+
+
+    /**
+     * Returns true if the dirty flag is set.
+     */
+    boolean isDirty()
+    {
+        return dirty;
+    }
+
+
+    /**
+     * Returns true if the block is still dirty with respect to the 
+     * transaction log.
+     */
+    boolean isInTransaction()
+    {
+        return transactionCount != 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.
+     */
+    synchronized void incrementTransactionCount()
+    {
+        transactionCount++;
+
+        // @fix me ( alex )
+        setClean();
+    }
+
+
+    /**
+     * Decrements transaction count for this block, to signal that this
+     * block has been written from the log to the data recordFile.
+     */
+    synchronized void decrementTransactionCount()
+    {
+        transactionCount--;
+
+        if ( transactionCount < 0 )
+        {
+            throw new Error( I18n.err( I18n.ERR_541, getBlockId() ) );
+        }
+    }
+
+
+    /**
+     * Reads a byte from the indicated position
+     */
+    public byte readByte( int pos )
+    {
+        return data[pos];
+    }
+
+
+    /**
+     * Writes a byte to the indicated position
+     */
+    public void writeByte( int pos, byte value )
+    {
+        data[pos] = value;
+        setDirty();
+    }
+
+
+    /**
+     * Reads a short from the indicated position
+     */
+    public short readShort( int pos )
+    {
+        return ( short ) ( ( ( short ) ( data[pos + 0] & 0xff ) << 8 ) | ( ( short ) ( data[pos + 1] & 0xff ) << 0 ) );
+    }
+
+
+    /**
+     * Writes a short to the indicated position
+     */
+    public void writeShort( int pos, short value )
+    {
+        data[pos + 0] = ( byte ) ( 0xff & ( value >> 8 ) );
+        data[pos + 1] = ( byte ) ( 0xff & ( value >> 0 ) );
+        setDirty();
+    }
+
+
+    /**
+     * Reads an int from the indicated position
+     */
+    public int readInt( int pos )
+    {
+        return ( ( ( data[pos + 0] & 0xff ) << 24 ) |
+            ( ( data[pos + 1] & 0xff ) << 16 ) |
+            ( ( data[pos + 2] & 0xff ) << 8 ) | ( ( data[pos + 3] & 0xff ) << 0 ) );
+    }
+
+
+    /**
+     * Writes an int to the indicated position
+     */
+    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 ) );
+        setDirty();
+    }
+
+
+    /**
+     * Reads a long from the indicated position
+     */
+    public long readLong( int pos )
+    {
+        // Contributed by Erwin Bolwidt <ejb@klomp.org>
+        // Gives about 15% performance improvement
+        return ( ( long ) ( ( ( data[pos + 0] & 0xff ) << 24 ) |
+            ( ( data[pos + 1] & 0xff ) << 16 ) |
+            ( ( data[pos + 2] & 0xff ) << 8 ) |
+            ( ( data[pos + 3] & 0xff ) ) ) << 32 ) |
+            ( ( long ) ( ( ( data[pos + 4] & 0xff ) << 24 ) |
+                ( ( data[pos + 5] & 0xff ) << 16 ) |
+                ( ( data[pos + 6] & 0xff ) << 8 ) |
+            ( ( data[pos + 7] & 0xff ) ) ) & 0xffffffff );
+    }
+
+
+    /**
+     * Writes a long to the indicated position
+     */
+    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 ) );
+        setDirty();
+    }
+
+
+    // overrides java.lang.Object
+
+    public String toString()
+    {
+        return "BlockIO ( "
+            + blockId + ", "
+            + dirty + ", "
+            + view + " )";
+    }
+
+
+    // 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/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/BlockView.java
URL: http://svn.apache.org/viewvc/directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/BlockView.java?rev=1435064&view=auto
==============================================================================
--- directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/BlockView.java (added)
+++ directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/BlockView.java Fri Jan 18 10:10:55 2013
@@ -0,0 +1,58 @@
+/**
+ * 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/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/CacheRecordManager.java
URL: http://svn.apache.org/viewvc/directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/CacheRecordManager.java?rev=1435064&view=auto
==============================================================================
--- directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/CacheRecordManager.java (added)
+++ directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/CacheRecordManager.java Fri Jan 18 10:10:55 2013
@@ -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 java.io.IOException;
+import java.util.Enumeration;
+
+import jdbm.I18n;
+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;
+
+
+/**
+ *  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 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/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/DataPage.java
URL: http://svn.apache.org/viewvc/directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/DataPage.java?rev=1435064&view=auto
==============================================================================
--- directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/DataPage.java (added)
+++ directory/jdbm/trunk/jdbm1/src/main/java/jdbm/recman/DataPage.java Fri Jan 18 10:10:55 2013
@@ -0,0 +1,114 @@
+/**
+ * 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 jdbm.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: 8172 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 block )
+    {
+        BlockView view = block.getView();
+        if ( view != null && view instanceof DataPage )
+        {
+            return ( DataPage ) view;
+        }
+        else
+        {
+            return new DataPage( block );
+        }
+    }
+
+
+    /** Returns the first rowid's offset */
+    short getFirst()
+    {
+        return block.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 ) );
+        }
+
+        block.writeShort( O_FIRST, value );
+    }
+}



Mime
View raw message