Skip to content
Open
171 changes: 159 additions & 12 deletions src/main/java/btree4j/BTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,17 @@
import btree4j.utils.io.FastMultiByteArrayOutputStream;
import btree4j.utils.lang.ArrayUtils;
import btree4j.utils.lang.Primitives;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Arrays;
import com.google.common.annotations.Beta;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.Arrays;

/**
* BTree represents a Variable Magnitude Simple-Prefix B+Tree File.
Expand Down Expand Up @@ -96,6 +91,7 @@ public class BTree extends Paged {

private BTreeRootInfo _rootInfo;
private BTreeNode _rootNode;
private BTreeNode _firstNode;

public BTree(@Nonnull File file) {
this(file, true);
Expand All @@ -106,7 +102,7 @@ public BTree(@Nonnull File file, boolean duplicateAllowed) {
}

public BTree(@Nonnull File file, @Nonnegative int pageSize, int caches,
boolean duplicateAllowed) {
boolean duplicateAllowed) {
super(file, pageSize);
BTreeFileHeader fh = getFileHeader();
fh.incrTotalPageCount(); // for root page
Expand Down Expand Up @@ -169,6 +165,7 @@ public boolean open() throws BTreeException {
long p = _fileHeader.getRootPage();
this._rootInfo = new BTreeRootInfo(p);
this._rootNode = getBTreeNode(_rootInfo, p, null);
this._firstNode = this._rootNode;
return true;
} else {
return false;
Expand Down Expand Up @@ -197,6 +194,7 @@ public boolean create(boolean close) throws BTreeException {
if (close) {
close();
}
this._firstNode = this._rootNode;
return true;
}
return false;
Expand All @@ -206,6 +204,24 @@ protected final boolean isDuplicateAllowed() {
return _fileHeader._duplicateAllowed;
}

/**
* peeks first value in Btree.
* @return minimum value
*/
public BTreeKey peekMinimum() throws BTreeException {
_firstNode = _firstNode.getFirstNode();
return _firstNode.peekMinimum();
}

/**
* pops first value in Btree.
* @return minimum value
*/
public synchronized BTreeKey popMinimum() throws BTreeException {
_firstNode = _firstNode.getFirstNode();
return _firstNode.popMinimum();
}

/**
* addValue adds a Value to the BTree and associates a pointer with it. The pointer can be used
* for referencing any type of data.
Expand Down Expand Up @@ -249,6 +265,10 @@ public synchronized int removeValue(@Nonnull Value key, long pointer) throws BTr
}
}

public synchronized void removeValueFrom(Value value) throws BTreeException {
_rootNode.removeFrom(value);
}

/**
* findValue finds a Value in the BTree and returns the associated pointer for it.
*
Expand Down Expand Up @@ -532,6 +552,14 @@ protected BTreeNode(final BTreeRootInfo root, final Page page) {
this.ph = (BTreePageHeader) page.getPageHeader();
}

private void clearParent() {
if (parentCache != null || ph.parentPage != Paged.NO_PAGE) {
ph.parentPage = Paged.NO_PAGE;
this.parentCache = null;
this.dirty = true;
}
}

private BTreeNode getParent() {
if (parentCache != null) {
return parentCache;
Expand All @@ -557,6 +585,84 @@ private void setParent(BTreeNode node) {
}
}

private boolean isEmpty() {
return this.ptrs.length == 0;
}

private void removeSelf() throws BTreeException {
if (ph.getStatus() == BRANCH) {
throw new RuntimeException("removeSelf is not implemented for Branch nodes.");
}

if (this._prev != -1) {
BTreeNode prev = getBTreeNode(root, this._prev);
prev._next = this._next;
}
if (this._next != -1) {
BTreeNode next = getBTreeNode(root, this._next);
next._prev = this._prev;
}

BTreeNode parent = this.getParent();
if (parent != null) {
parent.removeChild(this.page.getPageNum());
clearParent();
}
}
private void removeChild(long pointer) throws BTreeException {
if (this.ph.getStatus() == LEAF) return;
for (int i = 0; i < ptrs.length; i++) {
if (ptrs[i] == pointer && keys.length != 0) {
decrDataLength(keys[i]);
set(ArrayUtils.remove(keys, i), ArrayUtils.remove(ptrs, i));
break;
}
}
if (this.ptrs.length == 1) {
BTreeNode parent = this.getParent();
if (parent != null) {
parent.updateChild(this.page.getPageNum(), this.ptrs[0]);
} else {
_rootNode = getBTreeNode(_rootInfo, ptrs[0]);
}
}
}
private void updateChild(long currentPointer, long newPointer) throws BTreeException {
for (int i = 0; i < ptrs.length; i++) {
if (ptrs[i] == currentPointer) {
BTreeNode child = getBTreeNode(_rootInfo, newPointer, this);
// child.setParent(this); // Previous line makes it redundant?
ptrs[i] = newPointer;
break;
}
}
}

private BTreeNode getFirstNode() throws BTreeException {
if (ph.getStatus() == BRANCH) {
return getChildNode(0).getFirstNode();
} else if (this._prev != -1) { // Very unlikely, mostly for robustness
return getBTreeNode(root, this._prev).getFirstNode();
} else if (this.ptrs.length == 0) { // _prev links should be set correctly
return getBTreeNode(root, this._next).getFirstNode();
}
return this;
}

BTreeKey peekMinimum() {
return new BTreeKey(keys[0], ptrs[0]);
}

BTreeKey popMinimum() throws BTreeException {
BTreeKey result = new BTreeKey(keys[0], ptrs[0]);
set(ArrayUtils.remove(keys, 0), ArrayUtils.remove(ptrs, 0));
decrDataLength(result.getKey());
if (this.isEmpty()) {
removeSelf();
}
return result;
}

long addValue(@Nonnull Value key, final long pointer) throws IOException, BTreeException {
int idx = searchRightmostKey(keys, key, keys.length);
switch (ph.getStatus()) {
Expand Down Expand Up @@ -655,6 +761,43 @@ private int searchRightmostKey(final Value[] ary, final Value key, final int to)
return -(low + 1); // key not found.
}

/**
* remove all values greater than a threshold
* @return variable indicating if it should delete the child node as well
*/
@Beta
void removeFrom(Value searchKey) throws BTreeException {
resetDataLength();
int leftIdx = searchLeftmostKey(keys, searchKey, keys.length);
switch (ph.getStatus()) {
case BRANCH:
leftIdx = (leftIdx < 0) ? -(leftIdx + 1) : leftIdx;
getChildNode(leftIdx).removeFrom(searchKey);

if (leftIdx < keys.length && leftIdx + 1 < ptrs.length)
set(ArrayUtils.copyOf(keys, leftIdx), ArrayUtils.copyOf(ptrs, leftIdx + 1));
break;
case LEAF:
leftIdx = (leftIdx < 0) ? -(leftIdx + 1) : leftIdx;
if (leftIdx < keys.length && leftIdx < ptrs.length) {
set(ArrayUtils.copyOf(keys, leftIdx), ArrayUtils.copyOf(ptrs, leftIdx));
}
this._next = -1;
break;
default:
throw new BTreeCorruptException(
"Invalid page type '" + ph.getStatus() + "' in removeValue");
}

if (getParent() == null) {
while (_rootNode.ptrs.length == 1) {
_rootNode = _rootNode.getChildNode(0);
}
_rootNode.clearParent();
}
calculateDataLength();
}

