/*
* 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;
/**
* Leaf Node of BplusTree<K, V>
* This class is NOT Thread-Safe
*
* @param <K> key type (DataHolder<K>)
* @param <V> value type (DataHolder<V>)
*
* @author Guillermo Grandes / guillermo.grandes[at]gmail.com
*/
public final class LeafNode<K extends DataHolder<K>, V extends DataHolder<V>> extends Node<K, V> {
private static final Logger log = Logger.getLogger(LeafNode.class);
public final V[] values;
public int leftid = NULL_ID;
public int rightid = NULL_ID;
protected LeafNode(final BplusTree<K, V> tree) {
super(tree);
this.values = tree.getGenericFactoryV().newArray(getBOrder());
}
@Override
public int getBOrder() {
return tree.getBOrderLeaf();
}
@Override
public boolean isLeaf() {
return true;
}
@Override
public boolean isFreeable() {
return isEmpty() && (values[0] == null);
}
@Override
public void clear() {
super.clear();
leftid = rightid = NULL_ID;
Arrays.fill(values, null);
}
public V set(final int slot, final V newValue) {
final V oldValue = values[slot];
values[slot] = newValue;
return oldValue;
}
public boolean add(final K newKey, final V newValue) {
if (isFull()) { // node is full
if (log.isDebugEnabled()) log.debug("overflow");
return false;
}
// TODO: Reparar
int slot = findSlotByKey(newKey);
if (slot >= 0) {
if (log.isDebugEnabled()) log.debug("key already exists: " + newKey);
return false; // key already exist
}
slot = (-slot)-1;
return add(slot, newKey, newValue);
}
public boolean add(final int slot, final K newKey, final V newValue) {
//if (log.isDebugEnabled()) log.debug("add("+newKey+") i=" + slot);
if (slot < allocated) {
moveElementsRight(keys, slot);
moveElementsRight(values, slot);
}
allocated++;
keys[slot] = newKey;
values[slot] = newValue;
return true;
}
@Override
public boolean remove(final int slot) {
if (slot < 0) {
log.error("faking slot=" + slot + " allocated=" + allocated);
return false;
}
if (slot < allocated) {
moveElementsLeft(keys, slot);
moveElementsLeft(values, slot);
}
if (allocated > 0) allocated--;
if (log.isDebugEnabled()) log.debug("erased up key=" + keys[allocated] + " value="+ values[allocated]);
keys[allocated] = null;
values[allocated] = null;
return true;
}
@Override
public LeafNode<K, V> split() {
final LeafNode<K, V> newHigh = tree.createLeafNode();
newHigh.allocId();
//int j = ((allocated >> 1) | (allocated & 1)); // dividir por dos y sumar el resto (0 o 1)
int j = (allocated >> 1); // dividir por dos (libro)
final int newsize = allocated-j;
//if (log.isDebugEnabled()) log.debug("split j=" + j);
System.arraycopy(keys, j, newHigh.keys, 0, newsize);
System.arraycopy(values, j, newHigh.values, 0, newsize);
// Limpiar la parte alta de los arrays de referencias inutiles
for (int i = j; i < j + newsize; i++) {
keys[i] = null;
values[i] = null;
}
newHigh.allocated = newsize;
allocated -= newsize;
// Update Linked List (left) in old High
if (rightid != NULL_ID) {
final LeafNode<K, V> oldHigh = (LeafNode<K, V>)tree.getNode(rightid);
oldHigh.leftid = newHigh.id;
tree.putNode(oldHigh);
}
// Linked List (left) in new High
newHigh.leftid = id;
// Linked List (right)
newHigh.rightid = rightid;
rightid = newHigh.id;
// update lowIdx on tree
if (leftid == 0) tree.lowIdx = id;
// update highIdx on tree
if (newHigh.rightid == 0) tree.highIdx = newHigh.id;
//
tree.putNode(this);
tree.putNode(newHigh);
return newHigh;
}
@Override
public K splitShiftKeysLeft() {
return keys[0];
}
/**
* Return the previous LeafNode in LinkedList
* @return LeafNode<K, V> previous node
*/
public LeafNode<K, V> prevNode() {
if (leftid == NULL_ID) return null;
return (LeafNode<K, V>)tree.getNode(leftid);
}
/**
* Return the next LeafNode in LinkedList
* @return LeafNode<K, V> next node
*/
public LeafNode<K, V> nextNode() {
if (rightid == NULL_ID) return null;
return (LeafNode<K, V>)tree.getNode(rightid);
}
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(leftid).append("<<");
sb.append("[").append("L").append(id).append("]");
sb.append(">>").append(rightid);
sb.append("(").append(allocated).append("){");
for (int i = 0; i < allocated; i++) {
final K k = keys[i];
final V v = values[i];
sb.append(k).append("=").append(v).append("|");
}
if (allocated > 0) sb.setLength(sb.length()-1);
sb.append("}");
return sb.toString();
}
// ========= Remove Helpers =========
protected final void merge(final InternalNode<K, V> nodeParent, final int slot, final Node<K, V> nodeFROMx) {
final LeafNode<K, V> nodeFROM = (LeafNode<K, V>)nodeFROMx;
final LeafNode<K, V> nodeTO = this;
final int sizeTO = nodeTO.allocated;
final int sizeFROM = nodeFROM.allocated;
// copy keys from nodeFROM to nodeTO
System.arraycopy(nodeFROM.keys, 0, nodeTO.keys, sizeTO, sizeFROM);
System.arraycopy(nodeFROM.values, 0, nodeTO.values, sizeTO, sizeFROM);
nodeTO.allocated += sizeFROM;
// remove key from nodeParent
nodeParent.remove(slot);
// Update Linked List (left) in new High
if (nodeFROM.rightid != NULL_ID) {
final LeafNode<K, V> rightFROM = (LeafNode<K, V>)tree.getNode(nodeFROM.rightid);
rightFROM.leftid = id;
tree.putNode(rightFROM);
}
// Update Linked List (right)
rightid = nodeFROM.rightid;
// update lowIdx on tree
if (leftid == 0) tree.lowIdx = id;
// update highIdx on tree
if (rightid == 0) tree.highIdx = id;
//
// Free nodeFROM
tree.freeNode(nodeFROM);
}
protected final void shiftLR(final InternalNode<K, V> nodeParent, final int slot, final Node<K, V> nodeFROMx) {
final LeafNode<K, V> nodeFROM = (LeafNode<K, V>)nodeFROMx;
final LeafNode<K, V> nodeTO = this;
final int sizeTO = nodeTO.allocated;
final int sizeFROM = nodeFROM.allocated;
final int shift = ((sizeTO+sizeFROM)/2) - sizeTO; // num. keys to shift from nodeFROM to nodeTO
// make space for new keys in nodeTO
System.arraycopy(nodeTO.keys, 0, nodeTO.keys, shift, sizeTO);
System.arraycopy(nodeTO.values, 0, nodeTO.values, shift, sizeTO);
// move keys and children out of nodeFROM and into nodeTO (and nodeU)
nodeTO.keys[shift-1] = nodeParent.keys[slot];
nodeParent.keys[slot] = nodeFROM.keys[sizeFROM-shift];
System.arraycopy(nodeFROM.keys, sizeFROM-shift, nodeTO.keys, 0, shift);
Arrays.fill(nodeFROM.keys, sizeFROM-shift, sizeFROM, null);
System.arraycopy(nodeFROM.values, sizeFROM-shift, nodeTO.values, 0, shift);
Arrays.fill(nodeFROM.values, sizeFROM-shift, sizeFROM, null);
nodeTO.allocated += shift;
nodeFROM.allocated -= shift;
}
protected final void shiftRL(final InternalNode<K, V> nodeParent, final int slot, final Node<K, V> nodeFROMx) {
final LeafNode<K, V> nodeFROM = (LeafNode<K, V>)nodeFROMx;
final LeafNode<K, V> nodeTO = this;
final int sizeTO = nodeTO.allocated;
final int sizeFROM = nodeFROM.allocated;
final int shift = ((sizeTO+sizeFROM)/2) - sizeTO; // num. keys to shift from nodeFROM to nodeTO
// shift keys and children from nodeFROM to nodeTO
nodeTO.keys[sizeTO] = nodeParent.keys[slot];
System.arraycopy(nodeFROM.keys, 0, nodeTO.keys, sizeTO, shift);
System.arraycopy(nodeFROM.values, 0, nodeTO.values, sizeTO, shift);
nodeParent.keys[slot] = nodeFROM.keys[shift];
// delete keys and children from nodeFROM
System.arraycopy(nodeFROM.keys, shift, nodeFROM.keys, 0, sizeFROM-shift);
Arrays.fill(nodeFROM.keys, sizeFROM-shift, sizeFROM, null);
System.arraycopy(nodeFROM.values, shift, nodeFROM.values, 0, sizeFROM-shift);
Arrays.fill(nodeFROM.values, sizeFROM-shift, sizeFROM, null);
nodeTO.allocated += shift;
nodeFROM.allocated -= shift;
}
// ========= Serialization =========
@Override
public int getStructMaxSize() {
final V factoryV = tree.factoryV();
return super.getStructMaxSize() + (values.length * factoryV.byteLength()) + 4 + 4;
}
@Override
public int getStructEstimateSize(final int b) {
final V factoryV = tree.factoryV();
return super.getStructEstimateSize(b) + (b * factoryV.byteLength()) + 4 + 4;
}
@Override
public void serialize(final ByteBuffer buf) {
super.serialize(buf);
for (int i = 0; i < allocated; i++) {
values[i].serialize(buf);
}
buf.putInt(leftid); // 4 bytes
buf.putInt(rightid); // 4 bytes
buf.flip();
}
@Override
protected Node<K, V> deserializeNode(final ByteBuffer buf) {
super.deserializeNode(buf);
final V factoryV = tree.factoryV();
//Arrays.fill(values, null);
for (int i = 0; i < allocated; i++) {
values[i] = factoryV.deserialize(buf);
}
leftid = buf.getInt();
rightid = buf.getInt();
return this;
}
}