package org.ObjectLayout.examples;/*
* Written by Gil Tene, Martin Thompson and Michael Barker, and released
* to the public domain, as explained at:
* http://creativecommons.org/publicdomain/zero/1.0/
*/
import java.lang.invoke.MethodHandles;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import org.ObjectLayout.ReferenceArray;
import org.ObjectLayout.StructuredArray;
@SuppressWarnings("rawtypes")
public class BPlusTree<K, V> implements Iterable<Map.Entry<K, V>> {
private static final MethodHandles.Lookup lookup = MethodHandles.lookup();
private final int nodeSize;
private final Comparator comparator;
private final Leaf firstNode;
private Node root;
private int size;
private static final Object[] EMPTY_ARGS = {};
public BPlusTree(int nodeSize) {
this(nodeSize, null);
}
public BPlusTree(int nodeSize, Comparator<K> comparator) {
this.nodeSize = nodeSize;
this.comparator = comparator;
firstNode = Leaf.newInstance(nodeSize);
this.root = firstNode;
}
@SuppressWarnings("unchecked")
public V put(K key, V val) {
if (key == null || val == null) {
throw new NullPointerException("Keys and values may not be null");
}
Object o = root.put(comparator, key, val);
if (isSplit(o)) {
Node next = root.next();
root = Branch.newInstance(root, next, nodeSize);
o = null;
}
if (null == o) {
size++;
}
return (V) o;
}
@SuppressWarnings("unchecked")
public V get(Object key) {
return (V) root.get(comparator, key);
}
@SuppressWarnings("unchecked")
public V remove(Object key) {
Object o = root.remove(comparator, key);
if (null != o) {
size--;
if (root.hasOnlyChild()) {
root = (Node) root.firstValue();
}
}
return (V) o;
}
public int size() {
return size;
}
static class Entry implements Map.Entry {
private Object key;
private Object val;
public Entry() {
}
@Override
public Object getKey() {
return key;
}
@Override
public Object getValue() {
return val;
}
@Override
public Object setValue(Object value) {
Object oldValue = this.val;
this.val = value;
return oldValue;
}
@Override
public String toString() {
return "[" + key + "->" + val + "]";
}
public void set(Object key, Object val) {
this.key = key;
this.val = val;
}
public void clear() {
key = null;
val = null;
}
}
private static int binarySearch(StructuredArray<Entry> entries, int fromIndex,
int toIndex, Object key, Comparator c) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
Object midVal = entries.get(mid).getKey();
int cmp = compare(c, midVal, key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
static class Leaf extends StructuredArray<Entry> implements Node {
private int size = 0;
private final int capacity;
private Leaf next;
public Leaf() {
capacity = (int) this.getLength();
}
public Object put(Comparator comparator, Object key, Object val) {
Object oldVal;
int search = binarySearch(this, 0, size, key, comparator);
if (search > -1) {
Entry entry = get(search);
oldVal = entry.getValue();
entry.setValue(val);
} else if (size < capacity) {
oldVal = null;
search = -(search + 1);
insert(search, key, val);
} else {
Leaf next = Leaf.newInstance(capacity);
int halfSize = size / 2;
shallowCopy(this, halfSize, next, 0, halfSize);
clear(halfSize, size);
size = halfSize;
next.size = halfSize;
if (compare(comparator, key, next.firstKey()) < 0) {
put(comparator, key, val);
} else {
next.put(comparator, key, val);
}
next.next = this.next;
this.next = next;
oldVal = Node.Sentinal.SPLIT;
}
return oldVal;
}
private void clear(int offset, int count) {
for (int i = offset; i < count; i++) {
get(i).clear();
}
}
public Object get(Comparator comparator, Object key) {
int search = binarySearch(this, 0, size, key, comparator);
if (search < 0) {
return null;
}
return get(search).getValue();
}
@Override
public Object remove(Comparator comparator, Object key) {
int search = binarySearch(this, 0, size, key, comparator);
if (search < 0) {
return null;
}
return remove(search);
}
public BPlusTree.Leaf next() {
return next;
}
@Override
public boolean requiresCompacting() {
return size < capacity / 2;
}
@Override
public Object firstKey() {
return get(0).getKey();
}
@Override
public Object firstValue() {
return get(0).getValue();
}
@Override
public Object lastKey() {
return get(size - 1).getKey();
}
@Override
public Object lastValue() {
return get(size - 1).getValue();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < size; i++) {
Entry entry = get(i);
sb.append(entry.getKey()).append("->").append(entry.getValue());
sb.append(",");
}
sb.setLength(sb.length() - 1);
sb.append("]");
return sb.toString();
}
@Override
public int size() {
return size;
}
@Override
public boolean hasOnlyChild() {
return false;
}
public void mergeFrom(Node right) {
assert (right.size() == capacity / 2) || (size == capacity / 2) : "Should have exactly half capacity nodes";
Leaf leaf = (Leaf) right;
shallowCopy(leaf, 0, this, size, leaf.size());
next = leaf.next();
size += leaf.size();
}
public boolean stealFromRight(Leaf right) {
if (right.size() > capacity / 2) {
append(right.firstKey(), right.firstValue());
right.removeFirst();
return true;
}
return false;
}
public boolean stealFromLeft(Leaf left) {
if (left.size() > capacity / 2) {
push(left.lastKey(), left.lastValue());
left.clearLast();
return true;
}
return false;
}
private void removeFirst() {
remove(0);
}
private Object remove(int search) {
Object o = this.get(search).getValue();
if (search != size - 1) {
shallowCopy(this, search + 1, this, search, size - (search + 1));
}
this.get(size - 1).clear();
size--;
return o;
}
private void insert(int search, Object key, Object val) {
if (search != size) {
shallowCopy(this, search, this, search + 1, size - search);
}
this.get(search).set(key, val);
size++;
}
private void push(Object key, Object val) {
insert(0, key, val);
}
private void append(Object key, Object val) {
get(size).set(key, val);
size++;
}
private void clearLast() {
get(size - 1).clear();
size--;
}
public static Leaf newInstance(int nodeSize) {
return newInstance(lookup, Leaf.class, Entry.class, nodeSize);
}
}
static class Branch extends ReferenceArray<Object> implements Node {
private final int capacity;
private int size = 0;
public Branch() {
capacity = ((int) getLength() - 1) / 2;
}
private void setChild(int i, Object o)
{
set(i, o);
}
private Object getChild(int i) {
return get(i);
}
@Override
public Object put(Comparator comparator, Object key, Object val) {
Node node = findNode(comparator, key);
Object oldVal = node.put(comparator, key, val);
if (isSplit(oldVal)) {
Node nextNode = node.next();
Object keyForNextNode = nextNode.firstKey();
if (size == capacity) {
splitBranch(comparator, keyForNextNode, nextNode);
} else {
insertNode(comparator, keyForNextNode, nextNode);
oldVal = null;
}
}
return oldVal;
}
private void splitBranch(Comparator comparator, Object keyForNextNode,
Node nextNode) {
int halfSize = size / 2;
int comparison = compareWithMidValues(comparator, keyForNextNode,
halfSize);
if (comparison == 0) {
Branch nextBranch = create(capacity);
// Copy half of the nodes from the original
int copyFrom = keyOffset(halfSize);
int length = arraySize() - copyFrom;
shallowCopy(this, copyFrom, nextBranch, 1, length);
nextBranch.size = halfSize;
// Key from new node moved up.
nextBranch.firstKey(keyForNextNode);
nextBranch.setChild(0, nextNode);
// clear out latter half...
clearArrayFrom(keyOffset(halfSize));
size = halfSize;
// temporarily store the nextBranch for the parent.
next(nextBranch);
} else if (comparison < 0) {
Branch nextBranch = create(capacity);
// Copy half of the nodes from the original
int copyFrom = keyOffset(halfSize);
int length = arraySize() - copyFrom;
shallowCopy(this, copyFrom, nextBranch, 1, length);
nextBranch.size = halfSize;
// Last key from first half moved up.
nextBranch.firstKey(storedKey(halfSize - 1));
nextBranch.setChild(0, getChild(halfSize * 2));
// clear out latter half...
clearArrayFrom(keyOffset(halfSize - 1));
size = halfSize - 1;
// Insert the new node
insertNode(comparator, keyForNextNode, nextNode);
// temporarily store the nextBranch for the parent.
next(nextBranch);
} else {
Branch nextBranch = create(capacity);
// Copy just under half of the nodes from the original
int copyFrom = keyOffset(halfSize + 1);
int length = arraySize() - copyFrom;
shallowCopy(this, copyFrom, nextBranch, 1, length);
nextBranch.size = halfSize - 1;
// First key from second half moved up.
nextBranch.firstKey(storedKey(halfSize));
nextBranch.setChild(0, getChild((halfSize + 1) * 2));
// clear out latter half...
clearArrayFrom(keyOffset(halfSize));
size = halfSize;
// Insert the new node
nextBranch.insertNode(comparator, keyForNextNode, nextNode);
// temporarily store the nextBranch for the parent.
next(nextBranch);
}
}
private void clearArrayFrom(int offset) {
for (int i = offset; i < getLength(); i++) {
setChild(i, null);
}
}
private void clear(int index) {
setChild(index, null);
}
@Override
public Object get(Comparator comparator, Object key) {
return findNode(comparator, key).get(comparator, key);
}
@Override
public Object remove(Comparator comparator, Object key) {
int index = findKeyIndex(comparator, key);
int nodeOffset = index * 2;
Node node = (Node) getChild(nodeOffset);
Object oldVal = node.remove(comparator, key);
if (node.requiresCompacting()) {
if (node instanceof Leaf) {
compactLeaves((Leaf) node, index, nodeOffset);
} else {
compactBranches((Branch) node, index, nodeOffset);
}
}
return oldVal;
}
private void compactLeaves(Leaf node, int index, int nodeOffset) {
if (index + 1 <= size) {
Leaf left = node;
Leaf right = (Leaf) getChild(nodeOffset + 2);
if (left.stealFromRight(right)) {
setChild(nodeOffset + 1, right.firstKey());
} else {
left.mergeFrom(right);
removeMergedNode(nodeOffset);
}
} else {
Leaf left = (Leaf) getChild(nodeOffset - 2);
Leaf right = node;
if (right.stealFromLeft(left)) {
setChild(nodeOffset - 1, right.firstKey());
} else {
left.mergeFrom(right);
removeMergedNode(nodeOffset);
}
}
}
private void compactBranches(Branch node, int index, int nodeOffset) {
if (index + 1 <= size) {
Branch right = (Branch) getChild(nodeOffset + 2);
Object rightKey = getChild(nodeOffset + 1);
if (right.size() > capacity / 2) {
node.append(rightKey, right.firstValue());
Object poppedKey = right.popKey();
setChild(nodeOffset + 1, poppedKey);
} else {
node.mergeFrom(rightKey, right);
removeMergedNode(nodeOffset);
}
} else {
Branch left = (Branch) getChild(nodeOffset - 2);
Object nodeKey = getChild(nodeOffset - 1);
if (left.size() > capacity / 2) {
node.push(left.lastValue(), nodeKey);
setChild(nodeOffset - 1, left.lastKey());
left.clearLast();
} else {
left.mergeFrom(nodeKey, node);
removeMergedNode(nodeOffset);
}
}
}
private void removeMergedNode(int nodeOffset) {
int length = arraySize() - (nodeOffset + 3);
if (length > 0) {
shallowCopy(this, nodeOffset + 3, this, nodeOffset + 1, length);
}
size--;
clear(arraySize());
clear(arraySize() + 1);
}
public void mergeFrom(Object rightKey, Node right) {
Branch branch = (Branch) right;
setChild(arraySize(), rightKey);
shallowCopy(branch, 0, this, arraySize() + 1, branch.arraySize());
size += branch.size() + 1;
}
private int compareWithMidValues(Comparator comparator, Object key,
int halfSize) {
Object keyA = storedKey(halfSize - 1);
Object keyB = storedKey(halfSize);
int compareA = compare(comparator, key, keyA);
int compareB = compare(comparator, key, keyB);
assert compareA != 0 : "Should not get a key match on split";
assert compareB != 0 : "Should not get a key match on split";
return Integer.signum(compareA) + Integer.signum(compareB);
}
private void insertNode(Comparator comparator, Object key, Node child) {
int keyIndex = findKeyIndex(comparator, key);
int offset = keyIndex * 2 + 1;
int length = arraySize() - offset;
if (length > 0) {
shallowCopy(this, offset, this, offset + 2, length);
}
setChild(offset, key);
setChild(offset + 1, child);
size++;
}
private void next(Branch nextBranch) {
assert size < capacity : "Can only store next node if node is not full";
setChild((int) getLength() - 1, nextBranch);
}
private void firstKey(Object storedKey) {
assert size < capacity : "Can only store first key if node is not full";
setChild((int) getLength() - 1, storedKey);
}
@Override
public Node next() {
int offset = (int) getLength() - 1;
Object nextNode = getChild(offset);
assert nextNode != null : "Can only fetch next once";
setChild(offset, null);
return (Node) nextNode;
}
@Override
public Object firstKey() {
int offset = (int) getLength() - 1;
Object firstKey = getChild(offset);
assert firstKey != null : "Can only fetch firstKey once";
setChild(offset, null);
return firstKey;
}
public Object popKey() {
Object key = getChild(1);
int length = arraySize() - 2;
shallowCopy(this, 2, this, 0, length);
size--;
clear(arraySize());
clear(arraySize() + 1);
return key;
}
@Override
public Node firstValue() {
return (Node) getChild(0);
}
@Override
public Object lastKey() {
assert size > 0 : "Should not be modifying branch with only child";
return getChild(arraySize() - 2);
}
@Override
public Node lastValue() {
assert size > 0 : "Should not be modifying branch with only child";
return (Node) getChild(arraySize() - 1);
}
private void clearLast() {
assert size > 0 : "Should not be modifying branch with only child";
clear(arraySize() - 1);
clear(arraySize() - 2);
size--;
}
public void push(Node val, Object key) {
shallowCopy(this, 0, this, 2, arraySize());
setChild(0, val);
setChild(1, key);
size++;
}
public void append(Object key, Node val) {
setChild(arraySize(), key);
setChild(arraySize() + 1, val);
size++;
}
@Override
public boolean requiresCompacting() {
return size < capacity / 2;
}
private Node findNode(Comparator comparator, Object key) {
return storedNode(findKeyIndex(comparator, key));
}
private int keyOffset(int index) {
return index * 2 + 1;
}
private Node storedNode(int search) {
return (Node) getChild(search * 2);
}
private Object storedKey(int index) {
return getChild(keyOffset(index));
}
private int findKeyIndex(Comparator comparator, Object key) {
int lo = 0;
int hi = size - 1;
while (lo <= hi) {
final int mid = (lo + hi) >>> 1;
Object stored = storedKey(mid);
int comparison = compare(comparator, key, stored);
if (comparison == 0) {
return mid + 1;
} else if (comparison < 0) {
hi = mid - 1;
} else {
lo = mid + 1;
}
}
return lo;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("B:[");
for (int i = 0; i < size; i++) {
sb.append(getChild(i)).append(",");
}
sb.setLength(sb.length() - 1);
sb.append("]");
return sb.toString();
}
@Override
public int size() {
return size;
}
@Override
public boolean hasOnlyChild() {
return size == 0;
}
private int arraySize() {
return size * 2 + 1;
}
private static Branch newInstance(Node left, Node right, int nodeSize) {
Branch branch = create(nodeSize);
branch.setChild(0, left);
branch.setChild(1, right.firstKey());
branch.setChild(2, right);
branch.size = 1;
return branch;
}
private static Branch create(int nodeSize) {
int length = (nodeSize * 2) + 1;
return ReferenceArray.newInstance(lookup, Branch.class, length);
}
}
interface Node {
enum Sentinal {
SPLIT
};
Object get(Comparator comparator, Object key);
Object put(Comparator comparator, Object key, Object val);
Object remove(Comparator comparator, Object key);
Object lastValue();
Object lastKey();
Object firstValue();
int size();
boolean requiresCompacting();
Object firstKey();
Node next();
boolean hasOnlyChild();
}
@SuppressWarnings("unchecked")
private static int compare(Comparator comparator, Object a, Object b) {
if (null == comparator) {
return ((Comparable) a).compareTo(b);
} else {
return comparator.compare(a, b);
}
}
private static boolean isSplit(Object o) {
return Node.Sentinal.SPLIT == o;
}
@Override
public String toString() {
return "org.ObjectLayout.examples.BPlusTree [nodeSize=" + nodeSize + ", comparator=" + comparator
+ ", root=" + root + "]";
}
public Iterator<Map.Entry<K, V>> iterator() {
return new BPlusTreeIterator(firstNode);
}
public class BPlusTreeIterator implements Iterator<Map.Entry<K, V>> {
private Leaf leaf;
private int index = -1;
public BPlusTreeIterator(Leaf leaf) {
this.leaf = leaf;
}
@Override
public boolean hasNext() {
return (index + 1) < leaf.size() || leaf.next() != null;
}
@SuppressWarnings("unchecked")
@Override
public Map.Entry<K, V> next() {
if ((index + 1) < leaf.size()) {
index++;
Entry entry = leaf.get(index);
return entry;
} else if (leaf.next() != null) {
leaf = leaf.next();
index = 0;
Entry entry = leaf.get(index);
return entry;
}
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
public static <T> void shallowCopy(
final ReferenceArray<T> src, final long srcOffset,
final ReferenceArray<T> dst, final long dstOffset,
final long count) {
if (srcOffset + count > Integer.MAX_VALUE || dstOffset + count > Integer.MAX_VALUE) {
throw new ArrayIndexOutOfBoundsException();
}
int length = (int) count;
int srcOff = (int) srcOffset;
int dstOff = (int) dstOffset;
System.arraycopy(src.asArray(), srcOff, dst.asArray(), dstOff, length);
}
}