/*
* 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.
*
* $Id: BTree.java 533253 2007-04-27 23:12:12Z vgritsenko $
*/
package org.apache.xindice.core.filer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.core.DBException;
import org.apache.xindice.core.FaultCodes;
import org.apache.xindice.core.data.Value;
import org.apache.xindice.core.indexer.IndexQuery;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.DataOutput;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Map;
import java.util.WeakHashMap;
/**
* BTree represents a Variable Magnitude Simple-Prefix B+Tree File.
* A BTree is a bit flexible in that it can be used for set or
* map-based indexing. {@link BTreeFiler} uses the BTree as a set for
* producing RecordSet entries. The Indexers use BTree as a map for
* indexing entity and attribute values in Documents.
*
* <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.
*
* <br>
* Also, the Variable Magnitude attribute means that the btree attempts
* to store as many values and pointers on one page as is possible.
*
* <br>
* This implementation supports the notion of nested roots. This means
* that you can create a btree where the pointers actually point to the
* root of a separate btree being managed in the same file.
*
* @version $Revision: 533253 $, $Date: 2007-04-27 19:12:12 -0400 (Fri, 27 Apr 2007) $
*/
public class BTree extends Paged {
private static final Log log = LogFactory.getLog(BTree.class);
protected static final byte LEAF = 1;
protected static final byte BRANCH = 2;
protected static final byte STREAM = 3;
/**
* Identity map of the recently used tree nodes to ensure that same
* node is present in the memory once and only once.
*
* <p>Cache contains weak references to the BTreeNode objects, keys
* are page numbers (Long objects). Access synchronized by this map
* object itself.
*
* <p>This identity map can be made into cache to store nodes for
* extended time periods, but that might not be necessary since
* documents are cached on the Collection level.
*/
private final Map cache = new WeakHashMap();
private BTreeFileHeader fileHeader;
private BTreeRootInfo rootInfo;
private BTreeNode rootNode;
public BTree() {
super();
fileHeader = (BTreeFileHeader) getFileHeader();
}
public BTree(File file) {
this();
setFile(file);
}
public boolean open() throws DBException {
if (super.open()) {
long p = fileHeader.getRootPage();
rootInfo = new BTreeRootInfo(p);
rootNode = getBTreeNode(p, null);
return true;
} else {
return false;
}
}
public boolean create() throws DBException {
if (super.create()) {
try {
// Don't call this.open() as it will try to read rootNode from the disk
super.open();
Page p = getFreePage();
long pn = p.getPageNum();
fileHeader.setRootPage(pn);
rootInfo = new BTreeRootInfo(pn);
// Initialize root node
rootNode = new BTreeNode(p, null, new Value[0], new long[0]);
rootNode.ph.setStatus(LEAF);
rootNode.write();
synchronized (cache) {
cache.put(rootNode.page, new WeakReference(rootNode));
}
close();
return true;
} catch (Exception e) {
if (log.isWarnEnabled()) {
log.warn("Failed to create BTree, return false", e);
}
}
}
return false;
}
public synchronized boolean close() throws DBException {
boolean closed = super.close();
if (closed) {
synchronized (cache) {
cache.clear();
}
}
return closed;
}
/**
* addKey adds a Value to the BTree and associates a pointer with
* it. The pointer can be used for referencing any type of data, it
* just so happens that Xindice uses it for referencing pages of
* associated data in the BTree file or other files.
*
* @param value The Value to add
* @return Pointer to the value
*/
public long addKey(Value value) throws IOException, BTreeException {
return getRootNode().addKey(value);
}
/**
* addValue adds a Value to the BTree and associates a pointer with
* it. The pointer can be used for referencing any type of data, it
* just so happens that Xindice uses it for referencing pages of
* associated data in the BTree file or other files.
*
* @param value The Value to add
* @param pointer The pointer to associate with it
* @return The previous value for the pointer (or -1)
*/
public long addValue(Value value, long pointer) throws IOException, BTreeException {
return getRootNode().addValue(value, pointer);
}
/**
* addValue adds a Value to the BTree and associates a pointer with
* it. The pointer can be used for referencing any type of data, it
* just so happens that Xindice uses it for referencing pages of
* associated data in the BTree file or other files.
*
* @param root The BTree's root information (for nested trees)
* @param value The Value to add
* @param pointer The pointer to associate with it
* @return The previous value for the pointer (or -1)
*/
public long addValue(BTreeRootInfo root, Value value, long pointer) throws IOException, BTreeException {
return getRootNode(root).addValue(value, pointer);
}
/**
* removeValue removes a Value from the BTree and returns the
* associated pointer for it.
*
* @param value The Value to remove
* @return The pointer that was associated with it
*/
public long removeValue(Value value) throws IOException, BTreeException {
return getRootNode().removeValue(value);
}
/**
* removeValue removes a Value from the BTree and returns the
* associated pointer for it.
*
* @param root The BTree's root information (for nested trees)
* @param value The Value to remove
* @return The pointer that was associated with it
*/
public long removeValue(BTreeRootInfo root, Value value) throws IOException, BTreeException {
return getRootNode(root).removeValue(value);
}
/**
* findValue finds a Value in the BTree and returns the associated
* pointer for it.
*
* @param value The Value to find
* @return The pointer that was associated with it
*/
public long findValue(Value value) throws IOException, BTreeException {
return getRootNode().findValue(value);
}
/**
* findValue finds a Value in the BTree and returns the associated
* pointer for it.
*
* @param root The BTree's root information (for nested trees)
* @param value The Value to find
* @return The pointer that was associated with it
*/
public long findValue(BTreeRootInfo root, Value value) throws IOException, BTreeException {
return getRootNode(root).findValue(value);
}
/**
* query performs a query against the BTree and performs callback
* operations to report the search results.
*
* @param query The IndexQuery to use (or null for everything)
* @param callback The callback instance
*/
public void query(IndexQuery query, BTreeCallback callback) throws IOException, BTreeException {
getRootNode().query(query, callback);
}
/**
* query performs a query against the BTree and performs callback
* operations to report the search results.
*
* @param root The BTree's root information (for nested trees)
* @param query The IndexQuery to use (or null for everything)
* @param callback The callback instance
*/
public void query(BTreeRootInfo root, IndexQuery query, BTreeCallback callback) throws IOException, BTreeException {
getRootNode(root).query(query, callback);
}
/**
* createBTreeRoot creates a new BTree root node in the BTree file
* based on the provided value for the main tree.
*
* @param v The sub-tree Value to create
* @return The new BTreeRootInfo instance
*/
protected final BTreeRootInfo createBTreeRoot(Value v) throws IOException, BTreeException {
BTreeNode n = createBTreeNode(BTree.LEAF, null);
n.write();
long position = n.page.getPageNum();
addValue(v, position);
return new BTreeRootInfo(v, position);
}
/**
* createBTreeRoot creates a new BTree root node in the BTree file
* based on the provided root information, and value for the tree.
*
* @param root The BTreeRootInfo to build off of
* @param v The sub-tree Value to create
* @return The new BTreeRootInfo instance
*/
protected final BTreeRootInfo createBTreeRoot(BTreeRootInfo root, Value v) throws IOException, BTreeException {
BTreeNode n = createBTreeNode(BTree.LEAF, null);
n.write();
long position = n.page.getPageNum();
addValue(v, position);
return new BTreeRootInfo(root, v, position);
}
/**
* findBTreeRoot searches for a BTreeRoot in the file and returns
* the BTreeRootInfo for the specified value based on the main tree.
*
* @param v The sub-tree Value to search for
* @return The new BTreeRootInfo instance
*/
protected final BTreeRootInfo findBTreeRoot(Value v) throws IOException, BTreeException {
long position = findValue(v);
return new BTreeRootInfo(v, position);
}
/**
* findBTreeRoot searches for a BTreeRoot in the file and returns
* the BTreeRootInfo for the specified value based on the provided
* BTreeRootInfo value.
*
* @param root The BTreeRootInfo to search from
* @param v The sub-tree Value to search for
* @return The new BTreeRootInfo instance
*/
protected final BTreeRootInfo findBTreeRoot(BTreeRootInfo root, Value v) throws IOException, BTreeException {
long position = findValue(root, v);
return new BTreeRootInfo(root, v, position);
}
/**
* setRootNode resets the root for the specified root object to the
* provided BTreeNode's page number.
*
* This method is not thread safe.
*
* @param root The root to reset
* @param newRoot the new root node to use
*/
protected final void setRootNode(BTreeRootInfo root, BTreeNode newRoot) throws IOException, BTreeException {
BTreeRootInfo parent = root.getParent();
if (parent == null) {
rootNode = newRoot;
long p = rootNode.page.getPageNum();
rootInfo.setPage(p);
fileHeader.setRootPage(p);
} else {
long p = newRoot.page.getPageNum();
root.setPage(p);
addValue(parent, root.name, p);
}
}
/**
* setRootNode resets the file's root to the provided
* BTreeNode's page number.
*
* This method is not thread safe.
*
* @param rootNode the new root node to use
*/
protected final void setRootNode(BTreeNode rootNode) throws IOException, BTreeException {
setRootNode(rootInfo, rootNode);
}
/**
* getRootNode retreives the BTree node for the specified
* root object.
*
* @param root The root object to retrieve with
* @return The root node
*/
protected final BTreeNode getRootNode(BTreeRootInfo root) {
if (root.page == rootInfo.page) {
return rootNode;
} else {
return getBTreeNode(root.getPage(), null);
}
}
/**
* getRootNode retreives the BTree node for the file's root.
*
* @return The root node
*/
protected final BTreeNode getRootNode() {
return rootNode;
}
private BTreeNode getBTreeNode(long page, BTreeNode parent) {
try {
BTreeNode node = null;
synchronized (cache) {
WeakReference ref = (WeakReference) cache.get(new PageKey(page));
if (ref != null) {
node = (BTreeNode) ref.get();
}
if (node == null) {
node = new BTreeNode(getPage(page), parent);
} else {
node.parent = parent;
}
cache.put(node.page, new WeakReference(node));
}
node.read();
return node;
} catch (Exception e) {
if (log.isWarnEnabled()) {
log.warn("Ignored exception", e);
}
return null;
}
}
private BTreeNode createBTreeNode(byte status, BTreeNode parent) throws IOException {
Page p = getFreePage();
BTreeNode node = new BTreeNode(p, parent, new Value[0], new long[0]);
node.ph.setStatus(status);
synchronized (cache) {
cache.put(p, new WeakReference(node));
}
return node;
}
/**
* BTreeRootInfo
*/
public final class BTreeRootInfo {
private final BTreeRootInfo parent;
private final Value name;
private long page;
public BTreeRootInfo(BTreeRootInfo parent, String name, long page) {
this.parent = parent;
this.name = new Value(name);
this.page = page;
}
public BTreeRootInfo(BTreeRootInfo parent, Value name, long page) {
this.parent = parent;
this.name = name;
this.page = page;
}
public BTreeRootInfo(String name, long page) {
this.parent = rootInfo;
this.name = new Value(name);
this.page = page;
}
public BTreeRootInfo(Value name, long page) {
this.parent = rootInfo;
this.name = name;
this.page = page;
}
private BTreeRootInfo(long page) {
parent = null;
name = null;
this.page = page;
}
public BTreeRootInfo getParent() {
return parent;
}
public Value getName() {
return name;
}
public synchronized long getPage() {
return page;
}
public synchronized void setPage(long page) {
this.page = page;
}
}
/**
* BTreeNode
*/
private final class BTreeNode {
private final Page page;
private final BTreePageHeader ph;
private Value[] values;
private long[] ptrs;
private BTreeNode parent;
private boolean loaded;
public BTreeNode(Page page) {
this(page, null);
}
public BTreeNode(Page page, BTreeNode parent) {
this.page = page;
this.parent = parent;
this.ph = (BTreePageHeader) page.getPageHeader();
}
public BTreeNode(Page page, BTreeNode parent, Value[] values, long[] ptrs) {
this(page, parent);
set(values, ptrs);
this.loaded = true;
}
/**
* Sets values and pointers.
* Internal (to the BTreeNode) method, not synchronized.
*/
private void set(Value[] values, long[] ptrs) {
this.values = values;
this.ph.setValueCount((short) values.length);
this.ptrs = ptrs;
}
/**
* Reads node only if it is not loaded yet
*/
public synchronized void read() throws IOException {
if (!this.loaded) {
Value v = readValue(page);
DataInputStream is = new DataInputStream(v.getInputStream());
// Read in the Values
values = new Value[ph.getValueCount()];
for (int i = 0; i < values.length; i++) {
short valSize = is.readShort();
byte[] b = new byte[valSize];
is.read(b);
values[i] = new Value(b);
}
// Read in the pointers
ptrs = new long[ph.getPointerCount()];
for (int i = 0; i < ptrs.length; i++) {
ptrs[i] = is.readLong();
}
this.loaded = true;
}
}
public synchronized void write() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(fileHeader.getWorkSize());
DataOutputStream os = new DataOutputStream(bos);
// Write out the Values
for (int i = 0; i < values.length; i++) {
os.writeShort(values[i].getLength());
values[i].streamTo(os);
}
// Write out the pointers
for (int i = 0; i < ptrs.length; i++) {
os.writeLong(ptrs[i]);
}
writeValue(page, new Value(bos.toByteArray()));
}
/**
* Internal (to the BTreeNode) method.
* Because this method is called only by BTreeNode itself, no synchronization done inside of this method.
*/
private BTreeNode getChildNode(int idx) {
if (ph.getStatus() == BRANCH && idx >= 0 && idx < ptrs.length) {
return getBTreeNode(ptrs[idx], this);
} else {
return null;
}
}
/* Not used
private synchronized void getChildStream(int idx, Streamable stream) throws IOException {
if (ph.getStatus() == LEAF && idx >= 0 && idx < ptrs.length) {
Value v = readValue(ptrs[idx]);
DataInputStream dis = new DataInputStream(v.getInputStream());
stream.read(dis);
}
}
*/
public synchronized long removeValue(Value value) throws IOException, BTreeException {
int idx = Arrays.binarySearch(values, value);
switch (ph.getStatus()) {
case BRANCH:
idx = idx < 0 ? -(idx + 1) : idx + 1;
return getChildNode(idx).removeValue(value);
case LEAF:
if (idx < 0) {
throw new BTreeNotFoundException("Value '" + value + "' doesn't exist");
} else {
long oldPtr = ptrs[idx];
set(deleteArrayValue(values, idx), deleteArrayLong(ptrs, idx));
write();
return oldPtr;
}
default :
throw new BTreeCorruptException("Invalid page type '" + ph.getStatus() +
"' in removeValue");
}
}
public synchronized long addValue(Value value, long pointer) throws IOException, BTreeException {
if (value == null) {
throw new BTreeException(FaultCodes.DBE_CANNOT_CREATE, "Can't add a null Value");
}
int idx = Arrays.binarySearch(values, value);
switch (ph.getStatus()) {
case BRANCH:
idx = idx < 0 ? -(idx + 1) : idx + 1;
return getChildNode(idx).addValue(value, pointer);
case LEAF:
if (idx >= 0) {
// Value was found... Overwrite
long oldPtr = ptrs[idx];
ptrs[idx] = pointer;
set(values, ptrs);
write();
return oldPtr;
} else {
// Value was not found
idx = -(idx + 1);
// Check to see if we've exhausted the block
boolean split = needSplit(value);
set(insertArrayValue(values, value, idx), insertArrayLong(ptrs, pointer, idx));
if (split) {
split();
} else {
write();
}
}
return -1;
default :
throw new BTreeCorruptException("Invalid Page Type In addValue");
}
}
public synchronized long addKey(Value value) throws IOException, BTreeException {
if (value == null) {
throw new BTreeException(FaultCodes.DBE_CANNOT_CREATE, "Can't add a null Value");
}
int idx = Arrays.binarySearch(values, value);
switch (ph.getStatus()) {
case BRANCH:
idx = idx < 0 ? -(idx + 1) : idx + 1;
return getChildNode(idx).addKey(value);
case LEAF:
if (idx >= 0) {
// Key already exists
return ptrs[idx];
} else {
// Value was not found
idx = -(idx + 1);
// Check to see if we've exhausted the block
boolean split = needSplit(value);
long pointer = getFreePage().getPageNum();
set(insertArrayValue(values, value, idx), insertArrayLong(ptrs, pointer, idx));
if (split) {
split();
} else {
write();
}
fileHeader.incRecordCount();
return pointer;
}
default :
throw new BTreeCorruptException("Invalid Page Type In addValue");
}
}
private synchronized void promoteValue(Value value, long rightPointer) throws IOException, BTreeException {
// Check to see if we've exhausted the block
boolean split = needSplit(value);
int idx = Arrays.binarySearch(values, value);
idx = idx < 0 ? -(idx + 1) : idx + 1;
set(insertArrayValue(values, value, idx), insertArrayLong(ptrs, rightPointer, idx + 1));
if (split) {
split();
} else {
write();
}
}
private Value getSeparator(Value value1, Value value2) {
int idx = value1.compareTo(value2);
byte[] b = new byte[Math.abs(idx)];
value2.copyTo(b, 0, b.length);
return new Value(b);
}
/**
* Do we need to split this node after adding one more value?
*/
private boolean needSplit(Value value) {
// Do NOT split if just 4 key/values are in the node.
return this.values.length > 4 &&
// CurrLength + one Long pointer + value length + one short
this.ph.getDataLen() + 8 + value.getLength() + 2 > BTree.this.fileHeader.getWorkSize();
}
/**
* Internal to the BTreeNode method
*/
private void split() throws IOException, BTreeException {
Value[] leftVals;
Value[] rightVals;
long[] leftPtrs;
long[] rightPtrs;
Value separator;
short vc = ph.getValueCount();
int pivot = vc / 2;
// Split the node into two nodes
switch (ph.getStatus()) {
case BRANCH:
leftVals = new Value[pivot];
leftPtrs = new long[leftVals.length + 1];
rightVals = new Value[vc - (pivot + 1)];
rightPtrs = new long[rightVals.length + 1];
System.arraycopy(values, 0, leftVals, 0, leftVals.length);
System.arraycopy(ptrs, 0, leftPtrs, 0, leftPtrs.length);
System.arraycopy(values, leftVals.length + 1, rightVals, 0, rightVals.length);
System.arraycopy(ptrs, leftPtrs.length, rightPtrs, 0, rightPtrs.length);
separator = values[leftVals.length];
break;
case LEAF:
leftVals = new Value[pivot];
leftPtrs = new long[leftVals.length];
rightVals = new Value[vc - pivot];
rightPtrs = new long[rightVals.length];
System.arraycopy(values, 0, leftVals, 0, leftVals.length);
System.arraycopy(ptrs, 0, leftPtrs, 0, leftPtrs.length);
System.arraycopy(values, leftVals.length, rightVals, 0, rightVals.length);
System.arraycopy(ptrs, leftPtrs.length, rightPtrs, 0, rightPtrs.length);
separator = getSeparator(leftVals[leftVals.length - 1], rightVals[0]);
break;
default :
throw new BTreeCorruptException("Invalid Page Type In split");
}
// Promote the pivot to the parent branch
if (parent == null) {
// This can only happen if this is the root
BTreeNode rNode = createBTreeNode(ph.getStatus(), this);
rNode.set(rightVals, rightPtrs);
BTreeNode lNode = createBTreeNode(ph.getStatus(), this);
lNode.set(leftVals, leftPtrs);
ph.setStatus(BRANCH);
set(new Value[] {
separator
},
new long[] {
lNode.page.getPageNum(),
rNode.page.getPageNum()
});
write();
rNode.write();
lNode.write();
} else {
set(leftVals, leftPtrs);
BTreeNode rNode = createBTreeNode(ph.getStatus(), parent);
rNode.set(rightVals, rightPtrs);
write();
rNode.write();
parent.promoteValue(separator,
rNode.page.getPageNum());
}
}
/////////////////////////////////////////////////////////////////
public synchronized long findValue(Value value) throws IOException, BTreeException {
if (value == null) {
throw new BTreeNotFoundException("Can't search on null Value");
}
int idx = Arrays.binarySearch(values, value);
switch (ph.getStatus()) {
case BRANCH:
idx = idx < 0 ? -(idx + 1) : idx + 1;
return getChildNode(idx).findValue(value);
case LEAF:
if (idx < 0) {
throw new BTreeNotFoundException("Value '" + value + "' doesn't exist");
} else {
return ptrs[idx];
}
default :
throw new BTreeCorruptException("Invalid page type '" + ph.getStatus() +
"' in findValue");
}
}
// query is a BEAST of a method
public synchronized void query(IndexQuery query, BTreeCallback callback) throws IOException, BTreeException {
if (query != null && query.getOperator() != IndexQuery.ANY) {
Value[] qvals = query.getValues();
int n;
int leftIdx = Arrays.binarySearch(values, qvals[0]);
int rightIdx = qvals.length > 1 ? Arrays.binarySearch(values, qvals[qvals.length - 1]) : leftIdx;
switch (ph.getStatus()) {
case BRANCH:
leftIdx = leftIdx < 0 ? -(leftIdx + 1) : leftIdx + 1;
rightIdx = rightIdx < 0 ? -(rightIdx + 1) : rightIdx + 1;
switch (query.getOperator()) {
case IndexQuery.BWX:
case IndexQuery.BW:
case IndexQuery.IN:
case IndexQuery.SW:
// TODO: Can leftIdx be less than 0 here?
if (leftIdx < 0) {
leftIdx = 0;
}
if (rightIdx > ptrs.length - 1) {
rightIdx = ptrs.length - 1;
}
for (int i = leftIdx; i <= rightIdx; i++) {
getChildNode(i).query(query, callback);
}
break;
case IndexQuery.NBWX:
case IndexQuery.NBW:
case IndexQuery.NIN:
case IndexQuery.NSW:
if (leftIdx > ptrs.length - 1) {
leftIdx = ptrs.length - 1;
}
for (int i = 0; i <= leftIdx; i++) {
getChildNode(i).query(query, callback);
}
if (rightIdx < 0) {
rightIdx = 0;
}
for (int i = rightIdx; i < ptrs.length; i++) {
getChildNode(i).query(query, callback);
}
break;
case IndexQuery.EQ:
getChildNode(leftIdx).query(query, callback);
break;
case IndexQuery.LT:
case IndexQuery.LEQ:
if (leftIdx > ptrs.length - 1) {
leftIdx = ptrs.length - 1;
}
for (int i = 0; i <= leftIdx; i++) {
getChildNode(i).query(query, callback);
}
break;
case IndexQuery.GT:
case IndexQuery.GEQ:
if (rightIdx < 0) {
rightIdx = 0;
}
for (int i = rightIdx; i < ptrs.length; i++) {
getChildNode(i).query(query, callback);
}
break;
case IndexQuery.NEQ:
default :
for (int i = 0; i < ptrs.length; i++) {
getChildNode(i).query(query, callback);
}
break;
}
break;
case LEAF:
switch (query.getOperator()) {
case IndexQuery.EQ:
if (leftIdx >= 0) {
callback.indexInfo(values[leftIdx], ptrs[leftIdx]);
}
break;
case IndexQuery.NEQ:
for (int i = 0; i < ptrs.length; i++) {
if (i != leftIdx) {
callback.indexInfo(values[i], ptrs[i]);
}
}
break;
case IndexQuery.BWX:
case IndexQuery.BW:
case IndexQuery.SW:
case IndexQuery.IN:
if (leftIdx < 0) {
leftIdx = -(leftIdx + 1);
}
if (rightIdx < 0) {
rightIdx = -(rightIdx + 1);
}
n = Math.min(rightIdx + 1, ptrs.length);
for (int i = leftIdx; i < n; i++){
if (query.testValue(values[i])) {
callback.indexInfo(values[i], ptrs[i]);
}
}
break;
case IndexQuery.NBWX:
case IndexQuery.NBW:
case IndexQuery.NSW:
// FIXME: Looks like operators are not used now. Need query optimizer?
if (leftIdx < 0) {
leftIdx = -(leftIdx + 1);
}
if (rightIdx < 0) {
rightIdx = -(rightIdx + 1);
}
for (int i = 0; i < ptrs.length; i++) {
if ((i <= leftIdx || i >= rightIdx) && query.testValue(values[i])) {
callback.indexInfo(values[i], ptrs[i]);
}
}
break;
case IndexQuery.LT:
case IndexQuery.LEQ:
if (leftIdx < 0) {
leftIdx = -(leftIdx + 1);
}
n = Math.min(leftIdx + 1, ptrs.length);
for (int i = 0; i < n; i++) {
if (query.testValue(values[i])) {
callback.indexInfo(values[i], ptrs[i]);
}
}
break;
case IndexQuery.GT:
case IndexQuery.GEQ:
if (rightIdx < 0) {
rightIdx = -(rightIdx + 1);
}
for (int i = rightIdx; i < ptrs.length; i++) {
if (query.testValue(values[i])) {
callback.indexInfo(values[i], ptrs[i]);
}
}
break;
case IndexQuery.NIN:
default :
for (int i = 0; i < ptrs.length; i++) {
if (query.testValue(values[i])) {
callback.indexInfo(values[i], ptrs[i]);
}
}
break;
}
break;
default :
throw new BTreeCorruptException("Invalid Page Type In query");
}
} else {
// No Query - Just Walk The Tree
switch (ph.getStatus()) {
case BRANCH:
for (int i = 0; i < ptrs.length; i++) {
getChildNode(i).query(query, callback);
}
break;
case LEAF:
for (int i = 0; i < values.length; i++) {
callback.indexInfo(values[i], ptrs[i]);
}
break;
default :
throw new BTreeCorruptException("Invalid Page Type In query");
}
}
}
}
////////////////////////////////////////////////////////////////////
protected FileHeader createFileHeader() {
return new BTreeFileHeader();
}
protected PageHeader createPageHeader() {
return new BTreePageHeader();
}
/**
* BTreeFileHeader
*/
protected class BTreeFileHeader extends FileHeader {
private long rootPage;
public BTreeFileHeader() {
}
protected synchronized void read(RandomAccessFile raf) throws IOException {
super.read(raf);
rootPage = raf.readLong();
}
protected synchronized void write(RandomAccessFile raf) throws IOException {
super.write(raf);
raf.writeLong(rootPage);
}
/** The root page of the storage tree */
public synchronized final void setRootPage(long rootPage) {
this.rootPage = rootPage;
setDirty();
}
/** The root page of the storage tree */
public synchronized final long getRootPage() {
return rootPage;
}
}
/**
* BTreePageHeader
*/
protected class BTreePageHeader extends PageHeader {
private short valueCount;
public BTreePageHeader() {
}
public BTreePageHeader(DataInput dis) throws IOException {
super(dis);
}
public synchronized void read(DataInput dis) throws IOException {
super.read(dis);
if (getStatus() == UNUSED) {
return;
}
valueCount = dis.readShort();
}
public synchronized void write(DataOutput dos) throws IOException {
super.write(dos);
dos.writeShort(valueCount);
}
/** The number of values stored by this page */
public synchronized final void setValueCount(short valueCount) {
this.valueCount = valueCount;
setDirty();
}
/** The number of values stored by this page */
public synchronized final short getValueCount() {
return valueCount;
}
/** The number of pointers stored by this page */
public synchronized final short getPointerCount() {
if (getStatus() == BRANCH) {
return (short) (valueCount + 1);
} else {
return valueCount;
}
}
}
}