blob: 33635612e2d9dd9af4655548b8af037d611db9ae [file] [log] [blame]
/**
* 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 2001 (C) Alex Boisvert. All Rights Reserved.
* Contributions are Copyright (C) 2001 by their associated contributors.
*
*/
package jdbm.btree;
import jdbm.btree.BTree.EmptyBrowser;
import jdbm.btree.BTree.MetaRoot;
import jdbm.helper.LRUCache;
import jdbm.helper.Serializer;
import jdbm.helper.Tuple;
import jdbm.helper.TupleBrowser;
import jdbm.helper.ActionContext;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.directory.server.i18n.I18n;
/**
* Page of a Btree.
* <p>
* The page contains a number of key-value pairs. Keys are ordered to allow
* dichotomic search.
* <p>
* If the page is a leaf page, the keys and values are user-defined and
* represent entries inserted by the user.
* <p>
* If the page is non-leaf, each key represents the greatest key in the
* underlying BPages and the values are recids pointing to the children BPages.
* The only exception is the rightmost BPage, which is considered to have an
* "infinite" key value, meaning that any insert will be to the left of this
* pseudo-key
*
* @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
*/
public class BPage<K, V> implements Serializer
{
private static final boolean DEBUG = false;
/** Version id for serialization. */
final static long serialVersionUID = 1L;
/** Parent B+Tree. */
transient BTree<K, V> btree;
/** This BPage's record ID in the PageManager. */
protected transient long recordId;
/** Flag indicating if this is a leaf BPage. */
protected boolean isLeaf;
/** Keys of children nodes */
protected K[] keys;
/** Values associated with keys. (Only valid if leaf BPage) */
protected V[] values;
/** Children pages (recids) associated with keys. (Only valid if non-leaf BPage) */
protected long[] children;
/** Index of first used item at the page */
protected int first;
/** Previous leaf BPage (only if this BPage is a leaf) */
protected long previous;
/** Next leaf BPage (only if this BPage is a leaf) */
protected long next;
public static AtomicInteger outstandingBrowsers = new AtomicInteger(0);
/**
* No-argument constructor used by serialization.
*/
public BPage()
{
// empty
}
/**
* Root page overflow constructor
*/
@SuppressWarnings("unchecked")
BPage( BTree<K, V> btree, BPage<K, V> root, BPage<K, V> overflow ) throws IOException
{
this.btree = btree;
isLeaf = false;
first = btree.pageSize - 2;
keys = (K[])new Object[btree.pageSize];
keys[btree.pageSize - 2] = overflow.getLargestKey();
keys[btree.pageSize - 1] = root.getLargestKey();
children = new long[btree.pageSize];
children[btree.pageSize - 2] = overflow.recordId;
children[btree.pageSize - 1] = root.recordId;
recordId = btree.recordManager.insert( this, this );
}
/**
* Root page (first insert) constructor.
*/
@SuppressWarnings("unchecked") // Cannot create an array of generic objects
BPage( BTree<K,V> btree, K key, V value ) throws IOException
{
this.btree = btree;
isLeaf = true;
first = btree.pageSize - 2;
keys = (K[])new Object[btree.pageSize];
keys[btree.pageSize - 2] = key;
keys[btree.pageSize - 1] = null; // I am the root BPage for now
values = (V[])new Object[btree.pageSize];
values[btree.pageSize - 2] = btree.copyValue( value );
values[btree.pageSize - 1] = null; // I am the root BPage for now
recordId = btree.recordManager.insert( this, this );
}
/**
* Overflow page constructor. Creates an empty BPage.
*/
@SuppressWarnings("unchecked") // Cannot create an array of generic objects
BPage( BTree btree, boolean isLeaf ) throws IOException
{
this.btree = btree;
this.isLeaf = isLeaf;
// page will initially be half-full
first = btree.pageSize / 2;
keys = (K[])new Object[btree.pageSize];
if ( isLeaf )
{
values = (V[])new Object[btree.pageSize];
}
else
{
children = new long[btree.pageSize];
}
recordId = btree.recordManager.insert( this, this );
}
@SuppressWarnings("unchecked") // Cannot create an array of generic objects
BPage<K,V> copyOnWrite()
{
BPage<K, V> newPage = new BPage<K,V>();
newPage.btree = this.btree;
newPage.isLeaf = this.isLeaf;
newPage.first = this.first;
newPage.previous = this.previous;
newPage.next = this.next;
newPage.keys = (K[])new Object[btree.pageSize];
newPage.values = (V[])new Object[btree.pageSize];
newPage.children = new long[btree.pageSize];
newPage.recordId = this.recordId;
if ( this.children != null )
this.copyChildren( this, 0, newPage, 0, btree.pageSize ); // this copies keys as well
if (this.values != null )
this.copyEntries( this, 0, newPage, 0, btree.pageSize ); // this copies keys as well
return newPage;
}
/**
* Get largest key under this BPage. Null is considered to be the
* greatest possible key.
*/
K getLargestKey()
{
return keys[btree.pageSize - 1];
}
/**
* @return The record ID
*/
public long getRecordId()
{
return recordId;
}
/**
* Set the recordId
*
* @param recordId The recordId
*/
public void setRecordId( long recordId )
{
this.recordId = recordId;
}
/**
* Return true if BPage is empty.
*/
boolean isEmpty()
{
if ( isLeaf )
{
return ( first == values.length - 1 );
}
else
{
return ( first == children.length - 1 );
}
}
/**
* Return true if BPage is full.
*/
boolean isFull()
{
return ( first == 0 );
}
/**
* Find the object associated with the given key.
*
* @param height Height of the current BPage (zero is leaf page)
* @param key The key
* @param context action context in case of action capable record manager
* @return TupleBrowser positionned just before the given key, or before
* next greater key if key isn't found.
*/
TupleBrowser<K, V> find( int height, K key, ActionContext context) throws IOException
{
int index = this.findChildren( key );
if ( index < 0 )
{
index = -( index + 1 );
}
BPage<K, V> child = this;
while ( !child.isLeaf )
{
// non-leaf BPage
child = child.loadBPage( child.children[index] );
index = child.findChildren( key );
if ( index < 0 )
{
index = -( index + 1 );
}
}
return new Browser( child, index, context);
}
/**
* Find first entry and return a browser positioned before it.
*@param context Action Context in case of
* @return TupleBrowser positionned just before the first entry.
*/
TupleBrowser<K, V> findFirst( ActionContext context ) throws IOException
{
if ( isLeaf )
{
return new Browser( this, first, context );
}
else
{
BPage<K, V> child = childBPage( first );
return child.findFirst( context );
}
}
/**
* Insert the given key and value.
* <p>
* Since the Btree does not support duplicate entries, the caller must
* specify whether to replace the existing value.
*
* @param height Height of the current BPage (zero is leaf page)
* @param key Insert key
* @param value Insert value
* @param replace Set to true to replace the existing value, if one exists.
* @return Insertion result containing existing value OR a BPage if the key
* was inserted and provoked a BPage overflow.
*/
InsertResult<K, V> insert( int height, K key, V value, boolean replace ) throws IOException
{
InsertResult<K, V> result;
long overflow;
int index = findChildren( key );
boolean keyExists = index < 0;
if ( index < 0 )
{
index = -( index + 1 );
}
height -= 1;
BPage<K,V> pageNewCopy = null;
if ( height == 0 )
{
pageNewCopy = btree.copyOnWrite( this );
result = new InsertResult<K, V>();
result.pageNewCopy = pageNewCopy;
// inserting on a leaf BPage
overflow = -1;
if ( DEBUG )
{
System.out.println( "Bpage.insert() Insert on leaf Bpage key=" + key + " value=" + value + " index="
+ index );
}
// This is to deal with the special case where the key already exists.
// In this case, the index will contain the key's position, but as a
// negative number
if ( keyExists )
{
// key already exists
if ( DEBUG )
{
System.out.println( "Bpage.insert() Key already exists." );
}
result.existing = values[index];
if ( replace )
{
pageNewCopy.values[index] = value;
btree.recordManager.update( recordId, pageNewCopy, this );
}
// return the existing key
return result;
}
}
else
{
// non-leaf BPage
BPage<K, V> child = childBPage( index );
result = child.insert( height, key, value, replace );
if( result.pageNewCopy != null)
{
child = result.pageNewCopy;
result.pageNewCopy = null;
}
if ( result.existing != null )
{
// return existing key, if any.
return result;
}
if ( result.overflow == null )
{
// no overflow means we're done with insertion
return result;
}
// there was an overflow, we need to insert the overflow page
// on this BPage
pageNewCopy = btree.copyOnWrite( this );
result.pageNewCopy = pageNewCopy;
if ( DEBUG )
{
System.out.println( "BPage.insert() Overflow page: " + result.overflow.recordId );
}
key = result.overflow.getLargestKey();
overflow = result.overflow.recordId;
// update child's largest key
pageNewCopy.keys[index] = child.getLargestKey();
// clean result so we can reuse it
result.overflow = null;
}
// if we get here, we need to insert a new entry on the BPage
// before children[ index ]
if ( !pageNewCopy.isFull() )
{
if ( height == 0 )
{
insertEntry( pageNewCopy, index - 1, key, value );
}
else
{
insertChild( pageNewCopy, index - 1, key, overflow );
}
btree.recordManager.update( recordId, pageNewCopy, this );
return result;
}
// page is full, we must divide the page
int half = btree.pageSize >> 1;
BPage<K, V> newPage = new BPage<K, V>( btree, pageNewCopy.isLeaf );
if ( index < half )
{
// move lower-half of entries to overflow BPage,
// including new entry
if ( DEBUG )
{
System.out
.println( "Bpage.insert() move lower-half of entries to overflow BPage, including new entry." );
}
if ( height == 0 )
{
copyEntries( pageNewCopy, 0, newPage, half, index );
setEntry( newPage, half + index, key, value );
copyEntries( pageNewCopy, index, newPage, half + index + 1, half - index - 1 );
}
else
{
copyChildren( pageNewCopy, 0, newPage, half, index );
setChild( newPage, half + index, key, overflow );
copyChildren( pageNewCopy, index, newPage, half + index + 1, half - index - 1 );
}
}
else
{
// move lower-half of entries to overflow BPage,
// new entry stays on this BPage
if ( DEBUG )
{
System.out.println( "Bpage.insert() move lower-half of entries to overflow BPage. New entry stays" );
}
if ( height == 0 )
{
copyEntries( pageNewCopy, 0, newPage, half, half );
copyEntries( pageNewCopy, half, pageNewCopy, half - 1, index - half );
setEntry( pageNewCopy, index - 1, key, value );
}
else
{
copyChildren( pageNewCopy, 0, newPage, half, half );
copyChildren( pageNewCopy, half, pageNewCopy, half - 1, index - half );
setChild( pageNewCopy, index - 1, key, overflow );
}
}
pageNewCopy.first = half - 1;
// nullify lower half of entries
for ( int i = 0; i < pageNewCopy.first; i++ )
{
if ( height == 0 )
{
setEntry( pageNewCopy, i, null, null );
}
else
{
setChild( pageNewCopy, i, null, -1 );
}
}
if ( pageNewCopy.isLeaf )
{
// link newly created BPage
newPage.previous = pageNewCopy.previous;
newPage.next = pageNewCopy.recordId;
if ( pageNewCopy.previous != 0 )
{
BPage<K, V> previousBPage = loadBPage( pageNewCopy.previous );
previousBPage = btree.copyOnWrite( previousBPage );
previousBPage.next = newPage.recordId;
btree.recordManager.update( pageNewCopy.previous, previousBPage, this );
}
pageNewCopy.previous = newPage.recordId;
}
btree.recordManager.update( recordId, pageNewCopy, this );
btree.recordManager.update( newPage.recordId, newPage, this );
result.overflow = newPage;
return result;
}
/**
* Remove the entry associated with the given key.
*
* @param height Height of the current BPage (zero is leaf page)
* @param key Removal key
* @return Remove result object
*/
RemoveResult<K, V> remove( int height, K key ) throws IOException
{
RemoveResult<K, V> result;
int half = btree.pageSize / 2;
int index = findChildren( key );
boolean keyExists = index < 0;
if ( index < 0 )
{
index = -( index + 1 );
}
height -= 1;
BPage<K,V> pageNewCopy = btree.copyOnWrite( this );;
if ( height == 0 )
{
// remove leaf entry
if ( !keyExists )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_514, key ) );
}
result = new RemoveResult<K, V>();
result.value = pageNewCopy.values[index];
removeEntry( pageNewCopy, index );
// update this BPage
btree.recordManager.update( recordId, pageNewCopy, this );
}
else
{
// recurse into Btree to remove entry on a children page
BPage<K, V> child = childBPage( index );
result = child.remove( height, key );
if ( result.pageNewCopy != null )
{
child = result.pageNewCopy;
result.pageNewCopy = null;
}
else
{
child = btree.copyOnWrite( child );
}
// update children
pageNewCopy.keys[index] = child.getLargestKey();
btree.recordManager.update( recordId, pageNewCopy, this );
if ( result.underflow )
{
// underflow occured
if ( child.first != half + 1 )
{
throw new IllegalStateException( I18n.err( I18n.ERR_513, "1" ) );
}
if ( index < pageNewCopy.children.length - 1 )
{
// exists greater brother page
BPage<K, V> brother = pageNewCopy.childBPage( index + 1 );
brother = btree.copyOnWrite( brother );
int bfirst = brother.first;
if ( bfirst < half )
{
// steal entries from "brother" page
int steal = ( half - bfirst + 1 ) / 2;
brother.first += steal;
child.first -= steal;
if ( child.isLeaf )
{
copyEntries( child, half + 1, child, half + 1 - steal, half - 1 );
copyEntries( brother, bfirst, child, 2 * half - steal, steal );
}
else
{
copyChildren( child, half + 1, child, half + 1 - steal, half - 1 );
copyChildren( brother, bfirst, child, 2 * half - steal, steal );
}
for ( int i = bfirst; i < bfirst + steal; i++ )
{
if ( brother.isLeaf )
{
setEntry( brother, i, null, null );
}
else
{
setChild( brother, i, null, -1 );
}
}
// update child's largest key
pageNewCopy.keys[index] = child.getLargestKey();
// no change in previous/next BPage
// update BPages
btree.recordManager.update( recordId, pageNewCopy, this );
btree.recordManager.update( brother.recordId, brother, this );
btree.recordManager.update( child.recordId, child, this );
}
else
{
// move all entries from page "child" to "brother"
if ( brother.first != half )
{
throw new IllegalStateException( I18n.err( I18n.ERR_513, "2" ) );
}
brother.first = 1;
if ( child.isLeaf )
{
copyEntries( child, half + 1, brother, 1, half - 1 );
}
else
{
copyChildren( child, half + 1, brother, 1, half - 1 );
}
btree.recordManager.update( brother.recordId, brother, this );
// remove "child" from current BPage
if ( pageNewCopy.isLeaf )
{
copyEntries( pageNewCopy, pageNewCopy.first, pageNewCopy, pageNewCopy.first + 1, index - pageNewCopy.first );
setEntry( pageNewCopy, pageNewCopy.first, null, null );
}
else
{
copyChildren( pageNewCopy, pageNewCopy.first, pageNewCopy, pageNewCopy.first + 1, index - pageNewCopy.first );
setChild( pageNewCopy, pageNewCopy.first, null, -1 );
}
pageNewCopy.first += 1;
btree.recordManager.update( recordId, pageNewCopy, this );
// re-link previous and next BPages
if ( child.previous != 0 )
{
BPage<K, V> prev = loadBPage( child.previous );
prev = btree.copyOnWrite( prev );
prev.next = child.next;
btree.recordManager.update( prev.recordId, prev, this );
}
if ( child.next != 0 )
{
BPage<K, V> next = loadBPage( child.next );
next = btree.copyOnWrite( next );
next.previous = child.previous;
btree.recordManager.update( next.recordId, next, this );
}
// delete "child" BPage
btree.recordManager.delete( child.recordId );
}
}
else
{
// page "brother" is before "child"
BPage<K, V> brother = pageNewCopy.childBPage( index - 1 );
brother = btree.copyOnWrite( brother );
int bfirst = brother.first;
if ( bfirst < half )
{
// steal entries from "brother" page
int steal = ( half - bfirst + 1 ) / 2;
brother.first += steal;
child.first -= steal;
if ( child.isLeaf )
{
copyEntries( brother, 2 * half - steal, child, half + 1 - steal, steal );
copyEntries( brother, bfirst, brother, bfirst + steal, 2 * half - bfirst - steal );
}
else
{
copyChildren( brother, 2 * half - steal, child, half + 1 - steal, steal );
copyChildren( brother, bfirst, brother, bfirst + steal, 2 * half - bfirst - steal );
}
for ( int i = bfirst; i < bfirst + steal; i++ )
{
if ( brother.isLeaf )
{
setEntry( brother, i, null, null );
}
else
{
setChild( brother, i, null, -1 );
}
}
// update brother's largest key
pageNewCopy.keys[index - 1] = brother.getLargestKey();
// no change in previous/next BPage
// update BPages
btree.recordManager.update( recordId, pageNewCopy, this );
btree.recordManager.update( brother.recordId, brother, this );
btree.recordManager.update( child.recordId, child, this );
}
else
{
// move all entries from page "brother" to "child"
if ( brother.first != half )
{
throw new IllegalStateException( I18n.err( I18n.ERR_513, "3" ) );
}
child.first = 1;
if ( child.isLeaf )
{
copyEntries( brother, half, child, 1, half );
}
else
{
copyChildren( brother, half, child, 1, half );
}
btree.recordManager.update( child.recordId, child, this );
// remove "brother" from current BPage
if ( pageNewCopy.isLeaf )
{
copyEntries( pageNewCopy, pageNewCopy.first, pageNewCopy, pageNewCopy.first + 1, index - 1 - pageNewCopy.first );
setEntry( pageNewCopy, pageNewCopy.first, null, null );
}
else
{
copyChildren( pageNewCopy, pageNewCopy.first, pageNewCopy, pageNewCopy.first + 1, index - 1 - pageNewCopy.first );
setChild( pageNewCopy, pageNewCopy.first, null, -1 );
}
pageNewCopy.first += 1;
btree.recordManager.update( recordId, pageNewCopy, this );
// re-link previous and next BPages
if ( brother.previous != 0 )
{
BPage<K, V> prev = loadBPage( brother.previous );
prev = btree.copyOnWrite( prev );
prev.next = brother.next;
btree.recordManager.update( prev.recordId, prev, this );
}
if ( brother.next != 0 )
{
BPage<K, V> next = loadBPage( brother.next );
next = btree.copyOnWrite( next );
next.previous = brother.previous;
btree.recordManager.update( next.recordId, next, this );
}
// delete "brother" BPage
btree.recordManager.delete( brother.recordId );
}
}
}
}
// underflow if page is more than half-empty
result.underflow = pageNewCopy.first > half;
result.pageNewCopy = pageNewCopy;
return result;
}
/**
* Find the first children node with a key equal or greater than the given
* key.
*
* @return index of first children with equal or greater key. If the
* key already exists, the index value will be negative
*/
private int findChildren( K key )
{
int left = first;
int right = btree.pageSize - 1;
// binary search
while ( left < right )
{
int middle = ( left + right ) >>> 1;
int comp = compare( keys[middle], key );
if ( comp < 0 )
{
left = middle + 1;
}
else if ( comp > 0 )
{
right = middle;
}
else
{
// Special case : the key already exists,
// we can return immediately
return -middle - 1;
}
}
if ( left == right )
{
// Special case : we don't know if the key is present
if ( compare( keys[left], key ) ==0 )
{
return -right - 1;
}
}
return right;
}
/**
* Insert entry at given position.
*/
private void insertEntry( BPage<K, V> page, int index, K key, V value ) throws IOException
{
K[] keys = page.keys;
V[] values = page.values;
int start = page.first;
int count = index - page.first + 1;
// shift entries to the left
System.arraycopy( keys, start, keys, start - 1, count );
System.arraycopy( values, start, values, start - 1, count );
page.first -= 1;
keys[index] = key;
values[index] = btree.copyValue( value );
}
/**
* Insert child at given position.
*/
private void insertChild( BPage<K, V> page, int index, K key, long child )
{
K[] keys = page.keys;
long[] children = page.children;
int start = page.first;
int count = index - page.first + 1;
// shift entries to the left
System.arraycopy( keys, start, keys, start - 1, count );
System.arraycopy( children, start, children, start - 1, count );
page.first -= 1;
keys[index] = key;
children[index] = child;
}
/**
* Remove entry at given position.
*/
private void removeEntry( BPage<K, V> page, int index )
{
K[] keys = page.keys;
V[] values = page.values;
int start = page.first;
int count = index - page.first;
System.arraycopy( keys, start, keys, start + 1, count );
keys[start] = null;
System.arraycopy( values, start, values, start + 1, count );
values[start] = null;
page.first++;
}
/**
* Set the entry at the given index.
*/
private void setEntry( BPage<K, V> page, int index, K key, V value )
{
page.keys[index] = key;
page.values[index] = value;
}
/**
* Set the child BPage recordId at the given index.
*/
private void setChild( BPage<K, V> page, int index, K key, long recid )
{
page.keys[index] = key;
page.children[index] = recid;
}
/**
* Copy entries between two BPages
*/
private void copyEntries( BPage<K, V> source, int indexSource, BPage<K, V> dest, int indexDest, int count )
{
System.arraycopy( source.keys, indexSource, dest.keys, indexDest, count );
System.arraycopy( source.values, indexSource, dest.values, indexDest, count );
}
/**
* Copy child BPage recids between two BPages
*/
private void copyChildren( BPage<K, V> source, int indexSource, BPage<K, V> dest, int indexDest, int count )
{
System.arraycopy( source.keys, indexSource, dest.keys, indexDest, count );
System.arraycopy( source.children, indexSource, dest.children, indexDest, count );
}
/**
* Return the child BPage at given index.
*/
// False positive
@SuppressWarnings("PMD.UnnecessaryFinalModifier")
BPage<K, V> childBPage( int index ) throws IOException
{
return loadBPage( children[index] );
}
/**
* Load the BPage at the given recordId.
*/
@SuppressWarnings("unchecked") // The fetch method returns an Object
private BPage<K, V> loadBPage( long recid ) throws IOException
{
BPage<K, V> child = ( BPage<K, V> ) btree.recordManager.fetch( recid, this );
child.recordId = recid;
child.btree = btree;
return child;
}
private final int compare( K value1, K value2 )
{
if ( value1 == value2 )
{
return 0;
}
if ( value1 == null )
{
return 1;
}
if ( value2 == null )
{
return -1;
}
return btree.getComparator().compare( value1, value2 );
}
static byte[] readByteArray( ObjectInput in ) throws IOException
{
int len = in.readInt();
if ( len < 0 )
{
return null;
}
byte[] buf = new byte[len];
in.readFully( buf );
return buf;
}
static void writeByteArray( ObjectOutput out, byte[] buf ) throws IOException
{
if ( buf == null )
{
out.writeInt( -1 );
}
else
{
out.writeInt( buf.length );
out.write( buf );
}
}
/**
* Dump the structure of the tree on the screen. This is used for debugging
* purposes only.
*/
private void dump( int height )
{
String prefix = "";
for ( int i = 0; i < height; i++ )
{
prefix += " ";
}
System.out.println( prefix + "-------------------------------------- BPage recordId=" + recordId );
System.out.println( prefix + "first=" + first );
for ( int i = 0; i < btree.pageSize; i++ )
{
if ( isLeaf )
{
System.out.println( prefix + "BPage [" + i + "] " + keys[i] + " " + values[i] );
}
else
{
System.out.println( prefix + "BPage [" + i + "] " + keys[i] + " " + children[i] );
}
}
System.out.println( prefix + "--------------------------------------" );
}
/**
* Recursively dump the state of the BTree on screen. This is used for
* debugging purposes only.
*/
void dumpRecursive( int height, int level ) throws IOException
{
height -= 1;
level += 1;
if ( height > 0 )
{
for ( int i = first; i < btree.pageSize; i++ )
{
if ( keys[i] == null )
{
break;
}
BPage<K, V> child = childBPage( i );
child.dump( level );
child.dumpRecursive( height, level );
}
}
}
/**
* Assert the ordering of the keys on the BPage. This is used for testing
* purposes only.
*/
private void assertConsistency()
{
for ( int i = first; i < btree.pageSize - 1; i++ )
{
if ( compare( keys[i], keys[i + 1] ) >= 0 )
{
dump( 0 );
throw new Error( I18n.err( I18n.ERR_515 ) );
}
}
}
/**
* Recursively assert the ordering of the BPage entries on this page
* and sub-pages. This is used for testing purposes only.
*/
void assertConsistencyRecursive( int height ) throws IOException
{
assertConsistency();
if ( --height > 0 )
{
for ( int i = first; i < btree.pageSize; i++ )
{
if ( keys[i] == null )
{
break;
}
BPage<K, V> child = childBPage( i );
if ( compare( keys[i], child.getLargestKey() ) != 0 )
{
dump( 0 );
child.dump( 0 );
throw new Error( I18n.err( I18n.ERR_516 ) );
}
child.assertConsistencyRecursive( height );
}
}
}
/**
* Deserialize the content of an object from a byte array.
*
* @param serialized Byte array representation of the object
* @return deserialized object
*
*/
@SuppressWarnings("unchecked") // Cannot create an array of generic objects
public BPage<K, V> deserialize( byte[] serialized ) throws IOException
{
ByteArrayInputStream bais;
ObjectInputStream ois;
BPage<K, V> bpage;
bpage = new BPage<K, V>();
bais = new ByteArrayInputStream( serialized );
ois = new ObjectInputStream( bais );
bpage.isLeaf = ois.readBoolean();
if ( bpage.isLeaf )
{
bpage.previous = ois.readLong();
bpage.next = ois.readLong();
}
bpage.first = ois.readInt();
bpage.keys = (K[])new Object[btree.pageSize];
try
{
for ( int i = bpage.first; i < btree.pageSize; i++ )
{
if ( btree.keySerializer == null )
{
bpage.keys[i] = (K)ois.readObject();
}
else
{
serialized = readByteArray( ois );
if ( serialized != null )
{
bpage.keys[i] = (K)btree.keySerializer.deserialize( serialized );
}
}
}
}
catch ( ClassNotFoundException except )
{
throw new IOException( except.getLocalizedMessage() );
}
if ( bpage.isLeaf )
{
bpage.values = (V[])new Object[btree.pageSize];
try
{
for ( int i = bpage.first; i < btree.pageSize; i++ )
{
if ( btree.valueSerializer == null )
{
bpage.values[i] =(V) ois.readObject();
}
else
{
serialized = readByteArray( ois );
if ( serialized != null )
{
bpage.values[i] = (V)btree.valueSerializer.deserialize( serialized );
}
}
}
}
catch ( ClassNotFoundException except )
{
throw new IOException( except.getLocalizedMessage() );
}
}
else
{
bpage.children = new long[btree.pageSize];
for ( int i = bpage.first; i < btree.pageSize; i++ )
{
bpage.children[i] = ois.readLong();
}
}
ois.close();
bais.close();
return bpage;
}
/**
* Serialize the content of an object into a byte array.
*
* @param obj Object to serialize
* @return a byte array representing the object's state
*
*/
@SuppressWarnings("unchecked") // The serialize signature requires an Object, so we have to cast
public byte[] serialize( Object obj ) throws IOException
{
byte[] serialized;
ByteArrayOutputStream baos;
ObjectOutputStream oos;
BPage<K, V> bpage;
byte[] data;
// note: It is assumed that BPage instance doing the serialization is the parent
// of the BPage object being serialized.
bpage = ( BPage<K, V> ) obj;
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream( baos );
oos.writeBoolean( bpage.isLeaf );
if ( bpage.isLeaf )
{
oos.writeLong( bpage.previous );
oos.writeLong( bpage.next );
}
oos.writeInt( bpage.first );
for ( int i = bpage.first; i < btree.pageSize; i++ )
{
if ( btree.keySerializer == null )
{
oos.writeObject( bpage.keys[i] );
}
else
{
if ( bpage.keys[i] != null )
{
serialized = btree.keySerializer.serialize( bpage.keys[i] );
writeByteArray( oos, serialized );
}
else
{
writeByteArray( oos, null );
}
}
}
if ( bpage.isLeaf )
{
for ( int i = bpage.first; i < btree.pageSize; i++ )
{
if ( btree.valueSerializer == null )
{
oos.writeObject( bpage.values[i] );
}
else
{
if ( bpage.values[i] != null )
{
serialized = btree.valueSerializer.serialize( bpage.values[i] );
writeByteArray( oos, serialized );
}
else
{
writeByteArray( oos, null );
}
}
}
}
else
{
for ( int i = bpage.first; i < btree.pageSize; i++ )
{
oos.writeLong( bpage.children[i] );
}
}
oos.flush();
data = baos.toByteArray();
oos.close();
baos.close();
return data;
}
/** STATIC INNER CLASS
* Result from insert() method call. If the insertion has created
* a new page, it will be contained in the overflow field.
* If the inserted element already exist, then we will store
* the existing element.
*/
static class InsertResult<K, V>
{
/**
* Overflow page.
*/
BPage<K, V> overflow;
/**
* Existing value for the insertion key.
*/
V existing;
/**
* New version of the page doing the insert
*/
BPage<K, V> pageNewCopy;
}
/** STATIC INNER CLASS
* Result from remove() method call. If we had to removed a BPage,
* it will be stored into the underflow field.
*/
static class RemoveResult<K, V>
{
/**
* Set to true if underlying pages underflowed
*/
boolean underflow;
/**
* Removed entry value
*/
V value;
/**
* New version of the page doing the remove
*/
BPage<K, V> pageNewCopy;
}
/** PRIVATE INNER CLASS
* Browser to traverse leaf BPages.
*/
class Browser extends TupleBrowser<K, V>
{
/** Current page. */
private BPage<K, V> page;
/** context used to track browsing action */
ActionContext context;
/**
* Current index in the page. The index positionned on the next
* tuple to return.
*/
private int index;
/**
* Create a browser.
*
* @param page Current page
* @param context context in case of action capable record manager
* @param index Position of the next tuple to return.
*/
Browser( BPage<K, V> page, int index, ActionContext context)
{
this.page = page;
this.index = index;
this.context = context;
outstandingBrowsers.incrementAndGet();
}
/**
* Get the next Tuple in the current BTree. We have 3 cases to deal with :
* 1) we are at the end of the btree. We will return false, the tuple won't be set.
* 2) we are in the middle of a page : grab the values and store them into the tuple
* 3) we are at the end of the page : grab the next page, and get the tuple from it.
*
* @return true if we have found a tumple, false otherwise.
*/
public boolean getNext( Tuple<K, V> tuple ) throws IOException
{
btree.setAsCurrentAction( context );
try
{
// First, check that we are within a page
if ( index < page.btree.pageSize )
{
// We are. Now check that we have a Tuple
if ( page.keys[index] == null )
{
// no : reached end of the tree.
return false;
}
}
// all the tuple for this page has been read. Move to the
// next page, if we have one.
else if ( page.next != 0 )
{
// move to next page
page = page.loadBPage( page.next );
index = page.first;
}
tuple.setKey( page.keys[index] );
tuple.setValue( btree.copyValue( page.values[index] ) );
index++;
}
catch( IOException e )
{
btree.abortAction( context );
context = null;
this.close();
throw e;
}
finally
{
if ( context != null )
btree.unsetAsCurrentAction( context );
}
return true;
}
public boolean getPrevious( Tuple<K, V> tuple ) throws IOException
{
btree.setAsCurrentAction( context );
try
{
if ( index == page.first )
{
if ( page.previous != 0 )
{
page = page.loadBPage( page.previous );
index = page.btree.pageSize;
}
else
{
// reached beginning of the tree
return false;
}
}
index--;
tuple.setKey( page.keys[index] );
tuple.setValue( btree.copyValue( page.values[index] ) );
}
catch( IOException e )
{
btree.abortAction( context );
context = null;
this.close();
throw e;
}
finally
{
if ( context != null )
btree.unsetAsCurrentAction( context );
}
return true;
}
@Override
public void close()
{
super.close();
if ( context!= null )
{
btree.setAsCurrentAction( context );
btree.endAction( context );
context = null;
}
int browserCount = outstandingBrowsers.decrementAndGet();
if ( browserCount > 0 )
{
//System.out.println( "JDBM btree browsers are outstanding after close: " + browserCount );
}
}
}
public String toString()
{
StringBuilder sb = new StringBuilder();
if ( isLeaf )
{
sb.append( "Leaf(" );
}
else
{
sb.append( "Node(" );
}
sb.append( keys.length );
sb.append( ") : [" );
if ( isLeaf )
{
boolean isFirst = true;
int index = 0;
for ( K key : keys )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( ", " );
}
sb.append( "<" );
sb.append( String.valueOf( key ) );
sb.append( "/" );
sb.append( values[index] );
sb.append( ">" );
index++;
}
}
else
{
boolean isFirst = true;
//int index = 0;
for ( K key : keys )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( ", " );
}
sb.append( "<" );
sb.append( key );
//sb.append( "/" );
//sb.append( values[index] );
sb.append( ">" );
}
}
sb.append( "]\n" );
return sb.toString();
}
}