blob: e2c3fdf077779884e9d1f11ae7d5263a87fc971f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.mavibot.btree;
import static org.apache.directory.mavibot.btree.BTreeFactory.createLeaf;
import static org.apache.directory.mavibot.btree.BTreeFactory.createNode;
import static org.apache.directory.mavibot.btree.BTreeFactory.setKey;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import org.apache.directory.mavibot.btree.exception.BTreeAlreadyCreatedException;
import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException;
import org.apache.directory.mavibot.btree.exception.BTreeCreationException;
import org.apache.directory.mavibot.btree.exception.KeyNotFoundException;
import org.apache.directory.mavibot.btree.serializer.IntSerializer;
import org.apache.directory.mavibot.btree.serializer.LongSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A holder to store the Values
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @param <V> The value type
*/
/* No qualifier */class PersistedValueHolder<V> extends AbstractValueHolder<V>
{
/** The LoggerFactory used by this class */
protected static final Logger LOG = LoggerFactory.getLogger( PersistedValueHolder.class );
/** The parent BTree */
protected PersistedBTree<V, V> parentBtree;
/** The serialized value */
private byte[] raw;
/** A flag set to true when the raw value has been deserialized */
private boolean isDeserialized = false;
/** A flag to signal that the raw value represent the serialized values in their last state */
private boolean isRawUpToDate = false;
/**
* Creates a new instance of a ValueHolder, containing the serialized values.
*
* @param parentBtree the parent BTree
* @param raw The raw data containing the values
* @param nbValues the number of stored values
* @param raw the byte[] containing either the serialized array of values or the sub-btree offset
*/
PersistedValueHolder( BTree<?, V> parentBtree, int nbValues, byte[] raw )
{
this.parentBtree = ( PersistedBTree<V, V> ) parentBtree;
this.valueSerializer = parentBtree.getValueSerializer();
this.raw = raw;
isRawUpToDate = true;
valueThresholdUp = PersistedBTree.valueThresholdUp;
valueThresholdLow = PersistedBTree.valueThresholdLow;
// We create the array of values if they fit in an array. If they are stored in a
// BTree, we do nothing atm.
if ( nbValues <= valueThresholdUp )
{
// The values are contained into an array
valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues );
}
}
/**
* Creates a new instance of a ValueHolder, containing Values. This constructor is called
* when we need to create a new ValueHolder with deserialized values.
*
* @param parentBtree The parent BTree
* @param values The Values stored in the ValueHolder
*/
PersistedValueHolder( BTree<?, V> parentBtree, V... values )
{
this.parentBtree = ( PersistedBTree<V, V> ) parentBtree;
this.valueSerializer = parentBtree.getValueSerializer();
valueThresholdUp = PersistedBTree.valueThresholdUp;
valueThresholdLow = PersistedBTree.valueThresholdLow;
if ( values != null )
{
int nbValues = values.length;
if ( nbValues < PersistedBTree.valueThresholdUp )
{
// Keep an array
valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues );
try
{
System.arraycopy( values, 0, valueArray, 0, values.length );
}
catch ( ArrayStoreException ase )
{
ase.printStackTrace();
throw ase;
}
}
else
{
// Use a sub btree, now that we have reached the threshold
createSubTree();
try
{
if( values.length == 1 )
{
add( values[0] );
}
else
{
buildSubBTree( ( PersistedBTree<V, V> ) valueBtree, values );
}
}
catch ( Exception e )
{
throw new RuntimeException( e );
}
}
}
else
{
// No value, we create an empty array
valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), 0 );
}
isDeserialized = true;
}
/**
* @return a cursor on top of the values
*/
public ValueCursor<V> getCursor()
{
// Check that the values are deserialized before doing anything
checkAndDeserialize();
return super.getCursor();
}
/**
* @return the raw representation of the value holder. The serialized value will not be the same
* if the values are stored in an array or in a btree. <br/>
* If they are stored in a BTree, the raw value will contain the offset of the btree, otherwise
* it will contain a byte[] which will contain each serialized value, prefixed by their length.
*
*/
/* No qualifier*/byte[] getRaw()
{
if ( isRawUpToDate )
{
// Just have to return the raw value
return raw;
}
if ( isSubBtree() )
{
// The values are stored into a subBtree, return the offset of this subBtree
long btreeOffset = ( ( PersistedBTree<V, V> ) valueBtree ).getBtreeOffset();
raw = LongSerializer.serialize( btreeOffset );
}
else
{
// Create as many byte[] as we have length and serialized values to store
byte[][] valueBytes = new byte[valueArray.length * 2][];
int length = 0;
int pos = 0;
// Process each value now
for ( V value : valueArray )
{
// Serialize the value
byte[] bytes = valueSerializer.serialize( value );
length += bytes.length;
// Serialize the value's length
byte[] sizeBytes = IntSerializer.serialize( bytes.length );
length += sizeBytes.length;
// And store the two byte[]
valueBytes[pos++] = sizeBytes;
valueBytes[pos++] = bytes;
}
// Last, not least, create a buffer large enough to contain all the created byte[],
// and copy all those byte[] into this buffer
raw = new byte[length];
pos = 0;
for ( byte[] bytes : valueBytes )
{
System.arraycopy( bytes, 0, raw, pos, bytes.length );
pos += bytes.length;
}
}
// Update the flags
isRawUpToDate = true;
return raw;
}
/**
* {@inheritDoc}
*/
public int size()
{
checkAndDeserialize();
if ( valueArray == null )
{
return ( int ) valueBtree.getNbElems();
}
else
{
return valueArray.length;
}
}
/**
* Create a new Sub-BTree to store the values.
*/
protected void createSubTree()
{
try
{
PersistedBTreeConfiguration<V, V> configuration = new PersistedBTreeConfiguration<V, V>();
configuration.setAllowDuplicates( false );
configuration.setKeySerializer( valueSerializer );
configuration.setName( UUID.randomUUID().toString() );
configuration.setValueSerializer( valueSerializer );
configuration.setParentBTree( parentBtree );
configuration.setBtreeType( BTreeTypeEnum.PERSISTED_SUB );
valueBtree = BTreeFactory.createPersistedBTree( configuration );
try
{
// The sub-btree will not be added into the BOB.
parentBtree.getRecordManager().manage( valueBtree, RecordManager.INTERNAL_BTREE );
raw = null;
}
catch ( BTreeAlreadyManagedException e )
{
// should never happen
throw new BTreeAlreadyCreatedException( e );
}
}
catch ( IOException e )
{
throw new BTreeCreationException( e );
}
}
/**
* Set the subBtree in the ValueHolder
*/
/* No qualifier*/void setSubBtree( BTree<V, V> subBtree )
{
valueBtree = subBtree;
raw = null;
valueArray = null;
isDeserialized = true;
isRawUpToDate = false;
}
/**
* Check that the values are stored as raw value
*/
private void checkAndDeserialize()
{
if ( !isDeserialized )
{
if ( valueArray == null )
{
// the values are stored into a sub-btree. Read it now if it's not already done
deserializeSubBtree();
}
else
{
// The values are stored into an array. Deserialize it now
deserializeArray();
}
// Change the flag
isDeserialized = true;
}
}
/**
* {@inheritDoc}
*/
public void add( V value )
{
// First check that we have a loaded BTree
checkAndDeserialize();
super.add( value );
// The raw value is not anymore up to date with the content
isRawUpToDate = false;
raw = null;
}
/**
* Remove a value from an array
*/
private V removeFromArray( V value )
{
checkAndDeserialize();
// First check that the value is not already present in the ValueHolder
int pos = findPos( value );
if ( pos < 0 )
{
// The value does not exists : nothing to do
return null;
}
// Ok, we just have to delete the new element at the right position
// First, copy the array
V[] newValueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), valueArray.length - 1 );
System.arraycopy( valueArray, 0, newValueArray, 0, pos );
System.arraycopy( valueArray, pos + 1, newValueArray, pos, valueArray.length - pos - 1 );
// Get the removed element
V removedValue = valueArray[pos];
// And switch the arrays
valueArray = newValueArray;
return removedValue;
}
/**
* Remove the value from a sub btree
*/
private V removeFromBtree( V removedValue )
{
// First check that we have a loaded BTree
checkAndDeserialize();
if ( btreeContains( removedValue ) )
{
try
{
if ( valueBtree.getNbElems() - 1 < PersistedBTree.valueThresholdLow )
{
int nbValues = ( int ) ( valueBtree.getNbElems() - 1 );
// We have to switch to an Array of values
valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues );
// Now copy all the value but the one we have removed
TupleCursor<V, V> cursor = valueBtree.browse();
V returnedValue = null;
int pos = 0;
while ( cursor.hasNext() )
{
Tuple<V, V> tuple = cursor.next();
V value = tuple.getKey();
if ( valueSerializer.getComparator().compare( removedValue, value ) == 0 )
{
// This is the removed value : skip it
returnedValue = value;
}
else
{
valueArray[pos++] = value;
}
}
cursor.close();
return returnedValue;
}
else
{
Tuple<V, V> removedTuple = valueBtree.delete( removedValue );
if ( removedTuple != null )
{
return removedTuple.getKey();
}
else
{
return null;
}
}
}
catch ( IOException e )
{
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
catch ( KeyNotFoundException knfe )
{
// TODO Auto-generated catch block
knfe.printStackTrace();
return null;
}
}
else
{
return null;
}
}
/**
* {@inheritDoc}
*/
public V remove( V value )
{
V removedValue = null;
if ( valueArray != null )
{
removedValue = removeFromArray( value );
}
else
{
removedValue = removeFromBtree( value );
}
// The raw value is not anymore up to date wth the content
isRawUpToDate = false;
raw = null;
return removedValue;
}
/**
* {@inheritDoc}
*/
public boolean contains( V checkedValue )
{
// First, deserialize the value if it's still a byte[]
checkAndDeserialize();
return super.contains( checkedValue );
}
/**
* Find the position of a given value in the array, or the position where we
* would insert the element (in this case, the position will be negative).
* As we use a 0-based array, the negative position for 0 is -1.
* -1 means the element can be added in position 0
* -2 means the element can be added in position 1
* ...
*/
private int findPos( V value )
{
if ( valueArray.length == 0 )
{
return -1;
}
// Do a search using dichotomy
int pivot = valueArray.length / 2;
int low = 0;
int high = valueArray.length - 1;
Comparator<V> comparator = valueSerializer.getComparator();
while ( high > low )
{
switch ( high - low )
{
case 1:
// We have 2 elements
int result = comparator.compare( value, valueArray[pivot] );
if ( result == 0 )
{
return pivot;
}
if ( result < 0 )
{
if ( pivot == low )
{
return -( low + 1 );
}
else
{
result = comparator.compare( value, valueArray[low] );
if ( result == 0 )
{
return low;
}
if ( result < 0 )
{
return -( low + 1 );
}
else
{
return -( low + 2 );
}
}
}
else
{
if ( pivot == high )
{
return -( high + 2 );
}
else
{
result = comparator.compare( value, valueArray[high] );
if ( result == 0 )
{
return high;
}
if ( result < 0 )
{
return -( high + 1 );
}
else
{
return -( high + 2 );
}
}
}
default:
// We have 3 elements
result = comparator.compare( value, valueArray[pivot] );
if ( result == 0 )
{
return pivot;
}
if ( result < 0 )
{
high = pivot - 1;
}
else
{
low = pivot + 1;
}
pivot = ( high + low ) / 2;
continue;
}
}
int result = comparator.compare( value, valueArray[pivot] );
if ( result == 0 )
{
return pivot;
}
if ( result < 0 )
{
return -( pivot + 1 );
}
else
{
return -( pivot + 2 );
}
}
/**
* Create a clone of this instance
*/
public ValueHolder<V> clone() throws CloneNotSupportedException
{
PersistedValueHolder<V> copy = ( PersistedValueHolder<V> ) super.clone();
// copy the valueArray if it's not null
// We don't clone the BTree, as we will create new revisions when
// modifying it
if ( valueArray != null )
{
copy.valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), valueArray.length );
System.arraycopy( valueArray, 0, copy.valueArray, 0, valueArray.length );
}
// Also clone the raw value if its up to date
if ( isRawUpToDate )
{
copy.raw = new byte[raw.length];
System.arraycopy( raw, 0, copy.raw, 0, raw.length );
}
return copy;
}
@Override
public V replaceValueArray( V newValue )
{
V val = super.replaceValueArray( newValue );
// The raw value is not anymore up to date with the content
isRawUpToDate = false;
return val;
}
/**
* Deserialize the values stored in an array
*/
private void deserializeArray()
{
// We haven't yet deserialized the values. Let's do it now. The values are
// necessarily stored in an array at this point
int index = 0;
int pos = 0;
while ( pos < raw.length )
{
try
{
int size = IntSerializer.deserialize( raw, pos );
pos += 4;
V value = valueSerializer.fromBytes( raw, pos );
pos += size;
valueArray[index++] = value;
}
catch ( IOException e )
{
e.printStackTrace();
}
}
}
/**
* Deserialize the values stored in a sub-btree
*/
private void deserializeSubBtree()
{
// Get the sub-btree offset
long offset = LongSerializer.deserialize( raw );
// and reload the sub btree
valueBtree = parentBtree.getRecordManager().loadDupsBtree( offset, parentBtree );
}
/**
* @return The sub-btree offset
*/
/* No qualifier */long getOffset()
{
if ( valueArray == null )
{
return ( ( PersistedBTree<V, V> ) valueBtree ).getBtreeOffset();
}
else
{
return -1L;
}
}
/**
* Constructs the sub-BTree using bulkload instead of performing sequential inserts.
*
* @param btree the sub-BTtree to be constructed
* @param dupKeyValues the array of values to be inserted as keys
* @return
* @throws Exception
*/
protected BTree buildSubBTree( BTree<V, V> givenBTree, V[] dupKeyValues ) throws Exception
{
PersistedBTree<V, V> btree = ( PersistedBTree<V, V> ) givenBTree;
long newRevision = btree.getRevision() + 1;
int numKeysInNode = btree.getPageSize();
RecordManager rm = btree.getRecordManager();
List<Page<V, V>> lstLeaves = new ArrayList<Page<V, V>>();
int totalTupleCount = 0;
Page<V, V> leaf1 = BTreeFactory.createLeaf( btree, newRevision, numKeysInNode );
lstLeaves.add( leaf1 );
int leafIndex = 0;
for ( V v : dupKeyValues )
{
setKey( btree, leaf1, leafIndex, v );
leafIndex++;
totalTupleCount++;
if ( ( totalTupleCount % numKeysInNode ) == 0 )
{
leafIndex = 0;
PageHolder<V, V> pageHolder = ( PageHolder ) rm.writePage( btree, leaf1, newRevision );
leaf1 = createLeaf( btree, newRevision, numKeysInNode );
lstLeaves.add( leaf1 );
}
//TODO build the whole tree in chunks rather than processing *all* leaves at first
}
if ( lstLeaves.isEmpty() )
{
return btree;
}
// remove null keys and values from the last leaf and resize
PersistedLeaf lastLeaf = ( PersistedLeaf ) lstLeaves.get( lstLeaves.size() - 1 );
for ( int i = 0; i < lastLeaf.nbElems; i++ )
{
if ( lastLeaf.keys[i] == null )
{
int n = i;
lastLeaf.nbElems = n;
KeyHolder[] keys = lastLeaf.keys;
lastLeaf.keys = ( KeyHolder[] ) Array.newInstance( KeyHolder.class, n );
System.arraycopy( keys, 0, lastLeaf.keys, 0, n );
PageHolder pageHolder = ( PageHolder ) rm.writePage( btree, lastLeaf, newRevision );
break;
}
}
if( lastLeaf.keys.length == 0 )
{
lstLeaves.remove( lastLeaf );
}
// make sure either one of the root pages is reclaimed, cause when we call rm.manage()
// there is already a root page created
Page rootPage = attachNodes( lstLeaves, btree, numKeysInNode, rm );
Page oldRoot = btree.getRootPage();
long newRootPageOffset = ( ( AbstractPage ) rootPage ).getOffset();
System.out.println( "replacing old offset " + btree.getRootPageOffset() + " of the BTree " + btree.getName() + " with " + newRootPageOffset );
BTreeHeader header = btree.getBtreeHeader();
header.setRootPage( rootPage );
header.setRevision( newRevision );
header.setNbElems( totalTupleCount );
long newBtreeHeaderOffset = rm.writeBtreeHeader( btree, header );
header.setBTreeHeaderOffset( newBtreeHeaderOffset );
rm.free( ( ( AbstractPage ) oldRoot ).getOffset() );
return btree;
}
/**
* Attaches the Nodes together
*
* @param children the leaves
* @param btree the sub-BTree
* @param numKeysInNode number of keys per each node
* @param rm the RecordManager
* @return the new root page of the sub-BTree after attaching all the nodes
* @throws IOException
*/
private Page attachNodes( List<Page<V, V>> children, BTree btree, int numKeysInNode, RecordManager rm )
throws IOException
{
if ( children.size() == 1 )
{
return children.get( 0 );
}
List<Page<V, V>> lstNodes = new ArrayList<Page<V, V>>();
int numChildren = numKeysInNode + 1;
PersistedNode node = ( PersistedNode ) createNode( btree, btree.getRevision(), numKeysInNode );
lstNodes.add( node );
int i = 0;
int totalNodes = 0;
for ( Page p : children )
{
if ( i != 0 )
{
setKey( btree, node, i - 1, p.getLeftMostKey() );
}
node.children[i] = new PersistedPageHolder( btree, p );
i++;
totalNodes++;
if ( ( totalNodes % numChildren ) == 0 )
{
i = 0;
PageHolder pageHolder = ( PageHolder ) rm.writePage( btree, node, 1 );
node = ( PersistedNode ) createNode( btree, btree.getRevision(), numKeysInNode );
lstNodes.add( node );
}
}
// remove null keys and values from the last node and resize
AbstractPage lastNode = ( AbstractPage ) lstNodes.get( lstNodes.size() - 1 );
for ( int j = 0; j < lastNode.nbElems; j++ )
{
if ( lastNode.keys[j] == null )
{
int n = j;
lastNode.nbElems = n;
KeyHolder[] keys = lastNode.keys;
lastNode.keys = ( KeyHolder[] ) Array.newInstance( KeyHolder.class, n );
System.arraycopy( keys, 0, lastNode.keys, 0, n );
PageHolder pageHolder = ( PageHolder ) rm.writePage( btree, lastNode, 1 );
break;
}
}
if ( lastNode.keys.length == 0 )
{
lstNodes.remove( lastNode );
}
return attachNodes( lstNodes, btree, numKeysInNode, rm );
}
/**
* @see Object#toString()
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "ValueHolder[" ).append( valueSerializer.getClass().getSimpleName() );
if ( !isDeserialized )
{
sb.append( ", isRaw[" ).append( raw.length ).append( "]" );
}
else
{
if ( valueArray == null )
{
sb.append( ", SubBTree" );
}
else
{
sb.append( ", array{" );
if ( valueArray == null )
{
sb.append( "}" );
}
else
{
boolean isFirst = true;
for ( V value : valueArray )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( "/" );
}
sb.append( value );
}
sb.append( "}" );
}
}
}
sb.append( "]" );
return sb.toString();
}
}