/*
* 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;
/**
* Internal 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 InternalNode<K extends DataHolder<K>, V extends DataHolder<V>> extends Node<K, V> {
private static final Logger log = Logger.getLogger(InternalNode.class);
public final int[] childs;
protected InternalNode(final BplusTree<K, V> tree) {
super(tree);
this.childs = new int[getBOrder()+1];
}
@Override
public int getBOrder() {
return tree.getBOrderInternal();
}
@Override
public boolean isLeaf() {
return false;
}
@Override
public boolean isFreeable() {
return isEmpty() && (childs[0] == NULL_ID);
}
@Override
public void clear() {
super.clear();
Arrays.fill(childs, NULL_ID);
}
public int getSlotLeft(final int slot) {
return (slot + 1 > allocated ? slot - 1 : slot);
}
public int getSlotRight(final int slot) {
return (slot + 1 > allocated ? slot : slot + 1);
}
public boolean add(final K newKey, final int childId) {
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, childId);
}
public boolean add(final int slot, final K newKey, final int childId) {
if (log.isDebugEnabled()) log.debug("add("+newKey+") i=" + slot);
if (slot < allocated) {
moveElementsRight(keys, slot);
moveChildsRight(slot+1);
}
allocated++;
keys[slot] = newKey;
childs[slot+1] = childId;
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);
moveChildsLeft(slot+1);
}
if (allocated > 0) allocated--;
if (log.isDebugEnabled()) log.debug("[" + id + "] erased up ["+allocated+"] key=" + keys[allocated] + " value="+ childs[allocated+1]);
keys[allocated] = null;
childs[allocated+1] = NULL_ID;
return true;
}
@Override
public InternalNode<K, V> split() { // TODO
final InternalNode<K, V> newHigh = tree.createInternalNode();
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(childs, j+1, newHigh.childs, 0, newsize);
// TODO: Limpiar la parte alta de los arrays de referencias inutiles
for (int i = j; i < j + newsize; i++) {
keys[i] = null;
childs[i+1] = NULL_ID;
}
newHigh.allocated = newsize;
allocated -= newsize;
// Ahora habria que hacer un splitShiftKeysLeft para alinear los childs
tree.putNode(this); // TODO FAKE
tree.putNode(newHigh); // TODO FAKE
return newHigh;
}
@Override
public K splitShiftKeysLeft() {
final K removed = keys[0];
moveElementsLeft(keys, 0);
allocated--;
// TODO: Limpiar la parte alta de los arrays de referencias inutiles
keys[allocated] = null;
childs[allocated+1] = NULL_ID;
return removed;
}
// insert child
protected void moveChildsRight(final int srcPos) {
//if (log.isDebugEnabled()) log.debug("moveKeysRight("+srcPos+") allocated=" + allocated + ":" + keys.length + ":" + (allocated-srcPos) + ":" + (keys.length-srcPos-1));
System.arraycopy(childs, srcPos, childs, srcPos+1, allocated-srcPos+1);
}
// remove child
protected void moveChildsLeft(final int srcPos) {
//if (log.isDebugEnabled()) log.debug("moveKeysLeft("+srcPos+") allocated=" + allocated + ":" + keys.length + ":" + (allocated-srcPos-1) + ":" + (keys.length-srcPos-1));
System.arraycopy(childs, srcPos+1, childs, srcPos, allocated-srcPos);
}
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("[").append("I").append(id).append("]");
sb.append("(").append(allocated).append("){");
for (int i = 0; i < allocated; i++) {
final K k = keys[i];
if (i == 0) { // left
sb.append("c").append(childs[i]).append("<");
}
else
sb.append("<");
sb.append(k);
sb.append(">c").append(childs[i+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 InternalNode<K, V> nodeFROM = (InternalNode<K, V>)nodeFROMx;
final InternalNode<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+1, sizeFROM);
System.arraycopy(nodeFROM.childs, 0, nodeTO.childs, sizeTO+1, sizeFROM+1);
// add key to nodeTO
nodeTO.keys[sizeTO] = nodeParent.keys[slot];
nodeTO.allocated += sizeFROM + 1; // keys of FROM and key of nodeParent
// remove key from nodeParent
nodeParent.remove(slot);
// Free nodeFROM
tree.freeNode(nodeFROM);
}
protected final void shiftLR(final InternalNode<K, V> nodeParent, final int slot, final Node<K, V> nodeFROMx) {
final InternalNode<K, V> nodeFROM = (InternalNode<K, V>)nodeFROMx;
final InternalNode<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.childs, 0, nodeTO.childs, shift, sizeTO+1);
// 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+1, nodeTO.keys, 0, shift-1);
Arrays.fill(nodeFROM.keys, sizeFROM-shift, sizeFROM, null);
System.arraycopy(nodeFROM.childs, sizeFROM-shift+1, nodeTO.childs, 0, shift);
Arrays.fill(nodeFROM.childs, sizeFROM-shift+1, sizeFROM+1, NULL_ID);
nodeTO.allocated += shift;
nodeFROM.allocated -= shift;
}
protected final void shiftRL(final InternalNode<K, V> nodeParent, final int slot, final Node<K, V> nodeFROMx) {
final InternalNode<K, V> nodeFROM = (InternalNode<K, V>)nodeFROMx;
final InternalNode<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+1, shift-1);
System.arraycopy(nodeFROM.childs, 0, nodeTO.childs, sizeTO+1, shift);
nodeParent.keys[slot] = nodeFROM.keys[shift-1];
// 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.childs, shift, nodeFROM.childs, 0, sizeFROM-shift+1);
Arrays.fill(nodeFROM.childs, sizeFROM-shift+1, sizeFROM+1, NULL_ID);
nodeFROM.allocated -= shift;
nodeTO.allocated += shift;
}
/**
* Check if underflow occurred in child of slot
* @param slot the index of a child in nodeParent (this)
*/
protected boolean checkUnderflow(final int slot) {
if (childs[slot] == NULL_ID) return false;
if (slot == 0) {
return checkUnderflowWithRight(slot); // use nodeParent right sibling
}
else {
if (getSlotLeft(slot) == slot) {
return checkUnderflowWithRight(slot); // use nodeParent right sibling
}
else {
return checkUnderflowWithLeft(slot); // use nodeParent left sibling
}
}
}
/**
* Check if underflow occurred in child of slot
* @param slot the index of a child in nodeParent
*/
private final boolean checkUnderflowWithLeft(final int slot) {
final Node<K, V> nodeRight = tree.getNode(childs[slot]);
if (nodeRight.isUnderFlow()) {
final Node<K, V> nodeLeft = tree.getNode(childs[slot-1]);
if (nodeLeft.canMerge(nodeRight)) {
nodeLeft.merge(this, slot-1, nodeRight);
} else {
nodeRight.shiftLR(this, slot-1, nodeLeft);
}
//
// Update Changed Nodes
tree.putNode(this);
tree.putNode(nodeLeft);
tree.putNode(nodeRight);
return true;
}
return false;
}
/**
* Check if underflow ocurred in child of slot
* @param nodeParent
* @param slot the index of a child in nodeParent
*/
private final boolean checkUnderflowWithRight(final int slot) {
final Node<K, V> nodeLeft = tree.getNode(childs[slot]);
if (nodeLeft.isUnderFlow()) {
final Node<K, V> nodeRight = tree.getNode(childs[slot+1]);
if (nodeLeft.canMerge(nodeRight)) {
nodeLeft.merge(this, slot, nodeRight);
childs[slot] = nodeLeft.id;
} else {
nodeLeft.shiftRL(this, slot, nodeRight);
}
//
// Update Changed Nodes
tree.putNode(this);
tree.putNode(nodeRight);
tree.putNode(nodeLeft);
return true;
}
return false;
}
// ========= Serialization =========
@Override
public int getStructMaxSize() {
return super.getStructMaxSize() + (childs.length * 4);
}
@Override
public int getStructEstimateSize(final int b) {
return super.getStructEstimateSize(b) + ((b+1) * 4);
}
@Override
public void serialize(final ByteBuffer buf) {
super.serialize(buf);
for (int i = 0; i < allocated+1; i++) {
buf.putInt(childs[i]); // 4 bytes
}
buf.flip();
}
@Override
protected Node<K, V> deserializeNode(final ByteBuffer buf) {
super.deserializeNode(buf);
for (int i = 0; i < allocated+1; i++) {
childs[i] = buf.getInt();
}
return this;
}
}