/*
* 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.nio.ByteBuffer;
import java.util.Arrays;
import com.github.ggrandes.kvstore.holders.DataHolder;
import org.apache.log4j.Logger;
/**
* Generic Node definition
* This class is NOT Thread-Safe
*
* @param <K>
* @param <V>
*
* @author Guillermo Grandes / guillermo.grandes[at]gmail.com
*/
public abstract class Node<K extends DataHolder<K>, V extends DataHolder<V>> {
private static final Logger log = Logger.getLogger(Node.class);
public static final int NULL_ID = 0;
//
public final BplusTree<K, V> tree;
public final K[] keys;
//
public int id = Node.NULL_ID;
public int allocated = 0;
//
protected Node(final BplusTree<K, V> tree) {
this.tree = tree;
this.keys = tree.getGenericFactoryK().newArray(getBOrder());
}
public int allocId() {
id = tree.allocNode(isLeaf());
// Dont do this here, do after allocId or after split
//tree.putNode(this);
return id;
}
public static boolean isLeaf(final int id) {
return (id > 0);
}
/**
* Searches using the binary search algorithm.
* {@link Arrays#binarySearch(Object[], int, int, Object)}
* @param key the value to be searched for
* @return index of the search key, if it is contained in the array
* within the specified range;
* otherwise, <tt>(-(<i>insertion point</i>) - 1)</tt>. The
* <i>insertion point</i> is defined as the point at which the
* key would be inserted into the array: the index of the first
* element in the range greater than the key,
* or <tt>toIndex</tt> if all
* elements in the range are less than the specified key. Note
* that this guarantees that the return value will be >= 0 if
* and only if the key is found.
*/
public final int findSlotByKey(final K searchKey) {
//return Arrays.binarySearch(keys, 0, allocated, searchKey);
int low = 0;
int high = allocated - 1;
while (low <= high) {
final int mid = (low + high) >>> 1;
final K midVal = keys[mid];
final int cmp = midVal.compareTo(searchKey);
if (cmp < 0) {
low = mid + 1;
}
else if (cmp > 0) {
high = mid - 1;
}
else {
return mid; // key found
}
}
return -(low + 1); // key not found.
}
public boolean isEmpty() { // node empty
return (allocated <= 0);
}
public boolean isFull() { // node is full
if (log.isDebugEnabled()) log.debug("allocated=" + allocated + " keys.length=" + keys.length);
return (allocated >= keys.length);
}
public boolean isUnderFlow() {
return (allocated < (keys.length >> 1));
}
public boolean canMerge(final Node<K, V> other) {
return ((allocated + other.allocated + 1) < keys.length); // TODO: revisar el +1
}
protected void clear() {
Arrays.fill(keys, null);
allocated = 0;
}
protected void delete() {
clear();
allocated = Integer.MIN_VALUE;
}
protected boolean isDeleted() {
return (allocated == Integer.MIN_VALUE);
}
// insert element
protected void moveElementsRight(final Object[] elements, final int srcPos) {
if (log.isDebugEnabled()) log.debug("moveElementsRight("+srcPos+") allocated=" + allocated + ":" + keys.length + ":" + (allocated-srcPos) + ":" + (keys.length-srcPos-1));
System.arraycopy(elements, srcPos, elements, srcPos+1, allocated-srcPos);
}
// remove element
protected void moveElementsLeft(final Object[] elements, final int srcPos) {
if (log.isDebugEnabled()) log.debug("moveElementsLeft("+srcPos+") allocated=" + allocated + ":" + keys.length + ":" + (allocated-srcPos-1) + ":" + (keys.length-srcPos-1));
System.arraycopy(elements, srcPos+1, elements, srcPos, allocated-srcPos-1);
}
public int countKeys() {
int low = 0, high = keys.length;
while (high != low) {
int middle = (high+low)/2;
if (keys[middle] == null) {
high = middle;
}
else {
low = middle+1;
}
}
return low;
}
// ========= Serialization =========
public int getStructMaxSize() {
final K factoryK = tree.factoryK();
return (4 + 2 + (keys.length * factoryK.byteLength()));
}
public int getStructEstimateSize(final int b) {
final K factoryK = tree.factoryK();
return (4 + 2 + (b * factoryK.byteLength()));
}
public void serialize(final ByteBuffer buf) {
buf.clear();
buf.putInt(id); // 4 bytes
buf.putShort((short)(allocated & 0x7FFF)); // 2 bytes
for (int i = 0; i < allocated; i++) { // X bytes * b_order
keys[i].serialize(buf);
}
}
public final void clean(final ByteBuffer buf) {
buf.clear();
//buf.putInt(0); // 4 bytes
//buf.putShort((short)0); // 2 bytes
buf.putLong(0); // 8 bytes
buf.flip();
}
public static <K extends DataHolder<K>, V extends DataHolder<V>> Node<K, V> deserialize(final ByteBuffer buf, final BplusTree<K, V> tree) {
final int id = buf.getInt();
if (id == NULL_ID) {
throw InvalidNodeID.NULL_ID;
}
final boolean isLeaf = isLeaf(id);
final Node<K, V> node = (isLeaf ? tree.createLeafNode() : tree.createInternalNode());
node.id = id;
return node.deserializeNode(buf);
}
protected Node<K, V> deserializeNode(final ByteBuffer buf) {
final K factoryK = tree.factoryK();
allocated = buf.getShort();
//Arrays.fill(keys, null);
for (int i = 0; i < allocated; i++) {
keys[i] = factoryK.deserialize(buf);
}
return this;
}
// ========= Abstract =========
abstract public boolean remove(int slot);
abstract public boolean isLeaf();
abstract public Node<K, V> split();
abstract public int getBOrder();
abstract public boolean isFreeable();
abstract public K splitShiftKeysLeft();
/**
* Merge two nodes, this node will absorb nodeFROM
*
* @param nodeParent a node parent for this & nodeFROM
* @param nodeFROM a node (will be clean)
*/
abstract protected void merge(final InternalNode<K, V> nodeParent, final int slot, final Node<K, V> nodeFROM);
/**
* Shift keys from nodeFROM (left) into this node (right)
*
* @param nodeParent the parent of nodeFROM and this node
* @param slot the index nodeTO in nodeParent.childs
* @param nodeFROM the right sibling of nodeTO
*/
abstract protected void shiftLR(final InternalNode<K, V> nodeParent, final int slot, final Node<K, V> nodeFROM);
/**
* Shift keys from node nodeFROM (right) into this node (left)
* @param nodeParent the parent of nodeFROM and this node
* @param slot the index nodeTO in nodeParent.childs
* @param nodeFROM the left sibling of nodeTO
*/
abstract protected void shiftRL(final InternalNode<K, V> nodeParent, final int slot, final Node<K, V> nodeFROM);
public static class InvalidNodeID extends RuntimeException {
private static final long serialVersionUID = 42L;
public static final InvalidNodeID NULL_ID = new InvalidNodeID("Invalid Node id=NULL_ID");
public InvalidNodeID(final String error) {
super(error);
}
}
}