package net.jxta.impl.xindice.core.filer; /* * The Apache Software License, Version 1.1 * * * Copyright (c) 1999 The Apache Software Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Xindice" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation and was * originally based on software copyright (c) 1999-2001, The dbXML * Group, L.L.C., http://www.dbxmlgroup.com. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * */ import net.jxta.impl.xindice.core.DBException; import net.jxta.impl.xindice.core.FaultCodes; import net.jxta.impl.xindice.core.data.Value; import net.jxta.impl.xindice.core.indexer.IndexQuery; import net.jxta.logging.Logging; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Map; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * 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. HashFiler 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><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><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><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. */ public class BTree extends Paged { private final static Logger LOG = Logger.getLogger(BTree.class.getName()); protected static final byte LEAF = 1; protected static final byte BRANCH = 2; protected static final byte STREAM = 3; /** * Cache of the recently used tree nodes. * * Cache contains weak references to the BTreeNode objects, keys are page numbers (Long objects). * Access synchronized by this map itself. */ private final Map<Long, WeakReference<BTreeNode>> cache = new WeakHashMap<Long, WeakReference<BTreeNode>>(); private BTreeFileHeader fileHeader; private BTreeRootInfo rootInfo; private BTreeNode rootNode; public BTree() { super(); fileHeader = (BTreeFileHeader) getFileHeader(); } public BTree(File file) { this(); setFile(file); } /** * Setting this option forces all system buffers with the underlying device * if sync is set writes return after all modified data and attributes of the DB * have been written to the device. * by default sync is true. * {@link java.io.FileDescriptor} * @param sync if true, invokes FD.sync(), an expensive operation, required to ensure high consistency, especially * with system failures. */ public void setSync(boolean sync) { this.sync = sync; } @Override 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; } } @Override 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(); long p = fileHeader.getRootPage(); rootInfo = new BTreeRootInfo(p); // Initialize root node rootNode = new BTreeNode(getPage(p), null, new Value[0], new long[0]); rootNode.ph.setStatus(LEAF); rootNode.write(); synchronized (cache) { cache.put(rootNode.page.getPageNum(), new WeakReference<BTreeNode>(rootNode)); } close(); return true; } catch (Exception e) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "Failed to create BTree, return false", e); } } } return false; } /** * 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) * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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) * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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<BTreeNode> ref = cache.get(page); if (ref != null) { node = ref.get(); } if (node == null) { node = new BTreeNode(getPage(page), parent); } else { node.parent = parent; } cache.put(node.page.getPageNum(), new WeakReference<BTreeNode>(node)); } node.read(); return node; } catch (Exception e) { if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "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.getPageNum(), new WeakReference<BTreeNode>(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. * @param ptrs pointers * @param values their values */ 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 * @throws java.io.IOException if an io error occurs */ 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 (Value value : values) { os.writeShort(value.getLength()); value.streamTo(os); } // Write out the pointers for (long ptr : ptrs) { os.writeLong(ptr); } 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. * @param idx the index * @return the BTree node */ 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"); } } 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? * @param value the value to split * @return true if successful */ 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 * @throws java.io.IOException if an io error occurs * @throws BTreeException if a DB exception occurs */ 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 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); } for (int i = 0; i < ptrs.length; i++) { // FIXME: VG: Optimize this loop if (i >= leftIdx && i <= rightIdx && query.testValue(values[i])) { callback.indexInfo(values[i], ptrs[i]); } } break; case IndexQuery.NBWX: case IndexQuery.NBW: case IndexQuery.NSW: 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); } for (int i = 0; i < ptrs.length; i++) { if (i <= leftIdx && 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 = 0; i < ptrs.length; i++) { if (i >= rightIdx && 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"); } } } } // ////////////////////////////////////////////////////////////////// @Override public FileHeader createFileHeader() { BTreeFileHeader header = new BTreeFileHeader(); header.setPageCount(1); header.setTotalCount(1); return header; } @Override public FileHeader createFileHeader(boolean read) throws IOException { return new BTreeFileHeader(read); } @Override public FileHeader createFileHeader(long pageCount) { return new BTreeFileHeader(pageCount); } @Override public FileHeader createFileHeader(long pageCount, int pageSize) { return new BTreeFileHeader(pageCount, pageSize); } @Override public PageHeader createPageHeader() { return new BTreePageHeader(); } /** * BTreeFileHeader */ protected class BTreeFileHeader extends FileHeader { private long rootPage = 0; public BTreeFileHeader() {} public BTreeFileHeader(long pageCount) { super(pageCount); } public BTreeFileHeader(long pageCount, int pageSize) { super(pageCount, pageSize); } public BTreeFileHeader(boolean read) throws IOException { super(read); } @Override public synchronized void read(RandomAccessFile raf) throws IOException { super.read(raf); rootPage = raf.readLong(); } @Override public synchronized void write(RandomAccessFile raf) throws IOException { super.write(raf); raf.writeLong(rootPage); } /** * The root page of the storage tree * @param rootPage the new root page */ public synchronized final void setRootPage(long rootPage) { this.rootPage = rootPage; setDirty(); } /** * The root page of the storage tree * @return the root page */ public synchronized final long getRootPage() { return rootPage; } } /** * BTreePageHeader */ protected class BTreePageHeader extends PageHeader { private short valueCount = 0; public BTreePageHeader() {} public BTreePageHeader(DataInputStream dis) throws IOException { super(dis); } @Override public synchronized void read(DataInputStream dis) throws IOException { super.read(dis); if (getStatus() == UNUSED) { return; } valueCount = dis.readShort(); } @Override public synchronized void write(DataOutputStream dos) throws IOException { super.write(dos); dos.writeShort(valueCount); } /** The number of values stored by this page * @param valueCount the value count */ public synchronized final void setValueCount(short valueCount) { this.valueCount = valueCount; setDirty(); } /** * The number of values stored by this page * @return the value count */ public synchronized final short getValueCount() { return valueCount; } /** * The number of pointers stored by this page * @return the pointer count */ public synchronized final short getPointerCount() { if (getStatus() == BRANCH) { return (short) (valueCount + 1); } else { return valueCount; } } } }