/** @return pointer of left-most matched item */
long removeValue(Value searchKey) throws IOException, BTreeException {
int leftIdx = searchLeftmostKey(keys, searchKey, keys.length);
Expand Down Expand Up @@ -1064,6 +1207,10 @@ private void write() throws IOException, BTreeException {
setDirty(false);
}

private void resetDataLength() {
currentDataLen = -1;
}

private int calculateDataLength() {
if (currentDataLen > 0) {
return currentDataLen;
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/btree4j/BTreeKey.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package btree4j;

public class BTreeKey {
private Value key;
private Long pointer;

public BTreeKey(Value key, Long pointer) {
this.key = key;
this.pointer = pointer;
}

public Value getKey() {
return key;
}

public Long getPointer() {
return pointer;
}
}
100 changes: 100 additions & 0 deletions src/main/java/btree4j/utils/lang/ArrayUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
package btree4j.utils.lang;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Random;

Expand Down Expand Up @@ -389,6 +390,105 @@ public static boolean equals(final char[] a, final char[] a2, final int off, fin
return true;
}

public static char[] copyOfRange(final char[] original, final int from, final int to) {
final int newLength = to - from;
if (newLength < 0) {
throw new IllegalArgumentException(from + " > " + to);
}
final char[] copy = new char[newLength];
System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
return copy;
}

public static byte[] copyOfRange(final byte[] original, final int from, final int to) {
final int newLength = to - from;
if (newLength < 0) {
throw new IllegalArgumentException(from + " > " + to);
}
final byte[] copy = new byte[newLength];
System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
return copy;
}

public static int[] copyOfRange(final int[] original, final int from, final int to) {
final int newLength = to - from;
if (newLength < 0) {
throw new IllegalArgumentException(from + " > " + to);
}
final int[] copy = new int[newLength];
System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
return copy;
}

public static long[] copyOfRange(final long[] original, final int from, final int to) {
final int newLength = to - from;
if (newLength < 0) {
throw new IllegalArgumentException(from + " > " + to);
}
final long[] copy = new long[newLength];
System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
return copy;
}

@SuppressWarnings("unchecked")
public static <T> T[] copyOfRange(final T[] original, final int from, final int to) {
return copyOfRange(original, from, to, (Class<T[]>) original.getClass());
}

@SuppressWarnings("unchecked")
private static <T, U> T[] copyOfRange(final U[] original, final int from, final int to,
final Class<? extends T[]> newType) {
final int newLength = to - from;
if (newLength < 0) {
throw new IllegalArgumentException(from + " > " + to);
}
final T[] copy = ((Object) newType == (Object) Object[].class) ? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
return copy;
}

public static byte[] copyOf(final byte[] original, final int newLength) {
final byte[] copy = new byte[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}

public static int[] copyOf(final int[] original, final int newLength) {
final int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}

public static long[] copyOf(final long[] vals, final int idx) {
return Arrays.copyOf(vals, idx);

}

public static char[] copyOf(final char[] original, final int newLength) {
final char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}

@SuppressWarnings("unchecked")
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}

@SuppressWarnings("unchecked")
public static <T, U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
final T[] copy = ((Object) newType == (Object) Object[].class) ? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}

/**
* @param fromIndex Inclusive first index
* @param toIndex Non-inclusive last index
* @return returns either the found index or -1 * (the index of the first key greater than key + 1)
*/
public static <T extends Comparable<T>> int binarySearch(final T[] a, final int fromIndex,
final int toIndex, final T key) {
int low = fromIndex;
Expand Down
Loading