/*
* Licensed 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 com.github.ggrandes.kvstore.structures.btree;
import java.util.Iterator;
import com.github.ggrandes.kvstore.holders.DataHolder;
import com.github.ggrandes.kvstore.structures.stack.IntStack;
import com.github.ggrandes.kvstore.structures.stack.ObjectStack;
import com.github.ggrandes.kvstore.utils.GenericFactory;
import org.apache.log4j.Logger;
/**
* <p>
* Abstract class Implementation of B+Tree.
* <p>
* This class is Thread-Safe.
*
* <p>
* Note that the iterator cannot be guaranteed to be thread-safe as it is,
* generally speaking, impossible to make any hard guarantees in the presence of
* unsynchronized concurrent modification.
*
* <p>
* All TreeEntry pairs returned by methods in this class and its views represent
* snapshots of mappings at the time they were produced. They do not support the
* Entry.setValue method.
*
* @author Guillermo Grandes / guillermo.grandes[at]gmail.com
* @references <a href="https://github.com/jankotek/JDBM3">JDBM3</a> / <a href="http://opendatastructures.org/">ODS</a>
*/
public abstract class BplusTree<K extends DataHolder<K>, V extends DataHolder<V>> implements Iterable<BplusTree.TreeEntry<K,V>> {
private static final Logger log = Logger.getLogger(BplusTree.class);
/**
* Minimal B-Order allowed for leaf/internal nodes
*/
public static final int MIN_B_ORDER = 5;
/**
* maximum number of children of a node (odd/impar number)
* <p><i> Note: density of node minimal (b-order/2), average (b-order*2/3)
*/
protected int b_order_internal;
/**
* maximum number of values of a node (odd/impar number)
* <p><i> Note: density of node minimal (b-order/2), average (b-order*2/3)
*/
protected int b_order_leaf;
/**
* default block size (for HDD normally 512 bytes)
*/
protected int blockSize = 512;
/**
* Tree is in valid state?
*/
protected boolean validState = false;
/**
* Read Only Mode
*/
protected boolean readOnly = false;
/**
* META-DATA: nodeId of the root node
*/
protected int rootIdx;
/**
* META-DATA: nodeId of the first/lower node
*/
protected int lowIdx;
/**
* META-DATA: nodeId of the last/higher node
*/
protected int highIdx;
/**
* META-DATA: elements stored in the tree
*/
protected int elements = 0;
/**
* META-DATA: height of the tree
*/
protected int height = 0;
// Factorias para creacion de objetos
protected final LeafNode<K, V> leafNodeFactory;
protected final InternalNode<K, V> internalNodeFactory;
private final GenericFactory<K> genericFactoryK;
private final GenericFactory<V> genericFactoryV;
private final K factoryK;
private final V factoryV;
/**
* Trace Internal Nodes (put/remove)
*/
private final ObjectStack<InternalNode<K, V>> stackNodes = new ObjectStack<InternalNode<K, V>>(16);
/**
* Trace slots (put/remove)
*/
private final IntStack stackSlots = new IntStack(16, Node.NULL_ID);
/**
* Create B+Tree
* @param autoTune if true the tree try to find best b-order for leaf/internal nodes to fit in a block of b_size bytes
* @param isMemory if true the tree is in Memory only
* @param b_size if autoTune is true is the blockSize, if false is the b-order for leaf/internal nodes
* @param typeK the class type of Keys
* @param typeV the class type of Values
* @throws InstantiationException
* @throws IllegalAccessException
*/
public BplusTree(final boolean autoTune, final boolean isMemory, int b_size, final Class<K> typeK, final Class<V> typeV) throws InstantiationException, IllegalAccessException {
genericFactoryK = new GenericFactory<K>(typeK);
genericFactoryV = new GenericFactory<V>(typeV);
factoryK = genericFactoryK.newInstance();
factoryV = genericFactoryV.newInstance();
if (autoTune) {
findOptimalNodeOrder(b_size);
}
else {
if (b_size < MIN_B_ORDER) b_size = MIN_B_ORDER; // minimal b-order
b_size += 1 - (b_size % 2); // round odd/impar
b_order_leaf = b_size;
b_order_internal = b_size;
if (!isMemory)
blockSize = roundBlockSize(getMaxStructSize(), blockSize);
}
//
leafNodeFactory = createLeafNode();
internalNodeFactory = createInternalNode();
if (log.isDebugEnabled()) {
log.debug(this.getClass().getName() + "::BplusTree() LeafNode b=" + b_order_leaf + " size=" + (isMemory ? -1 : leafNodeFactory.getStructMaxSize()));
log.debug(this.getClass().getName() + "::BplusTree() InternalNode b=" + b_order_internal+ " size=" + (isMemory ? -1 : internalNodeFactory.getStructMaxSize()));
if (!isMemory)
log.debug(this.getClass().getName() + "::BplusTree() FileStorageBlock blocksize=" + blockSize);
}
validState = false;
}
/**
* Return the highest nodeid allocated
* @return integer
*/
abstract public int getHighestNodeId();
/**
* Alloc a nodeid of type Leaf/Internal
* @param isLeaf
* @return integer with nodeid
*/
abstract protected int allocNode(final boolean isLeaf);
/**
* Get node from the underlying store
* @param nodeid int with nodeid
* @return Node<K,V>
*/
abstract protected Node<K, V> getNode(final int nodeid);
/**
* Put a node in the underlying store
* @param node
*/
abstract protected void putNode(final Node<K, V> node);
/**
* Free a node from the underlying store
* @param node
*/
abstract protected void freeNode(final Node<K, V> node);
/**
* This method is invoked after any operation in the
* tree that set/put a node from underlying store
*/
abstract protected void releaseNodes();
/**
* Clear the underlying storage and empty the tree
* @return boolean true/false if operation end succesfully
*/
abstract protected boolean clearStorage();
/**
* Mark Storage as Read Only if not already opened
*/
public synchronized void setReadOnly() {
if (validState) throw new InvalidStateException();
readOnly = true;
}
/**
* Clear the tree
*/
public synchronized void clear() {
if (readOnly)
return;
if (clearStorage())
clearStates();
}
/**
* Reset elements and create new root node
*/
protected void clearStates() {
// Create new Root
createRootNode();
}
/**
* Return the number of elements in the tree
* @return
*/
public synchronized int size() {
if (!validState) throw new InvalidStateException();
return elements;
}
/**
* Returns true if this tree contains no key-value mappings.
* @return true if this tree contains no key-value mappings
*/
public synchronized boolean isEmpty() {
if (!validState) throw new InvalidStateException();
return _isEmpty();
}
/**
* Returns true if this tree contains no key-value mappings.
* @return true if this tree contains no key-value mappings
*/
private final boolean _isEmpty() {
return (elements == 0);
}
/**
* Create a LeafNode without alloc nodeid for this
* @return
*/
protected LeafNode<K, V> createLeafNode() {
return new LeafNode<K, V>(this);
}
/**
* Create an InternalNode without alloc nodeid for this
* @return
*/
protected InternalNode<K, V> createInternalNode() {
return new InternalNode<K, V>(this);
}
/**
* Create a Leaf root node and alloc nodeid for this
*/
protected void createRootNode() {
final Node<K, V> node = createLeafNode();
node.allocId();
rootIdx = node.id;
height = 1;
elements = 0;
putNode(node);
}
/**
* Get the maximal size for a node
* @return integer with the max size of a leaf / internal node
*/
private int getMaxStructSize() {
final int leafSize = (createLeafNode().getStructMaxSize());
final int internalSize = (createInternalNode().getStructMaxSize());
return Math.max(leafSize, internalSize);
}
/**
* Calculate optimal values for b-order to fit in a block of block_size
* @param block_size integer with block size (bytes)
*/
private void findOptimalNodeOrder(final int block_size) {
final Node<K, V> leaf = createLeafNode();
final Node<K, V> internal = createInternalNode();
// Get Maximal Size
final int nodeSize = Math.max(leaf.getStructEstimateSize(MIN_B_ORDER), internal.getStructEstimateSize(MIN_B_ORDER));
// Find minimal blockSize
blockSize = ((nodeSize > block_size) ? roundBlockSize(nodeSize, block_size) : block_size);
// Find b-order for Leaf Nodes
b_order_leaf = findOptimalNodeOrder(leaf);
// Find b-order for Internal Nodes
b_order_internal = findOptimalNodeOrder(internal);
//
}
/**
* Find b-order for a blockSize of this tree
* @param node of type Leaf or Integernal
* @return integer with b-order
*/
private int findOptimalNodeOrder(final Node<K, V> node) {
int low = MIN_B_ORDER; // minimal b-order
int high = (blockSize / node.getStructEstimateSize(1)) << 2; // estimate high b-order
while (low <= high) {
int mid = ((low + high) >>> 1);
mid += (1 - (mid % 2));
int nodeSize = node.getStructEstimateSize(mid);
if (log.isDebugEnabled())
log.debug(this.getClass().getName() + "::findOptimalNodeOrder(" + node.getClass().getName() + ") blockSize=" + blockSize + " nodeSize=" + nodeSize + " b_low=" + low + " b_order=" + mid + " b_high=" + high);
if (nodeSize < blockSize) {
low = mid + 2;
}
else if (nodeSize > blockSize) {
high = mid - 2;
}
else {
return mid;
}
}
return low-2;
}
/**
* Round a size with a blocksize
* @param size
* @param blocksize
* @return
*/
private int roundBlockSize(final int size, final int blocksize) {
return (((size / blocksize) + ((size % blocksize) == 0 ? 0 : 1)) * blocksize);
}
/**
* @return B-order of a InternalNode
*/
public int getBOrderInternal() {
return b_order_internal;
}
/**
* @return B-order of a LeafNode
*/
public int getBOrderLeaf() {
return b_order_leaf;
}
/**
* @return GenericFactory to create Keys
*/
protected GenericFactory<K> getGenericFactoryK() {
return genericFactoryK;
}
/**
* @return GenericFactory to create Values
*/
protected GenericFactory<V> getGenericFactoryV() {
return genericFactoryV;
}
/**
* @return Return Key
*/
protected K factoryK() {
return factoryK;
}
/**
* @return Return Value
*/
protected V factoryV() {
return factoryV;
}
/**
* Find node that can hold the key
* @param key
* @return LeafNode<K, V> containing the key or null if not found
*/
private final LeafNode<K, V> findLeafNode(final K key, final boolean tracePath) {
Node<K, V> node = getNode(rootIdx);
if (tracePath) {
stackNodes.clear();
stackSlots.clear();
}
while (!node.isLeaf()) {
final InternalNode<K, V> nodeInternal = (InternalNode<K, V>)node;
int slot = node.findSlotByKey(key);
slot = ((slot < 0) ? (-slot)-1 : slot+1);
if (tracePath) {
stackNodes.push(nodeInternal);
stackSlots.push(slot);
}
node = getNode(nodeInternal.childs[slot]);
if (node == null) {
log.error("ERROR childs["+slot+"] in node=" + nodeInternal);
return null;
}
}
return (node.isLeaf() ? (LeafNode<K, V>)node : null);
}
/**
* Returns the height of this tree.
* @return int with height of this tree, 0 is tree is empty
*/
public synchronized int getHeight() {
if (!validState) throw new InvalidStateException();
if (elements == 0) return 0;
// int height = 0;
// try {
// int nodeIdx = rootIdx;
// Node<K, V> nodeFind = getNode(nodeIdx);
// while (!nodeFind.isLeaf()) {
// ++height;
// nodeIdx = ((InternalNode<K, V>)nodeFind).childs[0];
// nodeFind = getNode(nodeIdx);
// }
// ++height;
// } finally {
// releaseNodes();
// }
return height;
}
/**
* Returns the first (lowest) key currently in this tree.
* @return key
*/
public synchronized K firstKey() {
if (!validState) throw new InvalidStateException();
final LeafNode<K, V> node = findSideLeafNode(true);
if (node == null) return null;
return node.keys[0];
}
/**
* Returns the last (highest) key currently in this tree.
* @return key
*/
public synchronized K lastKey() {
if (!validState) throw new InvalidStateException();
final LeafNode<K, V> node = findSideLeafNode(false);
if (node == null) return null;
return node.keys[node.allocated-1];
}
/**
* Returns the first (lowest) key currently in this tree.
* @return key
*/
public synchronized TreeEntry<K, V> firstEntry() {
if (!validState) throw new InvalidStateException();
final LeafNode<K, V> node = findSideLeafNode(true);
if (node == null) return null;
final K key = node.keys[0];
final V value = node.values[0];
return ((key == null) ? null : new TreeEntry<K, V>(key, value));
}
/**
* Returns the last (highest) key currently in this tree.
* @return key
*/
public synchronized TreeEntry<K, V> lastEntry() {
if (!validState) throw new InvalidStateException();
final LeafNode<K, V> node = findSideLeafNode(false);
if (node == null) return null;
final int slot = node.allocated-1;
final K key = node.keys[slot];
final V value = node.values[slot];
return ((key == null) ? null : new TreeEntry<K, V>(key, value));
}
/**
* Removes and returns a key-value mapping associated with the least key in this map, or null if the map is empty.
* @return entry
*/
public synchronized TreeEntry<K, V> pollFirstEntry() {
final TreeEntry<K, V> entry = firstEntry();
if (entry != null) remove(entry.getKey());
return entry;
}
/**
* Removes and returns a key-value mapping associated with the greatest key in this map, or null if the map is empty.
* @return entry
*/
public synchronized TreeEntry<K, V> pollLastEntry() {
final TreeEntry<K, V> entry = lastEntry();
if (entry != null) remove(entry.getKey());
return entry;
}
/**
* Return the first/low or last/high LeafNode in the Tree
* @param lowORhigh true first/low node, false last/high node
* @return the LeafNoder head/first/lower or tail/last/higher
*/
private final LeafNode<K, V> findSideLeafNode(final boolean lowORhigh) {
if (_isEmpty()) return null;
try {
int nodeIdx = (lowORhigh ? lowIdx : highIdx); // rootIdx;
Node<K, V> nodeFind = getNode((nodeIdx == 0) ? rootIdx : nodeIdx);
while (!nodeFind.isLeaf()) {
nodeIdx = ((InternalNode<K, V>)nodeFind).childs[(lowORhigh ? 0 : nodeFind.allocated)];
nodeFind = getNode(nodeIdx);
}
return (nodeFind.isLeaf() ? (LeafNode<K, V>)nodeFind : null);
} finally {
releaseNodes();
}
}
/**
* Returns the least key greater than or equal to the given key, or null if there is no such key.
* @param key the key
* @return the least key greater than or equal to key, or null if there is no such key
*/
public synchronized K ceilingKey(final K key) {
// Retorna la clave mas cercana mayor o igual a la clave indicada
return getRoundKey(key, true, true);
}
/**
* Returns the greatest key less than or equal to the given key, or null if there is no such key.
* @param key the key
* @return the greatest key less than or equal to key, or null if there is no such key
*/
public synchronized K floorKey(final K key) {
// Retorna la clave mas cercana menor o igual a la clave indicada
return getRoundKey(key, false, true);
}
/**
* Returns the least key strictly greater than the given key, or null if there is no such key.
* @param key the key
* @return the least key strictly greater than the given key, or null if there is no such key.
*/
public synchronized K higherKey(final K key) {
// Retorna la clave mas cercana mayor a la clave indicada
return getRoundKey(key, true, false);
}
/**
* Returns the greatest key strictly less than the given key, or null if there is no such key.
* @param key the key
* @return the greatest key strictly less than the given key, or null if there is no such key.
*/
public synchronized K lowerKey(final K key) {
// Retorna la clave mas cercana menor a la clave indicada
return getRoundKey(key, false, false);
}
/**
* Return ceilingKey, floorKey, higherKey or lowerKey
* @param key the key
* @param upORdown true returns ceilingKey, false floorKey
* @param acceptEqual true returns equal keys, otherwise, only higher or lower
* @return the key found or null if not found
*/
private final K getRoundKey(final K key, final boolean upORdown, final boolean acceptEqual) {
final TreeEntry<K, V> entry = getRoundEntry(key, upORdown, acceptEqual);
if (entry == null) return null;
return entry.getKey();
}
/**
* Returns the least key greater than or equal to the given key, or null if there is no such key.
* @param key the key
* @return the Entry with least key greater than or equal to key, or null if there is no such key
*/
public synchronized TreeEntry<K, V> ceilingEntry(final K key) {
// Retorna la clave mas cercana mayor o igual a la clave indicada
return getRoundEntry(key, true, true);
}
/**
* Returns the greatest key less than or equal to the given key, or null if there is no such key.
* @param key the key
* @return the Entry with greatest key less than or equal to key, or null if there is no such key
*/
public synchronized TreeEntry<K, V> floorEntry(final K key) {
// Retorna la clave mas cercana menor o igual a la clave indicada
return getRoundEntry(key, false, true);
}
/**
* Returns the least key strictly greater than the given key, or null if there is no such key.
* @param key the key
* @return the Entry with least key strictly greater than the given key, or null if there is no such key.
*/
public synchronized TreeEntry<K, V> higherEntry(final K key) {
// Retorna la clave mas cercana mayor a la clave indicada
return getRoundEntry(key, true, false);
}
/**
* Returns the greatest key strictly less than the given key, or null if there is no such key.
* @param key the key
* @return the Entry with greatest key strictly less than the given key, or null if there is no such key.
*/
public synchronized TreeEntry<K, V> lowerEntry(final K key) {
// Retorna la clave mas cercana menor a la clave indicada
return getRoundEntry(key, false, false);
}
/**
* Return ceilingEntry, floorEntry, higherEntry or lowerEntry
* @param key the key
* @param upORdown true returns ceiling/higher, false floor/lower
* @param acceptEqual true returns equal keys, otherwise, only higher or lower
* @return the Entry found or null if not found
*/
private final TreeEntry<K, V> getRoundEntry(final K key, final boolean upORdown, final boolean acceptEqual) {
if (!validState) throw new InvalidStateException();
if (_isEmpty()) return null;
if (key == null) return null;
try {
LeafNode<K, V> node = findLeafNode(key, false);
if (node == null) return null;
int slot = node.findSlotByKey(key);
if (upORdown) {
// ceiling / higher
slot = ((slot < 0) ? (-slot)-1 : (acceptEqual ? slot : slot+1));
if (slot >= node.allocated) {
node = node.nextNode();
if (node == null) return null;
slot = 0;
}
}
else {
// floor / lower
slot = ((slot < 0) ? (-slot)-2 : (acceptEqual ? slot : slot-1));
if (slot < 0) {
node = node.prevNode();
if (node == null) return null;
slot = node.allocated-1;
}
}
return ((node.keys[slot] == null) ? null : new TreeEntry<K, V>(node.keys[slot], node.values[slot]));
} finally {
releaseNodes();
}
}
/**
* Returns true if this tree contains the specified key.
* @param key to find
* @return true if found
*/
public boolean containsKey(final K key) {
return (get(key) != null);
}
/**
* Find a Key in the Tree
* @param key to find
* @return Value found or null if not
*/
public synchronized V get(final K key) {
if (!validState) throw new InvalidStateException();
if (_isEmpty()) return null;
if (key == null) return null;
try {
final LeafNode<K, V> node = findLeafNode(key, false);
if (node == null) return null;
int slot = node.findSlotByKey(key);
if (slot >= 0) {
return node.values[slot];
}
return null;
} finally {
releaseNodes();
}
}
/**
* Remove a key from key
* @param key to delete
* @return true if key was removed, false if not
*/
public synchronized boolean remove(final K key) {
if (readOnly)
return false;
if (!validState) throw new InvalidStateException();
if (key == null) return false;
try {
if (log.isDebugEnabled())
log.debug("trying remove key=" + key);
submitRedoRemove(key);
if (removeIterative(key)) { // removeRecursive(key, rootIdx)
elements--;
Node<K, V> nodeRoot = getNode(rootIdx);
if (nodeRoot.isEmpty() && (elements > 0)) { // root has only one child
// Si sale un ClassCastException es porque hay algun error en la cuenta de elementos
rootIdx = ((InternalNode<K, V>)nodeRoot).childs[0];
// Clean old nodeRoot
freeNode(nodeRoot);
if (log.isDebugEnabled())
log.debug("DECREASES TREE HEIGHT (ROOT): elements=" + elements + " oldRoot=" + nodeRoot.id + " newRoot=" + rootIdx);
height--; // tree height
}
else if (nodeRoot.isEmpty() && nodeRoot.isLeaf() && (elements == 0) && (getHighestNodeId() > 4096)) {
// Hace un reset del arbol para liberar espacio de modo rapido
if (log.isDebugEnabled())
log.debug("RESET TREE: elements=" + elements + " leaf=" + nodeRoot.isLeaf() + " empty=" + nodeRoot.isEmpty() + " id=" + nodeRoot.id + " lastNodeId=" + getHighestNodeId() + " nodeRoot=" + nodeRoot);
clear();
}
else if ((elements == 0) && (!nodeRoot.isLeaf() || !nodeRoot.isEmpty())) {
log.error("ERROR in TREE: elements=" + elements + " rootLeaf=" + nodeRoot.isLeaf() + " rootEmpty=" + nodeRoot.isEmpty() + " rootId=" + nodeRoot.id + " nodeRoot=" + nodeRoot);
}
return true;
}
return false;
} finally {
stackNodes.clear();
stackSlots.clear();
releaseNodes();
}
}
/**
* Remove the Key from the subtree rooted at the node with nodeid (this function is recursive)
*
* @param key to delete
* @param nodeid of the subtree to remove key from
* @return true if key was removed and false otherwise
*/
protected boolean removeRecursive(final K key, final int nodeid) {
if (nodeid == Node.NULL_ID) return false; // NOT FOUND
Node<K, V> nodeDelete = getNode(nodeid);
if (log.isDebugEnabled())
log.debug("trying removeRecursive nodeDelete=" + nodeDelete + " key=" + key);
int slot = nodeDelete.findSlotByKey(key);
if (nodeDelete.isLeaf()) {
if (slot < 0) {
if (log.isDebugEnabled())
log.debug("NOT FOUND nodeDelete=" + nodeDelete + " key=" + key);
return false; // NOT FOUND
}
nodeDelete.remove(slot);
putNode(nodeDelete);
return true;
}
slot = ((slot < 0) ? (-slot)-1 : slot+1);
final InternalNode<K, V> nodeDeleteInternal = (InternalNode<K, V>) nodeDelete;
if (removeRecursive(key, nodeDeleteInternal.childs[slot])) {
nodeDeleteInternal.checkUnderflow(slot);
return true;
}
return false;
}
/**
* Remove the Key from the tree (this function is iterative)
*
* @param key to delete
* @return true if key was removed and false otherwise
*/
protected boolean removeIterative(final K key) {
final LeafNode<K, V> nodeLeaf = findLeafNode(key, true);
//
// Find in leaf node for key and delete it
final int slot = nodeLeaf.findSlotByKey(key);
if (slot >= 0) { // found
// Remove Key
nodeLeaf.remove(slot);
putNode(nodeLeaf);
// Iterate back over nodes checking underflow
while (!stackNodes.isEmpty()) {
final InternalNode<K, V> node = stackNodes.pop();
final int slotcheck = stackSlots.pop();
if (!node.checkUnderflow(slotcheck)) {
return true;
}
}
return true;
}
return false;
}
/**
* submit put to redo
* @param key
* @param value
*/
abstract protected void submitRedoPut(final K key, final V value);
/**
* submit remove to redo
* @param value
*/
abstract protected void submitRedoRemove(final K key);
/**
* submit metadata to redo
* @param value
*/
abstract protected void submitRedoMeta(final int value);
/**
* Put the value in the tree (if key already exists, update value)
* @param key
* @param value
* @return true if operation is ok, else false
*/
public synchronized boolean put(final K key, final V value) {
if (readOnly)
return false;
if (!validState) throw new InvalidStateException();
if (key == null) return false;
if (value == null) return false;
try {
submitRedoPut(key, value);
Node<K, V> splitedNode;
try {
splitedNode = putIterative(key, value); // putRecursive(key, value, rootIdx);
}
catch (DuplicateKeyException e) {
return false;
}
if (splitedNode != null) { // root was split, make new root
InternalNode<K, V> nodeRootNew = createInternalNode();
nodeRootNew.allocId();
if (log.isDebugEnabled())
log.debug("INCREASES TREE HEIGHT (ROOT): elements=" + elements + " oldRoot=" + rootIdx + " newRoot=" + nodeRootNew.id);
final K newkey = splitedNode.splitShiftKeysLeft();
putNode(splitedNode);
nodeRootNew.childs[0] = rootIdx;
nodeRootNew.keys[0] = newkey;
nodeRootNew.childs[1] = splitedNode.id;
nodeRootNew.allocated++;
rootIdx = nodeRootNew.id;
putNode(nodeRootNew);
height++; // tree height
}
elements++;
return true;
} finally {
stackNodes.clear();
stackSlots.clear();
releaseNodes();
}
}
/**
* Put the key/value in the subtree rooted for nodeid (this function is recursive)
*
* If node is split by this operation then the return value is the Node
* that was created when node was split
*
* @param key the key to add
* @param value the value to add
* @param nodeid the nodeid
* @return a new node that was created when node was split, or null if u was not split
* @throws DuplicateKeyException
*/
protected Node<K, V> putRecursive(K key, final V value, final int nodeid) throws DuplicateKeyException {
final Node<K, V> nodeFind = getNode(nodeid);
if (nodeFind == null) {
if (log.isDebugEnabled())
log.debug(this.getClass().getName() + "::putRecursive getNode("+nodeid+")=null");
}
int slot = nodeFind.findSlotByKey(key);
if (slot >= 0) {
if (nodeFind.isLeaf()) { // leaf node, just reset it
final LeafNode<K, V> node = (LeafNode<K, V>)nodeFind;
node.set(slot, value);
putNode(node);
throw new DuplicateKeyException();
}
}
slot = ((slot < 0) ? (-slot)-1 : slot+1);
if (nodeFind.isLeaf()) { // leaf node, just add it
final LeafNode<K, V> node = (LeafNode<K, V>)nodeFind;
node.add(slot, key, value);
putNode(node);
}
else {
final InternalNode<K, V> node = (InternalNode<K, V>)nodeFind;
final Node<K, V> splitedNode = putRecursive(key, value, node.childs[slot]);
if (splitedNode != null) { // child was split, splitedNode is new child
key = splitedNode.splitShiftKeysLeft();
putNode(splitedNode);
node.add(slot, key, splitedNode.id);
putNode(node);
}
}
return nodeFind.isFull() ? nodeFind.split() : null;
}
/**
* Put the key/value in the tree (this function is iterative)
*
* If root node is split by this operation then the return value is the Node
* that was created when root node was split
*
* @param key the key to add
* @param value the value to add
* @return a new node that was created when root node was split, or null if u was not split
* @throws DuplicateKeyException
*/
protected Node<K, V> putIterative(final K key, final V value) throws DuplicateKeyException {
final LeafNode<K, V> nodeLeaf = findLeafNode(key, true);
if (nodeLeaf == null) {
final StringBuilder sb = new StringBuilder();
while (!stackNodes.isEmpty()) {
sb.append("\n").append(stackNodes.pop());
}
throw new NullPointerException("findLeafNode("+key+", true)==null:" + sb.toString());
}
//
// Find in leaf node for key
int slot = nodeLeaf.findSlotByKey(key);
if (slot >= 0) { // found, update
nodeLeaf.set(slot, value);
putNode(nodeLeaf);
throw new DuplicateKeyException();
}
//
// not found, add
slot = (-slot)-1;
Node<K, V> splitedNode = null;
nodeLeaf.add(slot, key, value);
putNode(nodeLeaf);
splitedNode = (nodeLeaf.isFull() ? nodeLeaf.split() : null);
//
// Iterate back over nodes checking overflow / splitting
while (!stackNodes.isEmpty()) {
final InternalNode<K, V> node = stackNodes.pop();
slot = stackSlots.pop();
if (splitedNode != null) {
// split occurred in previous phase, splitedNode is new child
final K childKey = splitedNode.splitShiftKeysLeft();
putNode(splitedNode);
node.add(slot, childKey, splitedNode.id);
putNode(node);
}
splitedNode = (node.isFull() ? node.split() : null);
}
return splitedNode;
}
/**
* Return a string version of the tree
*/
public synchronized String toString() {
if (!validState) throw new InvalidStateException();
try {
return toStringIterative();
} finally {
stackNodes.clear();
stackSlots.clear();
releaseNodes();
}
}
/**
* A iterative algorithm for converting this tree into a string
* @return String representing human readable tree
*/
private String toStringIterative() {
final String PADDING = " ";
final StringBuilder sb = new StringBuilder();
int elements_debug_local_recounter = 0;
Node<K, V> node = null;
int nodeid = rootIdx;
int depth = 0;
stackSlots.clear();
stackSlots.push(rootIdx); // init seed, root node
boolean lastIsInternal = !Node.isLeaf(rootIdx);
while (!stackSlots.isEmpty()) {
nodeid = stackSlots.pop();
node = getNode(nodeid);
if (!node.isLeaf()) {
for (int i = node.allocated; i >= 0; i--) {
stackSlots.push(((InternalNode<K, V>)node).childs[i]);
}
}
else {
elements_debug_local_recounter += node.allocated;
}
// For Indentation
if (lastIsInternal || !node.isLeaf()) { // Last or Curret are Internal
depth += (lastIsInternal ? +1 : -1);
}
lastIsInternal = !node.isLeaf();
sb.append(PADDING.substring(0, Math.min(PADDING.length(), Math.max(depth-1, 0))));
//
sb.append(node.toString()).append("\n");
}
// Count elements
sb
.append("height=").append(getHeight())
.append(" root=").append(rootIdx)
.append(" low=").append(lowIdx)
.append(" high=").append(highIdx)
.append(" elements=").append(elements)
.append(" recounter=").append(elements_debug_local_recounter);
return sb.toString();
}
/**
* A recursive algorithm for converting this tree into a string
* @return String representing human readable tree
*/
@SuppressWarnings("unused")
private String toStringRecursive() {
final StringBuilder sb = new StringBuilder();
int elements_debug_local_recounter = toString(rootIdx, sb, 0);
sb
.append("height=").append(getHeight())
.append(" root=").append(rootIdx)
.append(" low=").append(lowIdx)
.append(" high=").append(highIdx)
.append(" elements=").append(elements)
.append(" recounter=").append(elements_debug_local_recounter);
return sb.toString();
}
/**
* A recursive algorithm for converting this tree into a string
*
* @param nodeid the subtree to add to the the string
* @param sb a StringBuilder for building the string
*/
private int toString(final int nodeid, final StringBuilder sb, final int depth) {
if (nodeid == Node.NULL_ID) return 0;
final Node<K, V> node = getNode(nodeid);
int elements_debug_local_recounter = 0;
if (node == null) {
if (log.isDebugEnabled())
log.debug(this.getClass().getName() + "::toString() getNode("+nodeid+")=null");
return 0;
}
int i = 0;
sb
.append(" ".substring(0, depth))
//.append("[").append(nodeIdx).append("]")
.append(node.toString()).append("\n");
if (node.isLeaf()) {
elements_debug_local_recounter += node.allocated;
}
while((i < node.allocated) && (node.keys[i] != null)) {
if (!node.isLeaf()) {
elements_debug_local_recounter += toString(((InternalNode<K, V>)node).childs[i], sb, depth+1);
}
i++;
}
if (!node.isLeaf()) {
elements_debug_local_recounter += toString(((InternalNode<K, V>)node).childs[i], sb, depth+1);
}
return elements_debug_local_recounter;
}
// ========== Iterator
/**
* Note that the iterator cannot be guaranteed to be thread-safe as it is,
* generally speaking, impossible to make any hard guarantees in the
* presence of unsynchronized concurrent modification.
*
* @returns iterator over values in map
*/
public Iterator<BplusTree.TreeEntry<K, V>> iterator() {
return new TreeIterator<BplusTree.TreeEntry<K, V>>(this);
}
class TreeIterator<T extends BplusTree.TreeEntry<K, V>> implements Iterator<T> {
final BplusTree<K, V> tree;
T lastReturned = null;
T nextElement = null;
boolean hasBegin = false;
TreeIterator(final BplusTree<K, V> tree) {
this.tree = tree;
}
@SuppressWarnings("unchecked")
private final T nextEntry() {
if (!hasBegin) {
nextElement = (T)tree.firstEntry();
return nextElement;
}
else {
if (lastReturned.getKey().compareTo(nextElement.getKey()) >= 0) {
nextElement = (T)tree.higherEntry(lastReturned.getKey());
}
}
return (nextElement);
}
@Override
public boolean hasNext() {
return (nextEntry() != null);
}
@Override
public T next() {
lastReturned = nextEntry();
hasBegin = true;
if (nextElement == null)
throw new java.util.NoSuchElementException();
return nextElement;
}
@Override
public void remove() {
if (lastReturned == null)
throw new IllegalStateException();
tree.remove(lastReturned.getKey());
}
}
// ========== Exceptions
/**
* Exception throwed then set a key already existent in the tree
*/
static class DuplicateKeyException extends Exception {
private static final long serialVersionUID = 42L;
}
/**
* Exception throwed then MetaData is invalid
*/
public static class InvalidDataException extends Exception {
private static final long serialVersionUID = 42L;
public InvalidDataException(final String str) {
super(str);
}
}
/**
* Exception throwed when Tree is in invalid state (closed)
*/
public static class InvalidStateException extends RuntimeException {
private static final long serialVersionUID = 42L;
}
// ========== TreeEntry
/**
* A map entry (key-value pair). These TreeEntry are read-only objects.
*
* @param <K> the key
* @param <V> the value
*/
public static class TreeEntry<K, V> implements java.util.Map.Entry<K,V> {
private final K key;
private final V value;
private TreeEntry(final K key, final V value) {
this.key = key;
this.value = value;
}
/**
* Returns the key corresponding to this entry.
* @return the key corresponding to this entry
*/
@Override
public K getKey() {
return key;
}
/**
* Returns the value corresponding to this entry.
* @return the value corresponding to this entry
*/
@Override
public V getValue() {
return value;
}
/**
* operation not supported.
* @param value
* @throws UnsupportedOperationException operation is not supported
*/
@Override
public V setValue(final V value) {
throw new UnsupportedOperationException();
}
/**
* Compares the specified object with this entry for equality. Returns true if the given object is also a map entry and the two entries represent the same mapping.
* @param other object to compare
* @return boolean true if equals
*/
@Override
public boolean equals(final Object other) {
if (other == null) return false;
if (!(other instanceof TreeEntry)) return false;
//
final TreeEntry<K, V> e1 = this;
@SuppressWarnings("unchecked")
final TreeEntry<K, V> e2 = (TreeEntry<K, V>) other;
//
final boolean keyEquals = ((e1.getKey()==null) ? (e2.getKey()==null) : e1.getKey().equals(e2.getKey()));
final boolean valueEquals = ((e1.getValue()==null) ? (e2.getValue()==null) : e1.getValue().equals(e2.getValue()));
///
return (keyEquals && valueEquals);
}
/**
* Returns a string representation of the object.
*/
@Override
public String toString() {
return (key == null ? "null" : key.toString()) + "=" + (value == null? "null" : value.toString());
}
}
}