/*
* Copyright (c) 2014, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.google.dart.engine.internal.index.structure.btree;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* A simple B+ tree (http://en.wikipedia.org/wiki/B+_tree) implementation.
*
* <pre>
* [K] is the keys type.
* [V] is the values type.
* [N] is the type of node identifiers using by the [NodeManager].
* </pre>
*
* @coverage dart.engine.index.structure
*/
public class BPlusTree<K, V, N> {
/**
* An internal node with keys and children references.
*/
private class InternalNode extends Node {
List<N> children = new ArrayList<N>();
final int maxKeys;
final int minKeys;
InternalNode(N id, int maxKeys) {
super(id);
this.maxKeys = maxKeys;
minKeys = maxKeys / 2;
}
@Override
V find(K key) {
int index = findChildIndex(key);
Node child = readNode(children.get(index));
return child.find(key);
}
@Override
Split insert(K key, V value) {
// Early split.
if (keys.size() == maxKeys) {
int middle = (maxKeys + 1) / 2;
K splitKey = keys.get(middle);
// Overflow into a new sibling.
InternalNode sibling = newInternalNode();
sibling.keys.addAll(keys.subList(middle + 1, keys.size()));
sibling.children.addAll(children.subList(middle + 1, children.size()));
setListLength(keys, middle);
setListLength(children, middle + 1);
// Insert into this node or sibling.
if (comparator.compare(key, splitKey) < 0) {
insertNotFull(key, value);
} else {
sibling.insertNotFull(key, value);
}
// Prepare split.
writeInternalNode(this);
writeInternalNode(sibling);
return new Split(splitKey, id, sibling.id);
}
// No split.
insertNotFull(key, value);
return null;
}
@Override
Remove remove(K key, Node left, K anchor, Node right) {
int index = findChildIndex(key);
K thisAnchor = index == 0 ? keys.get(0) : keys.get(index - 1);
// Prepare children.
Node child0 = readNode(children.get(index));
Node leftChild;
Node rightChild;
if (index != 0) {
leftChild = readNode(children.get(index - 1));
} else {
leftChild = null;
}
if (index < children.size() - 1) {
rightChild = readNode(children.get(index + 1));
} else {
rightChild = null;
}
// Ask child to remove.
Remove result = child0.remove(key, leftChild, thisAnchor, rightChild);
V value = result.value;
if (value == null) {
return new Remove(value);
}
// Do keys / children updates
boolean hasUpdates = false;
{
// Update anchor if borrowed.
if (result.leftAnchor != null) {
keys.set(index - 1, result.leftAnchor);
hasUpdates = true;
}
if (result.rightAnchor != null) {
keys.set(index, result.rightAnchor);
hasUpdates = true;
}
// Update keys / children if merged.
if (result.mergedLeft) {
keys.remove(index - 1);
N child = children.remove(index);
manager.delete(child);
hasUpdates = true;
}
if (result.mergedRight) {
keys.remove(index);
N child = children.remove(index);
manager.delete(child);
hasUpdates = true;
}
}
// Write if updated.
if (!hasUpdates) {
return new Remove(value);
}
writeInternalNode(this);
// Perform balancing.
if (keys.size() < minKeys) {
// Try left sibling.
if (left instanceof BPlusTree.InternalNode) {
@SuppressWarnings("unchecked")
InternalNode leftInternal = (InternalNode) left;
// Try to redistribute.
int leftLength = left.keys.size();
if (leftLength > minKeys) {
int halfExcess = (leftLength - minKeys + 1) / 2;
int newLeftLength = leftLength - halfExcess;
keys.add(0, anchor);
keys.addAll(0, left.keys.subList(newLeftLength, leftLength));
children.addAll(0, leftInternal.children.subList(newLeftLength, leftLength + 1));
K newAnchor = left.keys.get(newLeftLength - 1);
setListLength(left.keys, newLeftLength - 1);
setListLength(leftInternal.children, newLeftLength);
writeInternalNode(this);
writeInternalNode(leftInternal);
return new Remove(value).borrowLeft(newAnchor);
}
// Do merge.
left.keys.add(anchor);
left.keys.addAll(keys);
leftInternal.children.addAll(children);
writeInternalNode(this);
writeInternalNode(leftInternal);
return new Remove(value).mergeLeft();
}
// Try right sibling.
if (right instanceof BPlusTree.InternalNode) {
@SuppressWarnings("unchecked")
InternalNode rightInternal = (InternalNode) right;
// Try to redistribute.
int rightLength = right.keys.size();
if (rightLength > minKeys) {
int halfExcess = (rightLength - minKeys + 1) / 2;
keys.add(anchor);
keys.addAll(right.keys.subList(0, halfExcess - 1));
children.addAll(rightInternal.children.subList(0, halfExcess));
K newAnchor = right.keys.get(halfExcess - 1);
removeListRange(right.keys, 0, halfExcess);
removeListRange(rightInternal.children, 0, halfExcess);
writeInternalNode(this);
writeInternalNode(rightInternal);
return new Remove(value).borrowRight(newAnchor);
}
// Do merge.
right.keys.add(0, anchor);
right.keys.addAll(0, keys);
rightInternal.children.addAll(0, children);
writeInternalNode(this);
writeInternalNode(rightInternal);
return new Remove(value).mergeRight();
}
}
// No balancing required.
return new Remove(value);
}
@Override
void writeOn(StringBuilder buffer, String indent) {
buffer.append(indent);
buffer.append("INode {\n");
for (int i = 0; i < keys.size(); i++) {
Node child = readNode(children.get(i));
child.writeOn(buffer, indent + " ");
buffer.append(indent);
buffer.append(" ");
buffer.append(keys.get(i));
buffer.append("\n");
}
Node child = readNode(children.get(keys.size()));
child.writeOn(buffer, indent + " ");
buffer.append(indent);
buffer.append("}\n");
}
/**
* Returns the index of the child into which [key] should be inserted.
*/
private int findChildIndex(K key) {
int lo = 0;
int hi = keys.size() - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
int compare = comparator.compare(key, keys.get(mid));
if (compare < 0) {
hi = mid - 1;
} else if (compare > 0) {
lo = mid + 1;
} else {
return mid + 1;
}
}
return lo;
}
private void insertNotFull(K key, V value) {
int index = findChildIndex(key);
Node child = readNode(children.get(index));
Split result = child.insert(key, value);
if (result != null) {
keys.add(index, result.key);
children.set(index, result.left);
children.add(index + 1, result.right);
writeInternalNode(this);
}
}
}
/**
* A leaf node with keys and values.
*/
private class LeafNode extends Node {
final int maxKeys;
final int minKeys;
List<V> values = new ArrayList<V>();
LeafNode(N id, int maxKeys) {
super(id);
this.maxKeys = maxKeys;
this.minKeys = maxKeys / 2;
}
@Override
V find(K key) {
int index = findKeyIndex(key);
if (index >= keys.size()) {
return null;
}
if (comparator.compare(keys.get(index), key) != 0) {
return null;
}
return values.get(index);
}
@Override
Split insert(K key, V value) {
int index = findKeyIndex(key);
// The node is full.
if (keys.size() == maxKeys) {
int middle = (maxKeys + 1) / 2;
LeafNode sibling = newLeafNode();
sibling.keys.addAll(keys.subList(middle, keys.size()));
sibling.values.addAll(values.subList(middle, values.size()));
setListLength(keys, middle);
setListLength(values, middle);
// Insert into the left / right sibling.
if (index < middle) {
insertNotFull(key, value, index);
} else {
sibling.insertNotFull(key, value, index - middle);
}
// Notify the parent about the split.
writeLeafNode(this);
writeLeafNode(sibling);
return new Split(sibling.keys.get(0), id, sibling.id);
}
// The node was not full.
insertNotFull(key, value, index);
return null;
}
@Override
Remove remove(K key, Node left, K anchor, Node right) {
// Find the key.
int index = keys.indexOf(key);
if (index == -1) {
return new Remove(null);
}
// Remove key / value.
keys.remove(index);
V value = values.remove(index);
writeLeafNode(this);
// Perform balancing.
if (keys.size() < minKeys) {
// Try left sibling.
if (left instanceof BPlusTree.LeafNode) {
@SuppressWarnings("unchecked")
LeafNode leftLeaf = (LeafNode) left;
// Try to redistribute.
int leftLength = left.keys.size();
if (leftLength > minKeys) {
int halfExcess = (leftLength - minKeys + 1) / 2;
int newLeftLength = leftLength - halfExcess;
keys.addAll(0, left.keys.subList(newLeftLength, leftLength));
values.addAll(0, leftLeaf.values.subList(newLeftLength, leftLength));
setListLength(left.keys, newLeftLength);
setListLength(leftLeaf.values, newLeftLength);
writeLeafNode(this);
writeLeafNode(leftLeaf);
return new Remove(value).borrowLeft(keys.get(0));
}
// Do merge.
left.keys.addAll(keys);
leftLeaf.values.addAll(values);
writeLeafNode(this);
writeLeafNode(leftLeaf);
return new Remove(value).mergeLeft();
}
// Try right sibling.
if (right instanceof BPlusTree.LeafNode) {
@SuppressWarnings("unchecked")
LeafNode rightLeaf = (LeafNode) right;
// Try to redistribute.
int rightLength = right.keys.size();
if (rightLength > minKeys) {
int halfExcess = (rightLength - minKeys + 1) / 2;
keys.addAll(right.keys.subList(0, halfExcess));
values.addAll(rightLeaf.values.subList(0, halfExcess));
removeListRange(right.keys, 0, halfExcess);
removeListRange(rightLeaf.values, 0, halfExcess);
writeLeafNode(this);
writeLeafNode(rightLeaf);
return new Remove(value).borrowRight(right.keys.get(0));
}
// Do merge.
right.keys.addAll(0, keys);
rightLeaf.values.addAll(0, values);
writeLeafNode(this);
writeLeafNode(rightLeaf);
return new Remove(value).mergeRight();
}
}
// No balancing required.
return new Remove(value);
}
@Override
void writeOn(StringBuilder buffer, String indent) {
buffer.append(indent);
buffer.append("LNode {");
for (int i = 0; i < keys.size(); i++) {
if (i != 0) {
buffer.append(", ");
}
buffer.append(keys.get(i));
buffer.append(": ");
buffer.append(values.get(i));
}
buffer.append("}\n");
}
/**
* Returns the index where [key] should be inserted.
*/
private int findKeyIndex(K key) {
int lo = 0;
int hi = keys.size() - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
int compare = comparator.compare(key, keys.get(mid));
if (compare < 0) {
hi = mid - 1;
} else if (compare > 0) {
lo = mid + 1;
} else {
return mid;
}
}
return lo;
}
private void insertNotFull(K key, V value, int index) {
if (index < keys.size() && comparator.compare(keys.get(index), key) == 0) {
values.set(index, value);
} else {
keys.add(index, key);
values.add(index, value);
}
writeLeafNode(this);
}
}
/**
* An internal or leaf node.
*/
private abstract class Node {
/**
* The identifier of this node.
*/
final N id;
/**
* The list of keys.
*/
List<K> keys = new ArrayList<K>();
Node(N id) {
this.id = id;
}
/**
* Looks for [key]. Returns the associated value if found. Returns `null` if not found.
*/
abstract V find(K key);
/**
* Inserts the [key] / [value] pair into this [Node]. Returns a [Split] object if split happens,
* or `null` otherwise.
*/
abstract Split insert(K key, V value);
/**
* Removes the association for the given [key]. Returns the [Remove] information about an
* operation performed. It may be restructuring or merging, with [left] or [left] siblings.
*/
abstract Remove remove(K key, Node left, K anchor, Node right);
/**
* Writes a textual presentation of the tree into [buffer].
*/
abstract void writeOn(StringBuilder buffer, String indent);
}
/**
* A container with information about redistribute / merge.
*/
private class Remove {
K leftAnchor;
boolean mergedLeft = false;
boolean mergedRight = false;
K rightAnchor;
final V value;
Remove(V value) {
this.value = value;
}
Remove borrowLeft(K leftAnchor) {
this.leftAnchor = leftAnchor;
return this;
}
Remove borrowRight(K rightAnchor) {
this.rightAnchor = rightAnchor;
return this;
}
Remove mergeLeft() {
this.mergedLeft = true;
return this;
}
Remove mergeRight() {
this.mergedRight = true;
return this;
}
}
/**
* A container with information about split during insert.
*/
private class Split {
final K key;
final N left;
final N right;
Split(K key, N left, N right) {
this.key = key;
this.left = left;
this.right = right;
}
}
/**
* The [Comparator] to compare keys.
*/
final Comparator<K> comparator;
/**
* The [NodeManager] to manage nodes.
*/
final NodeManager<K, V, N> manager;
/**
* The maximum number of keys in an index node.
*/
final int maxInternalKeys;
/**
* The maximum number of keys in a leaf node.
*/
final int maxLeafKeys;
/**
* The root node.
*/
Node root;
/**
* Creates a new [BPlusTree] instance.
*/
@SuppressWarnings("unchecked")
public BPlusTree(Comparator<K> _comparator, NodeManager<K, V, N> manager) {
this.comparator = _comparator;
this.manager = manager;
this.maxInternalKeys = manager.getMaxInternalKeys();
this.maxLeafKeys = manager.getMaxLeafKeys();
root = newLeafNode();
writeLeafNode((LeafNode) root);
}
/**
* Returns the value for [key] or `null` if [key] is not in the
*/
public V find(K key) {
return root.find(key);
}
/**
* Associates the [key] with the given [value]. If the key was already in the tree, its associated
* value is changed. Otherwise the key-value pair is added to the
*/
public void insert(K key, V value) {
Split result = root.insert(key, value);
if (result != null) {
InternalNode newRoot = newInternalNode();
newRoot.keys.add(result.key);
newRoot.children.add(result.left);
newRoot.children.add(result.right);
root = newRoot;
writeInternalNode(newRoot);
}
}
/**
* Removes the association for the given [key]. Returns the value associated with [key] in the
* tree or `null` if [key] is not in the tree.
*/
@SuppressWarnings("unchecked")
public V remove(K key) {
Remove result = root.remove(key, null, null, null);
if (root instanceof BPlusTree.InternalNode) {
List<N> children = ((InternalNode) root).children;
if (children.size() == 1) {
manager.delete(root.id);
root = readNode(children.get(0));
}
}
return result.value;
}
/**
* Writes a textual presentation of the tree into [buffer].
*/
public void writeOn(StringBuilder buffer) {
root.writeOn(buffer, "");
}
/**
* Creates a new [InternalNode] instance.
*/
private InternalNode newInternalNode() {
N id = manager.createInternal();
return new InternalNode(id, maxInternalKeys);
}
/**
* Creates a new [LeafNode] instance.
*/
private LeafNode newLeafNode() {
N id = manager.createLeaf();
return new LeafNode(id, maxLeafKeys);
}
/**
* Reads the [InternalNode] with [id] from the manager.
*/
private InternalNode readInternalNode(N id) {
InternalNodeData<K, N> data = manager.readInternal(id);
InternalNode node = new InternalNode(id, maxInternalKeys);
node.keys = data.keys;
node.children = data.children;
return node;
}
/**
* Reads the [LeafNode] with [id] from the manager.
*/
private LeafNode readLeafNode(N id) {
LeafNode node = new LeafNode(id, maxLeafKeys);
LeafNodeData<K, V> data = manager.readLeaf(id);
node.keys = data.keys;
node.values = data.values;
return node;
}
/**
* Reads the [InternalNode] or [LeafNode] with [id] from the manager.
*/
private Node readNode(N id) {
if (manager.isInternal(id)) {
return readInternalNode(id);
} else {
return readLeafNode(id);
}
}
private void removeListRange(List<?> list, int offset, int length) {
list.subList(offset, length).clear();
}
private void setListLength(List<?> list, int newLength) {
list.subList(newLength, list.size()).clear();
}
/**
* Writes [node] into the manager.
*/
private void writeInternalNode(InternalNode node) {
manager.writeInternal(node.id, new InternalNodeData<K, N>(node.keys, node.children));
}
/**
* Writes [node] into the manager.
*/
private void writeLeafNode(LeafNode node) {
manager.writeLeaf(node.id, new LeafNodeData<K, V>(node.keys, node.values));
}
}