blob: c96d495ab617a4dc45c575f8b0a23b084da32aca [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.kahadb.index;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.kahadb.page.Page;
import org.apache.kahadb.page.PageFile;
import org.apache.kahadb.page.Transaction;
import org.apache.kahadb.util.Marshaller;
/**
* BTreeIndex represents a Variable Magnitude B+Tree in a Page File.
* A BTree is a bit flexible in that it can be used for set or
* map-based indexing. Leaf nodes are linked together for faster
* iteration of the values.
*
* <br>
* The Variable Magnitude attribute means that the BTree attempts
* to store as many values and pointers on one page as is possible.
*
* <br>
* The implementation can optionally a be Simple-Prefix B+Tree.
*
* <br>
* For those who don't know how a Simple-Prefix B+Tree works, the primary
* distinction is that instead of promoting actual keys to branch pages,
* when leaves are split, a shortest-possible separator is generated at
* the pivot. That separator is what is promoted to the parent branch
* (and continuing up the list). As a result, actual keys and pointers
* can only be found at the leaf level. This also affords the index the
* ability to ignore costly merging and redistribution of pages when
* deletions occur. Deletions only affect leaf pages in this
* implementation, and so it is entirely possible for a leaf page to be
* completely empty after all of its keys have been removed.
*
* , $Date$
*/
public class BTreeIndex<Key,Value> implements Index<Key,Value> {
private static final Logger LOG = LoggerFactory.getLogger(BTreeIndex.class);
/**
* Interface used to determine the simple prefix of two keys.
*
* , $Date$
*/
static public interface Prefixer<Key> {
/**
* This methods should return shortest prefix of value2 where the following still holds:<br/>
* value1 <= prefix <= value2.<br/><br/>
*
* When this method is called, the following is guaranteed:<br/>
* value1 < value2<br/><br/>
*
*
* @param value1
* @param value2
* @return
*/
public Key getSimplePrefix(Key value1, Key value2);
}
/**
* StringPrefixer is a Prefixer implementation that works on strings.
*/
static public class StringPrefixer implements Prefixer<String> {
/**
* Example:
* If value1 is "Hello World"
* and value 2 is "Help Me"
* then the result will be: "Help"
*
* @see Prefixer#getSimplePrefix
*/
public String getSimplePrefix(String value1, String value2) {
char[] c1 = value1.toCharArray();
char[] c2 = value2.toCharArray();
int n = Math.min(c1.length, c2.length);
int i =0;
while (i < n) {
if (c1[i] != c2[i]) {
return value2.substring(0,i+1);
}
i++;
}
if( n == c2.length ) {
return value2;
}
return value2.substring(0,n);
}
}
private PageFile pageFile;
private long pageId;
private AtomicBoolean loaded = new AtomicBoolean();
private final BTreeNode.Marshaller<Key, Value> marshaller = new BTreeNode.Marshaller<Key, Value>(this);
private Marshaller<Key> keyMarshaller;
private Marshaller<Value> valueMarshaller;
private Prefixer<Key> prefixer;
public BTreeIndex() {
}
public BTreeIndex(long rootPageId) {
this.pageId = rootPageId;
}
@SuppressWarnings("unchecked")
public BTreeIndex(Page page) {
this(page.getPageId());
}
public BTreeIndex(PageFile pageFile, long rootPageId) {
this.pageFile = pageFile;
this.pageId = rootPageId;
}
@SuppressWarnings("unchecked")
public BTreeIndex(PageFile pageFile, Page page) {
this(pageFile, page.getPageId());
}
synchronized public void load(Transaction tx) throws IOException {
if (loaded.compareAndSet(false, true)) {
LOG.debug("loading");
if( keyMarshaller == null ) {
throw new IllegalArgumentException("The key marshaller must be set before loading the BTreeIndex");
}
if( valueMarshaller == null ) {
throw new IllegalArgumentException("The value marshaller must be set before loading the BTreeIndex");
}
final Page<BTreeNode<Key,Value>> p = tx.load(pageId, null);
if( p.getType() == Page.PAGE_FREE_TYPE ) {
// Need to initialize it..
BTreeNode<Key, Value> root = createNode(p, null);
storeNode(tx, root, true);
}
}
}
synchronized public void unload(Transaction tx) {
if (loaded.compareAndSet(true, false)) {
}
}
private BTreeNode<Key,Value> getRoot(Transaction tx) throws IOException {
return loadNode(tx, pageId, null);
}
synchronized public boolean containsKey(Transaction tx, Key key) throws IOException {
assertLoaded();
return getRoot(tx).contains(tx, key);
}
synchronized public Value get(Transaction tx, Key key) throws IOException {
assertLoaded();
return getRoot(tx).get(tx, key);
}
synchronized public Value put(Transaction tx, Key key, Value value) throws IOException {
assertLoaded();
return getRoot(tx).put(tx, key, value);
}
synchronized public Value remove(Transaction tx, Key key) throws IOException {
assertLoaded();
return getRoot(tx).remove(tx, key);
}
public boolean isTransient() {
return false;
}
synchronized public void clear(Transaction tx) throws IOException {
getRoot(tx).clear(tx);
}
synchronized public int getMinLeafDepth(Transaction tx) throws IOException {
return getRoot(tx).getMinLeafDepth(tx, 0);
}
synchronized public int getMaxLeafDepth(Transaction tx) throws IOException {
return getRoot(tx).getMaxLeafDepth(tx, 0);
}
synchronized public void printStructure(Transaction tx, PrintWriter out) throws IOException {
getRoot(tx).printStructure(tx, out, "");
}
synchronized public void printStructure(Transaction tx, OutputStream out) throws IOException {
PrintWriter pw = new PrintWriter(out,false);
getRoot(tx).printStructure(tx, pw, "");
pw.flush();
}
synchronized public boolean isEmpty(final Transaction tx) throws IOException {
return getRoot(tx).isEmpty(tx);
}
synchronized public Iterator<Map.Entry<Key,Value>> iterator(final Transaction tx) throws IOException {
return getRoot(tx).iterator(tx);
}
synchronized public Iterator<Map.Entry<Key,Value>> iterator(final Transaction tx, Key initialKey) throws IOException {
return getRoot(tx).iterator(tx, initialKey);
}
synchronized public void visit(Transaction tx, BTreeVisitor<Key, Value> visitor) throws IOException {
getRoot(tx).visit(tx, visitor);
}
synchronized public Map.Entry<Key,Value> getFirst(Transaction tx) throws IOException {
return getRoot(tx).getFirst(tx);
}
synchronized public Map.Entry<Key,Value> getLast(Transaction tx) throws IOException {
return getRoot(tx).getLast(tx);
}
///////////////////////////////////////////////////////////////////
// Internal implementation methods
///////////////////////////////////////////////////////////////////
private void assertLoaded() throws IllegalStateException {
if( !loaded.get() ) {
throw new IllegalStateException("The BTreeIndex is not loaded");
}
}
///////////////////////////////////////////////////////////////////
// Internal methods made accessible to BTreeNode
///////////////////////////////////////////////////////////////////
BTreeNode<Key,Value> loadNode(Transaction tx, long pageId, BTreeNode<Key,Value> parent) throws IOException {
Page<BTreeNode<Key,Value>> page = tx.load(pageId, marshaller);
BTreeNode<Key, Value> node = page.get();
node.setPage(page);
node.setParent(parent);
return node;
}
BTreeNode<Key,Value> createNode(Transaction tx, BTreeNode<Key,Value> parent) throws IOException {
Page<BTreeNode<Key,Value>> p = tx.allocate();
BTreeNode<Key,Value> node = new BTreeNode<Key,Value>(this);
node.setPage(p);
node.setParent(parent);
node.setEmpty();
p.set(node);
return node;
}
BTreeNode<Key,Value> createNode(Page<BTreeNode<Key,Value>> p, BTreeNode<Key,Value> parent) throws IOException {
BTreeNode<Key,Value> node = new BTreeNode<Key,Value>(this);
node.setPage(p);
node.setParent(parent);
node.setEmpty();
p.set(node);
return node;
}
void storeNode(Transaction tx, BTreeNode<Key,Value> node, boolean overflow) throws IOException {
tx.store(node.getPage(), marshaller, overflow);
}
///////////////////////////////////////////////////////////////////
// Property Accessors
///////////////////////////////////////////////////////////////////
public PageFile getPageFile() {
return pageFile;
}
public long getPageId() {
return pageId;
}
public Marshaller<Key> getKeyMarshaller() {
return keyMarshaller;
}
public void setKeyMarshaller(Marshaller<Key> keyMarshaller) {
this.keyMarshaller = keyMarshaller;
}
public Marshaller<Value> getValueMarshaller() {
return valueMarshaller;
}
public void setValueMarshaller(Marshaller<Value> valueMarshaller) {
this.valueMarshaller = valueMarshaller;
}
public Prefixer<Key> getPrefixer() {
return prefixer;
}
public void setPrefixer(Prefixer<Key> prefixer) {
this.prefixer = prefixer;
}
public void setPageFile(PageFile pageFile) {
this.pageFile = pageFile;
}
public void setPageId(long pageId) {
this.pageId = pageId;
}
}