/**
* Copyright 2013 Benjamin Lerer
*
* 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 io.horizondb.db.btree;
import io.horizondb.db.btree.NodeVisitor.NodeVisitResult;
import java.io.IOException;
import org.apache.commons.lang.Validate;
/**
* B+Tree implementation.
*
* @author Benjamin
*
* @param <K> the key type.
* @param <V> the value type.
*/
public final class BTree<K extends Comparable<K>, V> {
/**
* The branching factor
*/
private final int b;
/**
* The <code>NodeManager</code> in charge of loading and saving the node information.
*/
private final NodeManager<K, V> manager;
/**
* Creates a new <code>BTree</code> instance.
*
* @param manager The <code>NodeManager</code> in charge of loading and saving the node information.
* @param b The branching factor
*/
public BTree(NodeManager<K, V> manager, int b) {
Validate.notNull(manager, "manager parameter must not be null");
this.manager = manager;
this.b = b;
}
/**
* Inserts the specified record into this tree.
*
* @param key the record key.
* @param value the record value.
* @throws IOException if an IO problem occurs.
*/
public synchronized void insert(K key, V value) throws IOException {
Node<K, V>[] nodes = getRoot().insert(key, value);
if (nodes.length == 1) {
setRoot(nodes[0]);
} else {
setRoot(new InternalNode<>(this, nodes));
}
}
/**
* Inserts the specified record into this tree if no record exists with the specified key.
*
* @param key the record key.
* @param value the record value.
* @return <code>true</code> if the record has been successfully inserted, <code>false</code> otherwise.
* @throws IOException if an IO problem occurs.
*/
public synchronized boolean insertIfAbsent(K key, V value) throws IOException {
if (contains(key)) {
return false;
}
insert(key, value);
return true;
}
/**
* Deletes the specified record from this tree if it exists.
*
* @param key the record key.
* @throws IOException if an IO problem occurs.
* @return <code>true</code> if the record has been successfully deleted, <code>false</code> otherwise. *
*/
public synchronized boolean deleteIfPresent(K key) throws IOException {
if (!contains(key)) {
return false;
}
delete(key);
return true;
}
/**
* Delete the record with the specified key.
*
* @param key the key of the record to delete.
* @throws IOException if an I/O problem occurs.
*/
public synchronized void delete(K key) throws IOException {
setRoot(getRoot().delete(key));
}
/**
* Returns the value associated to the specified key if it exists or <code>null</code> if it does not.
*
* @param key the key of the value to retrieve
* @return the value associated to the specified key if it exists or <code>null</code> if it does not.
* @throws IOException if an I/O problem occurs.
*/
public V get(K key) throws IOException {
return getRoot().get(key);
}
/**
* Returns an iterator to iterate over the records whose keys range from
* {@code fromKey}, inclusive, to {@code toKey}, inclusive.
*
* @param fromKey low end-point (inclusive) of the keys in the returned iterator
* @param toKey high end-point (inclusive) of the keys in the returned iterator
* @return an iterator to iterate over the records whose keys range from
* {@code fromKey}, inclusive, to {@code toKey}, exclusive.
* @throws IOException if an I/O problem occurs.
*/
public KeyValueIterator<K, V> iterator(K fromKey, K toKey) throws IOException {
return getRoot().iterator(fromKey, toKey);
}
/**
* Returns <code>true</code> if this B+Tree contains the specified key, <code>false</code> otherwise.
*
* @param key the key to check
* @return <code>true</code> if this B+Tree contains the specified key, <code>false</code> otherwise.
* @throws IOException if an I/O problem occurs.
*/
public boolean contains(K key) throws IOException {
return getRoot().contains(key);
}
/**
* Accepts the specified visitor.
*
* @param visitor the visitor
* @return the result of the visit.
* @throws IOException if an I/O problem occurs.
*/
public void accept(NodeVisitor<K, V> visitor) throws IOException {
if (visitor == null) {
return;
}
Node<K, V> root = getRoot();
NodeVisitResult result = visitor.preVisitNode(root.getFirstKey(), root);
if (result == NodeVisitResult.TERMINATE || result == NodeVisitResult.SKIP_SIBLINGS) {
return;
}
if (result == NodeVisitResult.CONTINUE) {
result = root.accept(visitor);
}
if (result != NodeVisitResult.TERMINATE) {
return;
}
visitor.postVisitNode(root.getFirstKey(), root);
}
/**
* Returns the root node.
*
* @return the root node.
* @throws IOException if an I/O exception occurs.
*/
synchronized Node<K, V> getRoot() throws IOException {
return this.manager.getRoot(this);
}
/**
* Returns the branching factor.
*
* @return the branching factor.
*/
int getBranchingFactor() {
return this.b;
}
/**
* Returns the node manager used by this B+Tree.
*
* @return the node manager used by this B+Tree.
*/
NodeManager<K, V> getManager() {
return this.manager;
}
/**
* Allows this manager to take control of the storage of specified node.
*
* @param node the node.
* @return the decorated node.
* @throws IOException if an I/O problem occurs.
*/
Node<K, V> wrapNode(Node<K, V> node) throws IOException {
return this.manager.wrapNode(node);
}
/**
* Allows the user of the node to retrieve the original node.
*
* @param node the decorated node.
* @return the node.
* @throws IOException if an I/O problem occurs.
*/
Node<K, V> unwrapNode(Node<K, V> node) throws IOException {
return this.manager.unwrapNode(node);
}
/**
* Allows this manager to take control of the storage of specified value.
*
* @param value the value.
* @return the decorated value.
* @throws IOException if an I/O problem occurs.
*/
ValueWrapper<V> wrapValue(V value) throws IOException {
return this.manager.wrapValue(value);
}
/**
* Returns the root node.
*
* @return the root node.
* @throws IOException if an IO problem occurs while writing the data.
*/
private void setRoot(Node<K, V> root) throws IOException {
this.manager.setRoot(root);
}
}