blob: 4f5d6818e3356d446f6d1bf08c106f814bbf1f09 [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 java.io.IOException;
import java.lang.reflect.Array;
import java.util.List;
/**
* A MVCC Node. It stores the keys and references to its children page. It does not
* contain any value.
*
* @param <K> The type for the Key
* @param <V> The type for the stored value
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
/* No qualifier */class InMemoryNode<K, V> extends AbstractPage<K, V>
{
/**
* Creates a new Node which will contain only one key, with references to
* a left and right page. This is a specific constructor used by the btree
* when the root was full when we added a new value.
*
* @param btree the parent BTree
* @param revision the Node revision
* @param nbElems The number of elements in this Node
*/
@SuppressWarnings("unchecked")
InMemoryNode( BTree<K, V> btree, long revision, int nbElems )
{
super( btree, revision, nbElems );
// Create the children array
children = ( PageHolder<K, V>[] ) Array.newInstance( PageHolder.class, nbElems + 1 );
}
/**
* Creates a new Node which will contain only one key, with references to
* a left and right page. This is a specific constructor used by the btree
* when the root was full when we added a new value.
*
* @param btree the parent BTree
* @param revision the Node revision
* @param key The new key
* @param leftPage The left page
* @param rightPage The right page
*/
@SuppressWarnings("unchecked")
InMemoryNode( BTree<K, V> btree, long revision, K key, Page<K, V> leftPage, Page<K, V> rightPage )
{
super( btree, revision, 1 );
// Create the children array, and store the left and right children
children = ( PageHolder[] ) Array.newInstance( PageHolder.class, btree.getPageSize() + 1 );
children[0] = new PageHolder<K, V>( btree, leftPage );
children[1] = new PageHolder<K, V>( btree, rightPage );
// Create the keys array and store the pivot into it
// We get the type of array to create from the btree
// Yes, this is an hack...
setKeys( ( KeyHolder<K>[] ) Array.newInstance( KeyHolder.class, btree.getPageSize() ) );
setKey( 0, new KeyHolder<K>( key ) );
}
/**
* {@inheritDoc}
*/
public InsertResult<K, V> insert( K key, V value, long revision ) throws IOException
{
// Find the key into this leaf
int pos = findPos( key );
if ( pos < 0 )
{
// The key has been found in the page. As it's a Node, that means
// we must go down in the right child to insert the value
pos = -( pos++ );
}
// Get the child page into which we will insert the <K, V> tuple
Page<K, V> child = children[pos].getValue();
// and insert the <K, V> into this child
InsertResult<K, V> result = child.insert( key, value, revision );
// Ok, now, we have injected the <K, V> tuple down the tree. Let's check
// the result to see if we have to split the current page
if ( result instanceof ModifyResult )
{
// The child has been modified.
return replaceChild( revision, ( ModifyResult<K, V> ) result, pos );
}
else
{
// The child has been split. We have to insert the new pivot in the
// current page, and to reference the two new pages
SplitResult<K, V> splitResult = ( SplitResult<K, V> ) result;
K pivot = splitResult.getPivot();
Page<K, V> leftPage = splitResult.getLeftPage();
Page<K, V> rightPage = splitResult.getRightPage();
// We have to deal with the two cases :
// - the current page is full, we have to split it
// - the current page is not full, we insert the new pivot
if ( nbElems == btree.getPageSize() )
{
// The page is full
result = addAndSplit( splitResult.getCopiedPages(), revision, pivot, leftPage, rightPage, pos );
}
else
{
// The page can contain the new pivot, let's insert it
result = insertChild( splitResult.getCopiedPages(), revision, pivot, leftPage, rightPage, pos );
}
return result;
}
}
/**
* Modifies the current node after a remove has been done in one of its children.
* The node won't be merged with another node.
*
* @param removeResult The result of a remove operation
* @param index the position of the key, not transformed
* @param pos The position of the key, as a positive value
* @param found If the key has been found in the page
* @return The new result
* @throws IOException If we have an error while trying to access the page
*/
private RemoveResult<K, V> handleRemoveResult( RemoveResult<K, V> removeResult, int index, int pos, boolean found )
throws IOException
{
// Simplest case : the element has been removed from the underlying page,
// we just have to copy the current page an modify the reference to link to
// the modified page.
InMemoryNode<K, V> newPage = copy( revision );
Page<K, V> modifiedPage = removeResult.getModifiedPage();
if ( found )
{
newPage.children[index + 1] = new PageHolder<K, V>( btree, modifiedPage );
}
else
{
newPage.children[index] = new PageHolder<K, V>( btree, modifiedPage );
}
if ( pos < 0 )
{
newPage.setKey( index, new KeyHolder<K>( removeResult.getModifiedPage().getLeftMostKey() ) );
}
// Modify the result and return
removeResult.setModifiedPage( newPage );
removeResult.addCopiedPage( this );
return removeResult;
}
/**
* Handles the removal of an element from the root page, when two of its children
* have been merged.
*
* @param mergedResult The merge result
* @param pos The position in the current root
* @param found Tells if the removed key is present in the root page
* @return The resulting root page
* @throws IOException If we have an error while trying to access the page
*/
private RemoveResult<K, V> handleRootRemove( MergedWithSiblingResult<K, V> mergedResult, int pos, boolean found )
throws IOException
{
RemoveResult<K, V> removeResult = null;
// If the current node contains only one key, then the merged result will be
// the new root. Deal with this case
if ( nbElems == 1 )
{
removeResult = new RemoveResult<K, V>( mergedResult.getCopiedPages(), mergedResult.getModifiedPage(),
mergedResult.getRemovedElement() );
removeResult.addCopiedPage( this );
}
else
{
// Remove the element and update the reference to the changed pages
removeResult = removeKey( mergedResult, revision, pos );
}
return removeResult;
}
/**
* Borrows an element from the right sibling, creating a new sibling with one
* less element and creating a new page where the element to remove has been
* deleted and the borrowed element added on the right.
*
* @param revision The new revision for all the pages
* @param sibling The right sibling
* @param pos The position of the element to remove
* @return The resulting pages
* @throws IOException If we have an error while trying to access the page
*/
private DeleteResult<K, V> borrowFromRight( long revision, MergedWithSiblingResult<K, V> mergedResult,
InMemoryNode<K, V> sibling, int pos ) throws IOException
{
// Create the new sibling, with one less element at the beginning
InMemoryNode<K, V> newSibling = new InMemoryNode<K, V>( btree, revision, sibling.getNbElems() - 1 );
K siblingKey = sibling.children[0].getValue().getLeftMostKey();
// Copy the keys and children of the old sibling in the new sibling
System.arraycopy( sibling.getKeys(), 1, newSibling.getKeys(), 0, newSibling.getNbElems() );
System.arraycopy( sibling.children, 1, newSibling.children, 0, newSibling.getNbElems() + 1 );
// Create the new page and add the new element at the end
// First copy the current node, with the same size
InMemoryNode<K, V> newNode = new InMemoryNode<K, V>( btree, revision, nbElems );
// Copy the keys and the values up to the insertion position
int index = Math.abs( pos );
// Copy the key and children from sibling
newNode.setKey( nbElems - 1, new KeyHolder<K>( siblingKey ) ); // 1
newNode.children[nbElems] = sibling.children[0]; // 8
if ( index < 2 )
{
// Copy the keys
System.arraycopy( getKeys(), 1, newNode.getKeys(), 0, nbElems - 1 );
// Inject the modified page
Page<K, V> modifiedPage = mergedResult.getModifiedPage();
newNode.children[0] = new PageHolder<K, V>( btree, modifiedPage );
// Copy the children
System.arraycopy( children, 2, newNode.children, 1, nbElems - 1 );
}
else
{
if ( index > 2 )
{
// Copy the keys before the deletion point
System.arraycopy( getKeys(), 0, newNode.getKeys(), 0, index - 2 ); // 4
}
// Inject the new modified page key
newNode.setKey( index - 2, new KeyHolder<K>( mergedResult.getModifiedPage().getLeftMostKey() ) ); // 2
if ( index < nbElems )
{
// Copy the remaining keys after the deletion point
System.arraycopy( getKeys(), index, newNode.getKeys(), index - 1, nbElems - index ); // 3
// Copy the remaining children after the deletion point
System.arraycopy( children, index + 1, newNode.children, index, nbElems - index ); // 7
}
// Copy the children before the deletion point
System.arraycopy( children, 0, newNode.children, 0, index - 1 ); // 5
// Inject the modified page
Page<K, V> modifiedPage = mergedResult.getModifiedPage();
newNode.children[index - 1] = new PageHolder<K, V>( btree, modifiedPage ); // 6
}
// Create the result
DeleteResult<K, V> result = new BorrowedFromRightResult<K, V>( mergedResult.getCopiedPages(), newNode,
newSibling, mergedResult.getRemovedElement() );
result.addCopiedPage( this );
result.addCopiedPage( sibling );
return result;
}
/**
* Borrows an element from the left sibling, creating a new sibling with one
* less element and creating a new page where the element to remove has been
* deleted and the borrowed element added on the left.
*
* @param revision The new revision for all the pages
* @param sibling The left sibling
* @param pos The position of the element to remove
* @return The resulting pages
* @throws IOException If we have an error while trying to access the page
*/
private DeleteResult<K, V> borrowFromLeft( long revision, MergedWithSiblingResult<K, V> mergedResult,
InMemoryNode<K, V> sibling, int pos ) throws IOException
{
// The sibling is on the left, borrow the rightmost element
Page<K, V> siblingChild = sibling.children[sibling.nbElems].getValue();
// Create the new sibling, with one less element at the end
InMemoryNode<K, V> newSibling = new InMemoryNode<K, V>( btree, revision, sibling.getNbElems() - 1 );
// Copy the keys and children of the old sibling in the new sibling
System.arraycopy( sibling.getKeys(), 0, newSibling.getKeys(), 0, newSibling.getNbElems() );
System.arraycopy( sibling.children, 0, newSibling.children, 0, newSibling.getNbElems() + 1 );
// Create the new page and add the new element at the beginning
// First copy the current node, with the same size
InMemoryNode<K, V> newNode = new InMemoryNode<K, V>( btree, revision, nbElems );
// Sets the first children
newNode.children[0] = new PageHolder<K, V>( btree, siblingChild ); //1
int index = Math.abs( pos );
if ( index < 2 )
{
newNode.setKey( 0, new KeyHolder<K>( mergedResult.getModifiedPage().getLeftMostKey() ) );
System.arraycopy( getKeys(), 1, newNode.getKeys(), 1, nbElems - 1 );
Page<K, V> modifiedPage = mergedResult.getModifiedPage();
newNode.children[1] = new PageHolder<K, V>( btree, modifiedPage );
System.arraycopy( children, 2, newNode.children, 2, nbElems - 1 );
}
else
{
// Set the first key
newNode.setKey( 0, new KeyHolder<K>( children[0].getValue().getLeftMostKey() ) ); //2
if ( index > 2 )
{
// Copy the keys before the deletion point
System.arraycopy( getKeys(), 0, newNode.getKeys(), 1, index - 2 ); // 4
}
// Inject the modified key
newNode.setKey( index - 1, new KeyHolder<K>( mergedResult.getModifiedPage().getLeftMostKey() ) ); // 3
if ( index < nbElems )
{
// Add copy the remaining keys after the deletion point
System.arraycopy( getKeys(), index, newNode.getKeys(), index, nbElems - index ); // 5
// Copy the remaining children after the insertion point
System.arraycopy( children, index + 1, newNode.children, index + 1, nbElems - index ); // 8
}
// Copy the children before the insertion point
System.arraycopy( children, 0, newNode.children, 1, index - 1 ); // 6
// Insert the modified page
Page<K, V> modifiedPage = mergedResult.getModifiedPage();
newNode.children[index] = new PageHolder<K, V>( btree, modifiedPage ); // 7
}
// Create the result
DeleteResult<K, V> result = new BorrowedFromLeftResult<K, V>( mergedResult.getCopiedPages(), newNode,
newSibling,
mergedResult.getRemovedElement() );
result.addCopiedPage( this );
result.addCopiedPage( sibling );
return result;
}
/**
* We have to merge the node with its sibling, both have N/2 elements before the element
* removal.
*
* @param revision The revision
* @param mergedResult The result of the merge
* @param sibling The Page we will merge the current page with
* @param isLeft Tells if the sibling is on the left
* @param pos The position of the key that has been removed
* @return The page resulting of the merge
* @throws IOException If we have an error while trying to access the page
*/
private DeleteResult<K, V> mergeWithSibling( long revision, MergedWithSiblingResult<K, V> mergedResult,
InMemoryNode<K, V> sibling, boolean isLeft, int pos ) throws IOException
{
// Create the new node. It will contain N - 1 elements (the maximum number)
// as we merge two nodes that contain N/2 elements minus the one we remove
InMemoryNode<K, V> newNode = new InMemoryNode<K, V>( btree, revision, btree.getPageSize() );
Tuple<K, V> removedElement = mergedResult.getRemovedElement();
int half = btree.getPageSize() / 2;
int index = Math.abs( pos );
if ( isLeft )
{
// The sibling is on the left. Copy all of its elements in the new node first
System.arraycopy( sibling.getKeys(), 0, newNode.getKeys(), 0, half ); //1
System.arraycopy( sibling.children, 0, newNode.children, 0, half + 1 ); //2
// Then copy all the elements up to the deletion point
if ( index < 2 )
{
newNode.setKey( half, new KeyHolder<K>( mergedResult.getModifiedPage().getLeftMostKey() ) );
System.arraycopy( getKeys(), 1, newNode.getKeys(), half + 1, half - 1 );
Page<K, V> modifiedPage = mergedResult.getModifiedPage();
newNode.children[half + 1] = new PageHolder<K, V>( btree, modifiedPage );
System.arraycopy( children, 2, newNode.children, half + 2, half - 1 );
}
else
{
// Copy the left part of the node keys up to the deletion point
// Insert the new key
newNode.setKey( half, new KeyHolder<K>( children[0].getValue().getLeftMostKey() ) ); // 3
if ( index > 2 )
{
System.arraycopy( getKeys(), 0, newNode.getKeys(), half + 1, index - 2 ); //4
}
// Inject the new merged key
newNode.setKey( half + index - 1, new KeyHolder<K>( mergedResult.getModifiedPage().getLeftMostKey() ) ); //5
if ( index < half )
{
System.arraycopy( getKeys(), index, newNode.getKeys(), half + index, half - index ); //6
System.arraycopy( children, index + 1, newNode.children, half + index + 1, half - index ); //9
}
// Copy the children before the deletion point
System.arraycopy( children, 0, newNode.children, half + 1, index - 1 ); // 7
// Inject the new merged child
Page<K, V> modifiedPage = mergedResult.getModifiedPage();
newNode.children[half + index] = new PageHolder<K, V>( btree, modifiedPage ); //8
}
}
else
{
// The sibling is on the right.
if ( index < 2 )
{
// Copy the keys
System.arraycopy( getKeys(), 1, newNode.getKeys(), 0, half - 1 );
// Insert the first child
Page<K, V> modifiedPage = mergedResult.getModifiedPage();
newNode.children[0] = new PageHolder<K, V>( btree, modifiedPage );
// Copy the node children
System.arraycopy( children, 2, newNode.children, 1, half - 1 );
}
else
{
// Copy the keys and children before the deletion point
if ( index > 2 )
{
// Copy the first keys
System.arraycopy( getKeys(), 0, newNode.getKeys(), 0, index - 2 ); //1
}
// Copy the first children
System.arraycopy( children, 0, newNode.children, 0, index - 1 ); //6
// Inject the modified key
newNode.setKey( index - 2, new KeyHolder<K>( mergedResult.getModifiedPage().getLeftMostKey() ) ); //2
// Inject the modified children
Page<K, V> modifiedPage = mergedResult.getModifiedPage();
newNode.children[index - 1] = new PageHolder<K, V>( btree, modifiedPage ); // 7
// Add the remaining node's key if needed
if ( index < half )
{
System.arraycopy( getKeys(), index, newNode.getKeys(), index - 1, half - index ); //5
// Add the remining children if below half
System.arraycopy( children, index + 1, newNode.children, index, half - index ); // 8
}
}
// Inject the new key from sibling
newNode.setKey( half - 1, new KeyHolder<K>( sibling.findLeftMost().getKey() ) ); //3
// Copy the sibling keys
System.arraycopy( sibling.getKeys(), 0, newNode.getKeys(), half, half );
// Add the sibling children
System.arraycopy( sibling.children, 0, newNode.children, half, half + 1 ); // 9
}
// And create the result
DeleteResult<K, V> result = new MergedWithSiblingResult<K, V>( mergedResult.getCopiedPages(), newNode,
removedElement );
result.addCopiedPage( this );
result.addCopiedPage( sibling );
return result;
}
/**
* {@inheritDoc}
*/
/* no qualifier */ DeleteResult<K, V> delete( K key, V value, long revision, Page<K, V> parent, int parentPos )
throws IOException
{
// We first try to delete the element from the child it belongs to
// Find the key in the page
int pos = findPos( key );
boolean found = pos < 0;
int index = pos;
Page<K, V> child = null;
DeleteResult<K, V> deleteResult = null;
if ( found )
{
index = -( pos + 1 );
child = children[-pos].getValue();
deleteResult = ((AbstractPage<K, V>)child).delete( key, value, revision, this, -pos );
}
else
{
child = children[pos].getValue();
deleteResult = ((AbstractPage<K, V>)child).delete( key, value, revision, this, pos );
}
// If the key is not present in the tree, we simply return
if ( deleteResult instanceof NotPresentResult )
{
// Nothing to do...
return deleteResult;
}
// If we just modified the child, return a modified page
if ( deleteResult instanceof RemoveResult )
{
RemoveResult<K, V> removeResult = handleRemoveResult( ( RemoveResult<K, V> ) deleteResult, index, pos,
found );
return removeResult;
}
// If we had to borrow an element in the child, then have to update
// the current page
if ( deleteResult instanceof BorrowedFromSiblingResult )
{
RemoveResult<K, V> removeResult = handleBorrowedResult( ( BorrowedFromSiblingResult<K, V> ) deleteResult,
pos );
return removeResult;
}
// Last, not least, we have merged two child pages. We now have to remove
// an element from the local page, and to deal with the result.
if ( deleteResult instanceof MergedWithSiblingResult )
{
MergedWithSiblingResult<K, V> mergedResult = ( MergedWithSiblingResult<K, V> ) deleteResult;
// If the parent is null, then this page is the root page.
if ( parent == null )
{
RemoveResult<K, V> result = handleRootRemove( mergedResult, pos, found );
return result;
}
// We have some parent. Check if the current page is not half full
int halfSize = btree.getPageSize() / 2;
if ( nbElems > halfSize )
{
// The page has more than N/2 elements.
// We simply remove the element from the page, and if it was the leftmost,
// we return the new pivot (it will replace any instance of the removed
// key in its parents)
RemoveResult<K, V> result = removeKey( mergedResult, revision, pos );
return result;
}
else
{
// We will remove one element from a page that will have less than N/2 elements,
// which will lead to some reorganization : either we can borrow an element from
// a sibling, or we will have to merge two pages
int siblingPos = selectSibling( parent, parentPos );
InMemoryNode<K, V> sibling = ( InMemoryNode<K, V> ) ( ( ( InMemoryNode<K, V> ) parent ).children[siblingPos]
.getValue() );
if ( sibling.getNbElems() > halfSize )
{
// The sibling contains enough elements
// We can borrow the element from the sibling
if ( siblingPos < parentPos )
{
DeleteResult<K, V> result = borrowFromLeft( revision, mergedResult, sibling, pos );
return result;
}
else
{
// Borrow from the right
DeleteResult<K, V> result = borrowFromRight( revision, mergedResult, sibling, pos );
return result;
}
}
else
{
// We need to merge the sibling with the current page
DeleteResult<K, V> result = mergeWithSibling( revision, mergedResult, sibling,
( siblingPos < parentPos ), pos );
return result;
}
}
}
// We should never reach this point
return null;
}
/**
* The deletion in a children has moved an element from one of its sibling. The key
* is present in the current node.
* @param borrowedResult The result of the deletion from the children
* @param pos The position the key was found in the current node
* @return The result
* @throws IOException If we have an error while trying to access the page
*/
private RemoveResult<K, V> handleBorrowedResult( BorrowedFromSiblingResult<K, V> borrowedResult, int pos )
throws IOException
{
Page<K, V> modifiedPage = borrowedResult.getModifiedPage();
Page<K, V> modifiedSibling = borrowedResult.getModifiedSibling();
InMemoryNode<K, V> newPage = copy( revision );
if ( pos < 0 )
{
pos = -( pos + 1 );
if ( borrowedResult.isFromRight() )
{
// Update the keys
newPage.setKey( pos, new KeyHolder<K>( modifiedPage.findLeftMost().getKey() ) );
newPage.setKey( pos + 1, new KeyHolder<K>( modifiedSibling.findLeftMost().getKey() ) );
// Update the children
newPage.children[pos + 1] = new PageHolder<K, V>( btree, modifiedPage );
newPage.children[pos + 2] = new PageHolder<K, V>( btree, modifiedSibling );
}
else
{
// Update the keys
newPage.setKey( pos, new KeyHolder<K>( modifiedPage.findLeftMost().getKey() ) );
// Update the children
newPage.children[pos] = new PageHolder<K, V>( btree, modifiedSibling );
newPage.children[pos + 1] = new PageHolder<K, V>( btree, modifiedPage );
}
}
else
{
if ( borrowedResult.isFromRight() )
{
// Update the keys
newPage.setKey( pos, new KeyHolder<K>( modifiedSibling.findLeftMost().getKey() ) );
// Update the children
newPage.children[pos] = new PageHolder<K, V>( btree, modifiedPage );
newPage.children[pos + 1] = new PageHolder<K, V>( btree, modifiedSibling );
}
else
{
// Update the keys
newPage.setKey( pos - 1, new KeyHolder<K>( modifiedPage.findLeftMost().getKey() ) );
// Update the children
newPage.children[pos - 1] = new PageHolder<K, V>( btree, modifiedSibling );
newPage.children[pos] = new PageHolder<K, V>( btree, modifiedPage );
}
}
// Modify the result and return
RemoveResult<K, V> removeResult = new RemoveResult<K, V>( borrowedResult.getCopiedPages(), newPage,
borrowedResult.getRemovedElement() );
removeResult.addCopiedPage( this );
return removeResult;
}
/**
* Remove the key at a given position.
*
* @param mergedResult The page we will remove a key from
* @param revision The revision of the modified page
* @param pos The position into the page of the element to remove
* @return The modified page with the <K,V> element added
* @throws IOException If we have an error while trying to access the page
*/
private RemoveResult<K, V> removeKey( MergedWithSiblingResult<K, V> mergedResult, long revision, int pos )
throws IOException
{
// First copy the current page, but remove one element in the copied page
InMemoryNode<K, V> newNode = new InMemoryNode<K, V>( btree, revision, nbElems - 1 );
int index = Math.abs( pos ) - 2;
//
if ( index < 0 )
{
// Copy the keys and the children
System.arraycopy( getKeys(), 1, newNode.getKeys(), 0, newNode.nbElems );
Page<K, V> modifiedPage = mergedResult.getModifiedPage();
newNode.children[0] = new PageHolder<K, V>( btree, modifiedPage );
System.arraycopy( children, 2, newNode.children, 1, nbElems - 1 );
}
else
{
// Copy the keys
if ( index > 0 )
{
System.arraycopy( getKeys(), 0, newNode.getKeys(), 0, index );
}
newNode.setKey( index, new KeyHolder<K>( mergedResult.getModifiedPage().findLeftMost().getKey() ) );
if ( index < nbElems - 2 )
{
System.arraycopy( getKeys(), index + 2, newNode.getKeys(), index + 1, nbElems - index - 2 );
}
// Copy the children
System.arraycopy( children, 0, newNode.children, 0, index + 1 );
Page<K, V> modifiedPage = mergedResult.getModifiedPage();
newNode.children[index + 1] = new PageHolder<K, V>( btree, modifiedPage );
if ( index < nbElems - 2 )
{
System.arraycopy( children, index + 3, newNode.children, index + 2, nbElems - index - 2 );
}
}
// Create the result
RemoveResult<K, V> result = new RemoveResult<K, V>( mergedResult.getCopiedPages(), newNode,
mergedResult.getRemovedElement() );
result.addCopiedPage( this );
return result;
}
/**
* Set the value at a give position
*
* @param pos The position in the values array
* @param value the value to inject
*/
/* no qualifier */void setValue( int pos, Page<K, V> value )
{
children[pos] = new PageHolder<K, V>( btree, value );
}
/**
* This method is used when we have to replace a child in a page when we have
* found the key in the tree (the value will be changed, so we have made
* copies of the existing pages).
*
* @param revision The current revision
* @param result The modified page
* @param pos The position of the found key
* @return A modified page
* @throws IOException If we have an error while trying to access the page
*/
private InsertResult<K, V> replaceChild( long revision, ModifyResult<K, V> result, int pos ) throws IOException
{
// Just copy the current page and update its revision
Page<K, V> newPage = copy( revision );
// Last, we update the children table of the newly created page
// to point on the modified child
Page<K, V> modifiedPage = result.getModifiedPage();
( ( InMemoryNode<K, V> ) newPage ).children[pos] = new PageHolder<K, V>( btree, modifiedPage );
// We can return the result, where we update the modifiedPage,
// to avoid the creation of a new object
result.setModifiedPage( newPage );
result.addCopiedPage( this );
return result;
}
/**
* Adds a new key into a copy of the current page at a given position. We return the
* modified page. The new page will have one more key than the current page.
*
* @param copiedPages the list of copied pages
* @param revision The revision of the modified page
* @param key The key to insert
* @param leftPage The left child
* @param rightPage The right child
* @param pos The position into the page
* @return The modified page with the <K,V> element added
* @throws IOException If we have an error while trying to access the page
*/
private InsertResult<K, V> insertChild( List<Page<K, V>> copiedPages, long revision, K key, Page<K, V> leftPage,
Page<K, V> rightPage, int pos )
throws IOException
{
// First copy the current page, but add one element in the copied page
InMemoryNode<K, V> newNode = new InMemoryNode<K, V>( btree, revision, nbElems + 1 );
// Copy the keys and the children up to the insertion position
if ( nbElems > 0 )
{
System.arraycopy( getKeys(), 0, newNode.getKeys(), 0, pos );
System.arraycopy( children, 0, newNode.children, 0, pos );
}
// Add the new key and children
newNode.setKey( pos, new KeyHolder<K>( key ) );
// If the BTree is managed, we now have to write the modified page on disk
// and to add this page to the list of modified pages
newNode.children[pos] = new PageHolder<K, V>( btree, leftPage );
newNode.children[pos + 1] = new PageHolder<K, V>( btree, rightPage );
// And copy the remaining keys and children
if ( nbElems > 0 )
{
System.arraycopy( getKeys(), pos, newNode.getKeys(), pos + 1, getKeys().length - pos );
System.arraycopy( children, pos + 1, newNode.children, pos + 2, children.length - pos - 1 );
}
// Create the result
InsertResult<K, V> result = new ModifyResult<K, V>( copiedPages, newNode, null );
result.addCopiedPage( this );
return result;
}
/**
* Splits a full page into two new pages, a left, a right and a pivot element. The new pages will
* each contains half of the original elements. <br/>
* The pivot will be computed, depending on the place
* we will inject the newly added element. <br/>
* If the newly added element is in the middle, we will use it
* as a pivot. Otherwise, we will use either the last element in the left page if the element is added
* on the left, or the first element in the right page if it's added on the right.
*
* @param copiedPages the list of copied pages
* @param revision The new revision for all the created pages
* @param pivot The key that will be move up after the split
* @param leftPage The left child
* @param rightPage The right child
* @param pos The position of the insertion of the new element
* @return An OverflowPage containing the pivot, and the new left and right pages
* @throws IOException If we have an error while trying to access the page
*/
private InsertResult<K, V> addAndSplit( List<Page<K, V>> copiedPages, long revision, K pivot, Page<K, V> leftPage,
Page<K, V> rightPage, int pos ) throws IOException
{
int middle = btree.getPageSize() >> 1;
// Create two new pages
InMemoryNode<K, V> newLeftPage = new InMemoryNode<K, V>( btree, revision, middle );
InMemoryNode<K, V> newRightPage = new InMemoryNode<K, V>( btree, revision, middle );
// Determinate where to store the new value
// If it's before the middle, insert the value on the left,
// the key in the middle will become the new pivot
if ( pos < middle )
{
// Copy the keys and the children up to the insertion position
System.arraycopy( getKeys(), 0, newLeftPage.getKeys(), 0, pos );
System.arraycopy( children, 0, newLeftPage.children, 0, pos );
// Add the new element
newLeftPage.setKey( pos, new KeyHolder<K>( pivot ) );
newLeftPage.children[pos] = new PageHolder<K, V>( btree, leftPage );
newLeftPage.children[pos + 1] = new PageHolder<K, V>( btree, rightPage );
// And copy the remaining elements minus the new pivot
System.arraycopy( getKeys(), pos, newLeftPage.getKeys(), pos + 1, middle - pos - 1 );
System.arraycopy( children, pos + 1, newLeftPage.children, pos + 2, middle - pos - 1 );
// Copy the keys and the children in the right page
System.arraycopy( getKeys(), middle, newRightPage.getKeys(), 0, middle );
System.arraycopy( children, middle, newRightPage.children, 0, middle + 1 );
// Create the result
InsertResult<K, V> result = new SplitResult<K, V>( copiedPages, getKey( middle - 1 ), newLeftPage,
newRightPage );
result.addCopiedPage( this );
return result;
}
else if ( pos == middle )
{
// A special case : the pivot will be propagated up in the tree
// The left and right pages will be spread on the two new pages
// Copy the keys and the children up to the insertion position (here, middle)
System.arraycopy( getKeys(), 0, newLeftPage.getKeys(), 0, middle );
System.arraycopy( children, 0, newLeftPage.children, 0, middle );
newLeftPage.children[middle] = new PageHolder<K, V>( btree, leftPage );
// And process the right page now
System.arraycopy( getKeys(), middle, newRightPage.getKeys(), 0, middle );
System.arraycopy( children, middle + 1, newRightPage.children, 1, middle );
newRightPage.children[0] = new PageHolder<K, V>( btree, rightPage );
// Create the result
InsertResult<K, V> result = new SplitResult<K, V>( copiedPages, pivot, newLeftPage, newRightPage );
result.addCopiedPage( this );
return result;
}
else
{
// Copy the keys and the children up to the middle
System.arraycopy( getKeys(), 0, newLeftPage.getKeys(), 0, middle );
System.arraycopy( children, 0, newLeftPage.children, 0, middle + 1 );
// Copy the keys and the children in the right page up to the pos
System.arraycopy( getKeys(), middle + 1, newRightPage.getKeys(), 0, pos - middle - 1 );
System.arraycopy( children, middle + 1, newRightPage.children, 0, pos - middle - 1 );
// Add the new element
newRightPage.setKey( pos - middle - 1, new KeyHolder<K>( pivot ) );
newRightPage.children[pos - middle - 1] = new PageHolder<K, V>( btree, leftPage );
newRightPage.children[pos - middle] = new PageHolder<K, V>( btree, rightPage );
// And copy the remaining elements minus the new pivot
System.arraycopy( getKeys(), pos, newRightPage.getKeys(), pos - middle, nbElems - pos );
System.arraycopy( children, pos + 1, newRightPage.children, pos + 1 - middle, nbElems - pos );
// Create the result
InsertResult<K, V> result = new SplitResult<K, V>( copiedPages, getKey( middle ), newLeftPage, newRightPage );
result.addCopiedPage( this );
return result;
}
}
/**
* Copies the current page and all its keys, with a new revision.
*
* @param revision The new revision
* @return The copied page
*/
protected InMemoryNode<K, V> copy( long revision )
{
InMemoryNode<K, V> newPage = new InMemoryNode<K, V>( btree, revision, nbElems );
// Copy the keys
System.arraycopy( getKeys(), 0, newPage.getKeys(), 0, nbElems );
// Copy the children
System.arraycopy( children, 0, newPage.children, 0, nbElems + 1 );
return newPage;
}
/**
* {@inheritDoc}
*/
public K getLeftMostKey()
{
return children[0].getValue().getLeftMostKey();
}
/**
* {@inheritDoc}
*/
public K getRightMostKey()
{
int index = ( nbElems + 1 ) - 1;
if ( children[index] != null )
{
return children[index].getValue().getRightMostKey();
}
return children[nbElems - 1].getValue().getRightMostKey();
}
/**
* {@inheritDoc}
*/
public boolean isLeaf()
{
return false;
}
/**
* {@inheritDoc}
*/
public boolean isNode()
{
return true;
}
/**
* @see Object#toString()
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "Node[" );
sb.append( super.toString() );
sb.append( "] -> {" );
if ( nbElems > 0 )
{
// Start with the first child
if ( children[0] == null )
{
sb.append( "null" );
}
else
{
sb.append( 'r' ).append( children[0].getValue().getRevision() );
}
for ( int i = 0; i < nbElems; i++ )
{
sb.append( "|<" ).append( getKey( i ) ).append( ">|" );
if ( children[i + 1] == null )
{
sb.append( "null" );
}
else
{
sb.append( 'r' ).append( children[i + 1].getValue().getRevision() );
}
}
}
sb.append( "}" );
return sb.toString();
}
}