You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by ch...@apache.org on 2008/07/18 17:49:52 UTC
svn commit: r677944 [8/11] - in /activemq/sandbox/kahadb: ./ src/ src/main/
src/main/java/ src/main/java/org/ src/main/java/org/apache/
src/main/java/org/apache/kahadb/ src/main/java/org/apache/kahadb/impl/
src/main/java/org/apache/kahadb/impl/async/ s...
Added: activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/Value.java
URL: http://svn.apache.org/viewvc/activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/Value.java?rev=677944&view=auto
==============================================================================
--- activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/Value.java (added)
+++ activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/Value.java Fri Jul 18 08:49:48 2008
@@ -0,0 +1,303 @@
+/*
+ * 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: Value.java 594315 2007-11-12 22:10:44Z vgritsenko $
+ */
+
+package org.apache.kahadb.xindice;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
+/**
+ * Value is the primary base class for all data storing objects.
+ * The content window of value objects are immutable, but the
+ * underlying byte array which was used for constructing the value might be not.
+ *
+ * @version $Revision: 594315 $, $Date: 2007-11-12 17:10:44 -0500 (Mon, 12 Nov 2007) $
+ */
+public class Value implements Comparable {
+
+ protected final byte[] data;
+ protected final int pos;
+ protected final int len;
+
+ /**
+ * Usually there is no need to create a copy of the value, since it is
+ * an immutable object. This constructor is mainly used to create
+ * key objects.
+ *
+ * @param value the value object which data will be used to construct this
+ * value.
+ */
+ public Value(Value value) {
+ this.data = value.data;
+ this.pos = value.pos;
+ this.len = value.len;
+ }
+
+ public Value(byte[] data) {
+ this.data = data;
+ this.pos = 0;
+ this.len = data.length;
+ }
+
+ public Value(byte[] data, int pos, int len) {
+ if (pos >= data.length || pos < 0 || pos + len > data.length) {
+ throw new ArrayIndexOutOfBoundsException("Value cannot be created");
+ }
+
+ this.data = data;
+ this.pos = pos;
+ this.len = len;
+ }
+
+ public Value(String data) {
+ try {
+ this.data = data.getBytes("utf-8");
+ this.pos = 0;
+ this.len = this.data.length;
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Java doesn't support UTF-8 encoding", e);
+ }
+ }
+
+ public boolean equals(Value value) {
+ return len == value.len && hashCode() == value.hashCode() && compareTo(value) == 0;
+ }
+
+ public int hashCode() {
+ // modeled after String.hashCode()
+ int rc = 0;
+ for (int i = 0; i < len; i++) {
+ rc = 31 * rc + data[pos + i];
+ }
+ return rc;
+ }
+
+ public final int compareTo(Value value) {
+ byte[] ddata = value.data;
+ int dpos = value.pos;
+ int dlen = value.len;
+
+ int stop = len > dlen ? dlen : len;
+
+ for (int i = 0; i < stop; i++) {
+ byte b1 = data[pos + i];
+ byte b2 = ddata[dpos + i];
+
+ if (b1 != b2) {
+ // get unsigned value
+ int s1 = ((int) b1) & 0xFF;
+ int s2 = ((int) b2) & 0xFF;
+ return s1 > s2 ? (i + 1) : -(i + 1);
+ }
+ }
+
+ if (len == dlen) {
+ return 0;
+ } else {
+ return len > dlen ? stop + 1 : -(stop + 1);
+ }
+ }
+
+ public final int compareTo(Object obj) {
+ return compareTo((Value) obj);
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if( obj == null || obj.getClass() != getClass() ) {
+ return false;
+ }
+ return equals((Value) obj);
+ }
+
+
+ /**
+ * getLength retrieves the length of the data being stored by the Value.
+ *
+ * @return The Value length
+ */
+ public final int getLength() {
+ return len;
+ }
+
+ /**
+ * getData retrieves a copy of the data which is being stored
+ * by this value as a byte array.
+ *
+ * <p>Data copying is performed in order to ensure immutability of the Value.
+ * Avoid using this method if possible.
+ *
+ * @return The data
+ */
+ public final byte[] getData() {
+ byte[] b = new byte[len];
+ System.arraycopy(data, pos, b, 0, len);
+ return b;
+ }
+
+ //
+ // Data extraction
+ //
+
+ /**
+ * Returns the byte at the specified index.
+ *
+ * @param index byte index
+ * @return the byte at the specified index.
+ * @throws ArrayIndexOutOfBoundsException if index is negative number or
+ * is not less that the length of Value data
+ */
+ public final byte byteAt(int index) {
+ if (index < 0 || index >= len) {
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+ return data[pos + index];
+ }
+
+ /**
+ * Returns the short value at the specified index.
+ *
+ * @param index short index
+ * @return the short at the specified index.
+ * @throws ArrayIndexOutOfBoundsException if index is negative number or
+ * is not less that the length of the data array
+ */
+ public final short shortAt(int index) {
+ return (short) ((data[index += pos] << 8) | data[index + 1]);
+ }
+
+ /**
+ * Returns the int value at the specified index.
+ *
+ * @param index int index
+ * @return the int at the specified index.
+ * @throws ArrayIndexOutOfBoundsException if index is negative number or
+ * is not less that the length of the data array
+ */
+ public final int intAt(int index) {
+ return (short) ((data[index += pos] << 24) | (data[index + 1] << 16) | (data[index + 2] << 8) | data[index + 3]);
+ }
+
+ /**
+ * Get a value that is part of this value object.
+ *
+ * @param start beginning index
+ * @param len length of the new value
+ * @return Value object
+ * @throws ArrayIndexOutOfBoundsException if start index is either negative
+ * or isn't less then length of original Value
+ */
+ public final Value valueAt(int start, int len) {
+ return new Value(data, pos + start, len);
+ }
+
+ /**
+ * Get a key that is part of this value object.
+ *
+ * @param start beginning index
+ * @param len length of the new key
+ * @return Key object
+ * @throws ArrayIndexOutOfBoundsException if start index is either negative
+ * or isn't less then length of original Value
+ */
+ public final Key keyAt(int start, int len) {
+ return new Key(data, pos + start, len);
+ }
+
+ //
+ // I/O
+ //
+
+ /**
+ * Return an InputStream for the value.
+ *
+ * @return An InputStream
+ */
+ public final InputStream getInputStream() {
+ return new ByteArrayInputStream(data, pos, len);
+ }
+
+ /**
+ * Stream the content of the value into an OutputStream.
+ *
+ * @param out the OutputStream
+ * @throws IOException if write failed
+ */
+ public final void streamTo(OutputStream out) throws IOException {
+ out.write(data, pos, len);
+ }
+
+ /**
+ * Copy contents of the value into supplied byte array.
+ *
+ * @param tdata byte array for the value
+ * @param tpos starting position
+ */
+ public final void copyTo(byte[] tdata, int tpos) {
+ System.arraycopy(data, pos, tdata, tpos, len);
+ }
+
+ /**
+ * Copy <code>len</code> bytes of value's content into supplied
+ * byte array.
+ *
+ * @param tdata byte array for the value
+ * @param tpos starting position
+ * @param len count of bytes to copy
+ */
+ public final void copyTo(byte[] tdata, int tpos, int len) {
+ System.arraycopy(data, pos, tdata, tpos, len);
+ }
+
+ //
+ // Comparisons
+ //
+
+ public final boolean startsWith(Value value) {
+ if (len < value.len) {
+ return false;
+ }
+
+ byte[] ddata = value.data;
+ int dpos = value.pos;
+
+ for (int i = 0; i < value.len; i++) {
+ if (data[i + pos] != ddata[i + dpos]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ public final String toString() {
+ try {
+ return new String(data, pos, len, "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Java doesn't seem to support UTF-8!", e);
+ }
+ }
+}
Added: activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTree.java
URL: http://svn.apache.org/viewvc/activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTree.java?rev=677944&view=auto
==============================================================================
--- activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTree.java (added)
+++ activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTree.java Fri Jul 18 08:49:48 2008
@@ -0,0 +1,1151 @@
+/*
+ * 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 541508 2007-05-25 01:54:12Z vgritsenko $
+ */
+
+package org.apache.kahadb.xindice.btree;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.kahadb.xindice.FaultCodes;
+import org.apache.kahadb.xindice.IndexException;
+import org.apache.kahadb.xindice.Key;
+import org.apache.kahadb.xindice.page.Paged;
+
+/**
+ * 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 BTreeIndex} 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: 541508 $, $Date: 2007-05-24 21:54:12 -0400 (Thu, 24 May 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 IndexException {
+ if (super.open()) {
+ long p = fileHeader.getRootPage();
+ rootInfo = new BTreeRootInfo(p);
+ rootNode = getBTreeNode(p, null);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public boolean create() throws IndexException {
+ 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 Key[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 IndexException {
+ boolean closed = super.close();
+ if (closed) {
+ synchronized (cache) {
+ cache.clear();
+ }
+ }
+
+ return closed;
+ }
+
+ /**
+ * addKey adds a Key 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 Key to add
+ * @return Pointer to the value
+ */
+ public long addKey(Key value) throws IOException, IndexException {
+ return getRootNode().addKey(value);
+ }
+
+ /**
+ * addValue adds a Key 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 Key to add
+ * @param pointer The pointer to associate with it
+ * @return The previous value for the pointer (or -1)
+ */
+ public long addValue(Key value, long pointer) throws IOException, IndexException {
+ return getRootNode().addValue(value, pointer);
+ }
+
+ /**
+ * addValue adds a Key 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 Key to add
+ * @param pointer The pointer to associate with it
+ * @return The previous value for the pointer (or -1)
+ */
+ public long addValue(BTreeRootInfo root, Key value, long pointer) throws IOException, IndexException {
+ return getRootNode(root).addValue(value, pointer);
+ }
+
+ /**
+ * removeValue removes a Key from the BTree and returns the
+ * associated pointer for it.
+ *
+ * @param value The Key to remove
+ * @return The pointer that was associated with it
+ */
+ public long removeValue(Key value) throws IOException, IndexException {
+ return getRootNode().removeValue(value);
+ }
+
+ /**
+ * removeValue removes a Key from the BTree and returns the
+ * associated pointer for it.
+ *
+ * @param root The BTree's root information (for nested trees)
+ * @param value The Key to remove
+ * @return The pointer that was associated with it
+ */
+ public long removeValue(BTreeRootInfo root, Key value) throws IOException, IndexException {
+ return getRootNode(root).removeValue(value);
+ }
+
+ /**
+ * findValue finds a Key in the BTree and returns the associated
+ * pointer for it.
+ *
+ * @param value The Key to find
+ * @return The pointer that was associated with it
+ */
+ public long findValue(Key value) throws IOException, IndexException {
+ return getRootNode().findValue(value);
+ }
+
+ /**
+ * findValue finds a Key in the BTree and returns the associated
+ * pointer for it.
+ *
+ * @param root The BTree's root information (for nested trees)
+ * @param value The Key to find
+ * @return The pointer that was associated with it
+ */
+ public long findValue(BTreeRootInfo root, Key value) throws IOException, IndexException {
+ 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, IndexException {
+// 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, IndexException {
+// getRootNode(root).query(query, callback);
+// }
+//
+
+ /**
+ * Iterate 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 iterate(BTreeCallback callback) throws IOException, IndexException {
+ getRootNode().iterate(callback);
+ }
+
+ /**
+ * Iterates the BTree and performs callback
+ * operations to report the search results.
+ *
+ * @param root The BTree's root information (for nested trees)
+ * @param callback The callback instance
+ */
+ public void iterate(BTreeRootInfo root, BTreeCallback callback) throws IOException, IndexException {
+ getRootNode(root).iterate(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 Key to create
+ * @return The new BTreeRootInfo instance
+ */
+ protected final BTreeRootInfo createBTreeRoot(Key v) throws IOException, IndexException {
+ 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 Key to create
+ * @return The new BTreeRootInfo instance
+ */
+ protected final BTreeRootInfo createBTreeRoot(BTreeRootInfo root, Key v) throws IOException, IndexException {
+ 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 Key to search for
+ * @return The new BTreeRootInfo instance
+ */
+ protected final BTreeRootInfo findBTreeRoot(Key v) throws IOException, IndexException {
+ 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 Key to search for
+ * @return The new BTreeRootInfo instance
+ */
+ protected final BTreeRootInfo findBTreeRoot(BTreeRootInfo root, Key v) throws IOException, IndexException {
+ 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, IndexException {
+ 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, IndexException {
+ 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 Key[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 Key name;
+ private long page;
+
+ public BTreeRootInfo(BTreeRootInfo parent, String name, long page) {
+ this.parent = parent;
+ this.name = new Key(name);
+ this.page = page;
+ }
+
+ public BTreeRootInfo(BTreeRootInfo parent, Key name, long page) {
+ this.parent = parent;
+ this.name = name;
+ this.page = page;
+ }
+
+ public BTreeRootInfo(String name, long page) {
+ this.parent = rootInfo;
+ this.name = new Key(name);
+ this.page = page;
+ }
+
+ public BTreeRootInfo(Key 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 Key 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 Key[] 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, Key[] 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(Key[] keys, long[] ptrs) {
+ this.values = keys;
+ this.ph.setValueCount((short) keys.length);
+ this.ptrs = ptrs;
+ }
+
+ /**
+ * Reads node only if it is not loaded yet
+ */
+ public synchronized void read() throws IOException {
+ if (!this.loaded) {
+ Key v = new Key(readValue(page));
+ DataInputStream is = new DataInputStream(v.getInputStream());
+
+ // Read in the Keys
+ values = new Key[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 Key(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 Keys
+ 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 Key(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) {
+ Key v = readValue(ptrs[idx]);
+ DataInputStream dis = new DataInputStream(v.getInputStream());
+ stream.read(dis);
+ }
+ }
+ */
+
+ public synchronized long removeValue(Key value) throws IOException, IndexException {
+ 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("Key '" + 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(Key value, long pointer) throws IOException, IndexException {
+ if (value == null) {
+ throw new IndexException(FaultCodes.DBE_CANNOT_CREATE, "Can't add a null Key");
+ }
+
+ 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) {
+ // Key was found... Overwrite
+ long oldPtr = ptrs[idx];
+ ptrs[idx] = pointer;
+
+ set(values, ptrs);
+
+ write();
+ return oldPtr;
+ } else {
+ // Key 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(Key value) throws IOException, IndexException {
+ if (value == null) {
+ throw new IndexException(FaultCodes.DBE_CANNOT_CREATE, "Can't add a null Key");
+ }
+
+ 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 {
+ // Key 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(Key value, long rightPointer) throws IOException, IndexException {
+ // 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 Key getSeparator(Key value1, Key value2) {
+ int idx = value1.compareTo(value2);
+ byte[] b = new byte[Math.abs(idx)];
+ value2.copyTo(b, 0, b.length);
+ return new Key(b);
+ }
+
+ /**
+ * Do we need to split this node after adding one more value?
+ */
+ private boolean needSplit(Key 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, IndexException {
+ Key[] leftVals;
+ Key[] rightVals;
+ long[] leftPtrs;
+ long[] rightPtrs;
+ Key separator;
+
+ short vc = ph.getValueCount();
+ int pivot = vc / 2;
+
+ // Split the node into two nodes
+ switch (ph.getStatus()) {
+ case BRANCH:
+ leftVals = new Key[pivot];
+ leftPtrs = new long[leftVals.length + 1];
+ rightVals = new Key[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 Key[pivot];
+ leftPtrs = new long[leftVals.length];
+ rightVals = new Key[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 Key[] {
+ 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(Key value) throws IOException, IndexException {
+ if (value == null) {
+ throw new BTreeNotFoundException("Can't search on null Key");
+ }
+
+ 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("Key '" + 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, IndexException {
+// if (query != null && query.getOperator() != IndexQuery.ANY) {
+// Key[] 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
+// iterate(callback);
+// }
+// }
+
+ private void iterate(BTreeCallback callback) throws BTreeCorruptException {
+ switch (ph.getStatus()) {
+ case BRANCH:
+ for (int i = 0; i < ptrs.length; i++) {
+ getChildNode(i).iterate(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;
+ }
+ }
+ }
+}
Added: activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeCallback.java
URL: http://svn.apache.org/viewvc/activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeCallback.java?rev=677944&view=auto
==============================================================================
--- activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeCallback.java (added)
+++ activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeCallback.java Fri Jul 18 08:49:48 2008
@@ -0,0 +1,40 @@
+/*
+ * 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: BTreeCallback.java 541508 2007-05-25 01:54:12Z vgritsenko $
+ */
+
+package org.apache.kahadb.xindice.btree;
+
+import org.apache.kahadb.xindice.Value;
+
+/**
+ * BTreeCallback is a callback interface for BTree queries.
+ *
+ * @version $Revision: 541508 $, $Date: 2007-05-24 21:54:12 -0400 (Thu, 24 May 2007) $
+ */
+public interface BTreeCallback {
+
+ /**
+ * indexInfo is a callback method for index enumeration.
+ *
+ * @param value The Value being reported
+ * @param pointer The data pointer being reported
+ * @return false to cancel the enumeration
+ */
+ boolean indexInfo(Value value, long pointer);
+}
+
Added: activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeCorruptException.java
URL: http://svn.apache.org/viewvc/activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeCorruptException.java?rev=677944&view=auto
==============================================================================
--- activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeCorruptException.java (added)
+++ activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeCorruptException.java Fri Jul 18 08:49:48 2008
@@ -0,0 +1,44 @@
+/*
+ * 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: BTreeCorruptException.java 541508 2007-05-25 01:54:12Z vgritsenko $
+ */
+
+package org.apache.kahadb.xindice.btree;
+
+import org.apache.kahadb.xindice.FaultCodes;
+import org.apache.kahadb.xindice.IndexException;
+
+
+/**
+ * A BTreecorruptException is thrown by the BTree if the BTree
+ * appears to be corrupted in some way.
+ *
+ * @version $Revision: 541508 $, $Date: 2007-05-24 21:54:12 -0400 (Thu, 24 May 2007) $
+ */
+public final class BTreeCorruptException extends IndexException {
+ public BTreeCorruptException() {
+ super(FaultCodes.IDX_CORRUPTED);
+ }
+
+ public BTreeCorruptException(String message) {
+ super(FaultCodes.IDX_CORRUPTED, message);
+ }
+
+ public BTreeCorruptException(String message, Throwable cause) {
+ super(FaultCodes.IDX_CORRUPTED, message, cause);
+ }
+}
Added: activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeIndex.java
URL: http://svn.apache.org/viewvc/activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeIndex.java?rev=677944&view=auto
==============================================================================
--- activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeIndex.java (added)
+++ activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeIndex.java Fri Jul 18 08:49:48 2008
@@ -0,0 +1,327 @@
+/*
+ * 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: BTreeFiler.java 541516 2007-05-25 02:46:51Z vgritsenko $
+ */
+
+package org.apache.kahadb.xindice.btree;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.kahadb.xindice.FaultCodes;
+import org.apache.kahadb.xindice.Index;
+import org.apache.kahadb.xindice.IndexException;
+import org.apache.kahadb.xindice.Key;
+import org.apache.kahadb.xindice.Record;
+import org.apache.kahadb.xindice.RecordSet;
+import org.apache.kahadb.xindice.Value;
+
+/**
+ * BTreeFiler is a Filer implementation based on the BTree class.
+ *
+ * <br>
+ * BTreeFiler has folowing configuration attributes:
+ * <ul>
+ * <li><strong>pagesize</strong>: Size of the page used by the filer.
+ * Default page size is 4096 bytes. This parameter can be set only
+ * before paged file is created. Once it is created, this parameter
+ * can not be changed.</li>
+ * <li><strong>pagecount</strong>: Number of pages filer will be created
+ * with.</li>
+ * <li><strong>maxkeysize</strong>: Maximum allowed size of the key.
+ * Default maximum key size is 256 bytes.</li>
+ * <li><strong>max-descriptors</strong>: Defines maximum amount of
+ * simultaneously opened file descriptors this paged file can have.
+ * Several descriptors are needed to provide multithreaded access
+ * to the underlying file. Too large number will limit amount of
+ * collections you can open. Default value is 16
+ * (DEFAULT_DESCRIPTORS_MAX).</li>
+ * </ul>
+ *
+ * @version $Revision: 541516 $, $Date: 2007-05-24 22:46:51 -0400 (Thu, 24 May 2007) $
+ */
+public class BTreeIndex extends BTree
+ implements Index {
+
+ private static final Log log = LogFactory.getLog(BTreeIndex.class);
+
+ /**
+ * Record page status
+ */
+ protected static final byte RECORD = 20;
+
+
+ private BTreeFilerHeader fileHeader;
+
+
+ public BTreeIndex() {
+ super();
+ fileHeader = (BTreeFilerHeader) getFileHeader();
+ }
+
+ public void setLocation(File root, String location) {
+ setFile(new File(root, location + ".tbl"));
+ }
+
+ public String getName() {
+ return "BTreeFiler";
+ }
+
+ public Record readRecord(Key key) throws IndexException {
+ return readRecord(key, false);
+ }
+
+ public Record readRecord(Key key, boolean metaOnly) throws IndexException {
+ if (key == null || key.getLength() == 0) {
+ return null;
+ }
+
+ checkOpened();
+ try {
+ long pos = findValue(key);
+ Page startPage = getPage(pos);
+ Value v = metaOnly ? null : readValue(startPage);
+ BTreeFilerPageHeader sph = (BTreeFilerPageHeader) startPage.getPageHeader();
+ return new Record(key, v);
+ } catch (BTreeNotFoundException e) {
+ if (log.isDebugEnabled()) {
+ log.debug("Record '" + key + "' not found: " + e);
+ }
+ } catch (IOException e) {
+ throw new IndexException(FaultCodes.DBE_CANNOT_READ,
+ "Can't read record '" + key + "': " + e.getMessage(), e);
+ }
+ return null;
+ }
+
+ public Record writeRecord(Key key, Value value) throws IndexException {
+ if (key == null || key.getLength() == 0) {
+ throw new IndexException(FaultCodes.DBE_CANNOT_CREATE, "Invalid key: null or empty");
+ }
+ if (value == null) {
+ throw new IndexException(FaultCodes.DBE_CANNOT_CREATE, "Invalid value: null");
+ }
+
+ checkOpened();
+ try {
+ long pos = addKey(key);
+ Page p = getPage(pos);
+
+ BTreeFilerPageHeader ph = (BTreeFilerPageHeader) p.getPageHeader();
+ long t = System.currentTimeMillis();
+ if (ph.getStatus() == UNUSED) {
+ ph.setCreated(t);
+ }
+ ph.setModified(t);
+ ph.setStatus(RECORD);
+
+ writeValue(p, value);
+ flush();
+
+ return new Record(key, value);
+
+ } catch (IOException e) {
+ // FIXME: cleanup?
+ throw new IndexException(FaultCodes.DBE_CANNOT_CREATE,
+ "Can't write record '" + key + "': " + e.getMessage(), e);
+ }
+ }
+
+ public boolean deleteRecord(Key key) throws IndexException {
+ if (key == null || key.getLength() == 0) {
+ return false;
+ }
+
+ checkOpened();
+ try {
+ long pos = removeValue(key);
+ Page p = getPage(pos);
+ unlinkPages(p);
+
+ fileHeader.decRecordCount();
+
+ flush();
+ return true;
+ } catch (BTreeNotFoundException e) {
+ if (log.isDebugEnabled()) {
+ log.debug("Record '" + key + "' not found (" + e + ")");
+ }
+ } catch (IOException e) {
+ throw new IndexException(FaultCodes.DBE_CANNOT_DROP,
+ "Can't delete record '" + key + "': " + e.getMessage(), e);
+ }
+
+ return false;
+ }
+
+ public long getRecordCount() throws IndexException {
+ checkOpened();
+ return fileHeader.getRecordCount();
+ }
+
+ public RecordSet getRecordSet() throws IndexException {
+ checkOpened();
+ return new BTreeFilerRecordSet();
+ }
+
+ /**
+ * BTreeFilerRecordSet
+ */
+ private class BTreeFilerRecordSet implements RecordSet, BTreeCallback {
+ private List keys = new ArrayList();
+ private Iterator iter;
+
+ public BTreeFilerRecordSet() throws IndexException {
+ try {
+ iterate(this);
+ iter = keys.iterator();
+ } catch (IOException e) {
+ throw new IndexException(FaultCodes.GEN_CRITICAL_ERROR,
+ "Error generating RecordSet", e);
+ }
+ }
+
+ public synchronized boolean indexInfo(Value value, long pointer) {
+ keys.add(new Key(value));
+ return true;
+ }
+
+ public synchronized Key getNextKey() {
+ return (Key) iter.next();
+ }
+
+ public synchronized Record getNextRecord() throws IndexException {
+ return readRecord((Key) iter.next(), false);
+ }
+
+ public synchronized Value getNextValue() throws IndexException {
+ return getNextRecord().getValue();
+ }
+
+ public synchronized boolean hasMoreRecords() {
+ return iter.hasNext();
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////
+
+ protected FileHeader createFileHeader() {
+ return new BTreeFilerHeader();
+ }
+
+ protected PageHeader createPageHeader() {
+ return new BTreeFilerPageHeader();
+ }
+
+ /**
+ * BTreeFilerHeader
+ */
+ private final class BTreeFilerHeader extends BTreeFileHeader {
+ private long totalBytes;
+
+ public BTreeFilerHeader() {
+ }
+
+ protected synchronized void read(RandomAccessFile raf) throws IOException {
+ super.read(raf);
+ totalBytes = raf.readLong();
+ }
+
+ protected synchronized void write(RandomAccessFile raf) throws IOException {
+ super.write(raf);
+ raf.writeLong(totalBytes);
+ }
+
+ /** The total number of bytes in use by the file */
+ public synchronized void setTotalBytes(long totalBytes) {
+ this.totalBytes = totalBytes;
+ setDirty();
+ }
+
+ /** The total number of bytes in use by the file */
+ public synchronized long getTotalBytes() {
+ return totalBytes;
+ }
+ }
+
+ /**
+ * BTreeFilerPageHeader
+ */
+ private final class BTreeFilerPageHeader extends BTreePageHeader {
+ private long created;
+ private long modified;
+
+ public BTreeFilerPageHeader() {
+ }
+
+ public BTreeFilerPageHeader(DataInput dis) throws IOException {
+ super(dis);
+ }
+
+ public synchronized void read(DataInput dis) throws IOException {
+ super.read(dis);
+
+ if (getStatus() == UNUSED) {
+ return;
+ }
+
+ created = dis.readLong();
+ modified = dis.readLong();
+ }
+
+ public synchronized void write(DataOutput dos) throws IOException {
+ super.write(dos);
+ dos.writeLong(created);
+ dos.writeLong(modified);
+ }
+
+ public synchronized void setRecordLen(int recordLen) {
+ fileHeader.setTotalBytes((fileHeader.totalBytes - getRecordLen()) + recordLen);
+ super.setRecordLen(recordLen);
+ }
+
+ /** UNIX-time when this record was created */
+ public synchronized void setCreated(long created) {
+ this.created = created;
+ setDirty();
+ }
+
+ /** UNIX-time when this record was created */
+ public synchronized long getCreated() {
+ return created;
+ }
+
+ /** UNIX-time when this record was last modified */
+ public synchronized void setModified(long modified) {
+ this.modified = modified;
+ setDirty();
+ }
+
+ /** UNIX-time when this record was last modified */
+ public synchronized long getModified() {
+ return modified;
+ }
+ }
+}
Added: activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeNotFoundException.java
URL: http://svn.apache.org/viewvc/activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeNotFoundException.java?rev=677944&view=auto
==============================================================================
--- activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeNotFoundException.java (added)
+++ activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/btree/BTreeNotFoundException.java Fri Jul 18 08:49:48 2008
@@ -0,0 +1,44 @@
+/*
+ * 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: BTreeNotFoundException.java 541508 2007-05-25 01:54:12Z vgritsenko $
+ */
+
+package org.apache.kahadb.xindice.btree;
+
+import org.apache.kahadb.xindice.FaultCodes;
+import org.apache.kahadb.xindice.IndexException;
+
+
+/**
+ * A BTreeNotFoundException is thrown by the BTree if a Value
+ * can't be found in the BTree.
+ *
+ * @version $Revision: 541508 $, $Date: 2007-05-24 21:54:12 -0400 (Thu, 24 May 2007) $
+ */
+public final class BTreeNotFoundException extends IndexException {
+ public BTreeNotFoundException() {
+ super(FaultCodes.IDX_VALUE_NOT_FOUND);
+ }
+
+ public BTreeNotFoundException(String message) {
+ super(FaultCodes.IDX_VALUE_NOT_FOUND, message);
+ }
+
+ public BTreeNotFoundException(String message, Throwable cause) {
+ super(FaultCodes.IDX_VALUE_NOT_FOUND, message, cause);
+ }
+}
Added: activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/fs/FileCache.java
URL: http://svn.apache.org/viewvc/activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/fs/FileCache.java?rev=677944&view=auto
==============================================================================
--- activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/fs/FileCache.java (added)
+++ activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/fs/FileCache.java Fri Jul 18 08:49:48 2008
@@ -0,0 +1,100 @@
+/*
+ * 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: FileCache.java 541508 2007-05-25 01:54:12Z vgritsenko $
+ */
+
+package org.apache.kahadb.xindice.fs;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * FileCache caches the content of files in memory.
+ *
+ * @version $Revision: 541508 $, $Date: 2007-05-24 21:54:12 -0400 (Thu, 24 May 2007) $
+ */
+public class FileCache {
+
+ /**
+ * Caches FileCacheInfo objects. Keys are File objects.
+ */
+ private final Map cache = new WeakHashMap();
+
+ public FileCache() {
+ }
+
+ public final boolean isInCache(File file) {
+ return (cache.get(file) != null);
+ }
+
+ public final boolean isInCache(String name) {
+ return (cache.get(new File(name)) != null);
+ }
+
+ public final boolean isModified(String name) {
+ return isModified(new File(name));
+ }
+
+ public final boolean isModified(File file) {
+ FileCacheInfo finfo = (FileCacheInfo) cache.get(file);
+ return !file.exists()
+ || finfo == null
+ || (file.lastModified() != finfo.lastModified);
+ }
+
+ public final byte[] getFile(String name) throws IOException {
+ return getFile(new File(name));
+ }
+
+ public final byte[] getFile(File file) throws IOException {
+ if (!file.exists()) {
+ return null;
+ }
+
+ FileCacheInfo finfo = (FileCacheInfo) cache.get(file);
+ long lastmod = file.lastModified();
+ if (finfo == null || finfo.lastModified != lastmod) {
+ FileInputStream fis = new FileInputStream(file);
+ byte[] content = new byte[fis.available()];
+ fis.read(content);
+ fis.close();
+ finfo = new FileCacheInfo(file, lastmod, content);
+ cache.put(file, finfo);
+ return content;
+ } else {
+ return finfo.content;
+ }
+ }
+
+ /**
+ * FileCacheInfo
+ */
+ private class FileCacheInfo {
+ public File file;
+ public long lastModified = 0;
+ public byte[] content;
+
+ public FileCacheInfo(File file, long lastModified, byte[] content) {
+ this.file = file;
+ this.lastModified = lastModified;
+ this.content = content;
+ }
+ }
+}
Added: activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/fs/FileSystemIndex.java
URL: http://svn.apache.org/viewvc/activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/fs/FileSystemIndex.java?rev=677944&view=auto
==============================================================================
--- activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/fs/FileSystemIndex.java (added)
+++ activemq/sandbox/kahadb/src/main/java/org/apache/kahadb/xindice/fs/FileSystemIndex.java Fri Jul 18 08:49:48 2008
@@ -0,0 +1,309 @@
+/*
+ * 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: FSIndex.java 541516 2007-05-25 02:46:51Z vgritsenko $
+ */
+
+package org.apache.kahadb.xindice.fs;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.apache.kahadb.xindice.FaultCodes;
+import org.apache.kahadb.xindice.Index;
+import org.apache.kahadb.xindice.IndexException;
+import org.apache.kahadb.xindice.Key;
+import org.apache.kahadb.xindice.Record;
+import org.apache.kahadb.xindice.RecordSet;
+import org.apache.kahadb.xindice.Value;
+
+/**
+ * FSIndex allows you to use existing file systems withing Xindice.
+ *
+ * @version $Revision: 541516 $, $Date: 2007-05-24 22:46:51 -0400 (Thu, 24 May 2007) $
+ */
+public final class FileSystemIndex implements Index {
+
+ private static final String EXT = "ext";
+ private static final String READONLY = "readonly";
+
+ private FileCache cache = new FileCache();
+ private LockManager locks = new LockManager(16);
+
+ private Set<String> extensionSet;
+ private String extensions;
+
+ private File dir;
+ private boolean opened;
+ private boolean readOnly;
+
+
+ public FileSystemIndex() {
+ }
+
+ public String getName() {
+ return "FSIndex";
+ }
+
+ public void setLocation(File root, String location) {
+ }
+
+ private void checkOpened() throws IndexException {
+ if (!opened) {
+ throw new IndexException(FaultCodes.COL_COLLECTION_CLOSED,
+ "Index is closed");
+ }
+ }
+
+ private void checkReadOnly() throws IndexException {
+ if (readOnly) {
+ throw new IndexException(FaultCodes.COL_COLLECTION_READ_ONLY,
+ "Index is read-only");
+ }
+ }
+
+ public boolean close() {
+ opened = false;
+ return true;
+ }
+
+ public boolean open() {
+ opened = (dir.exists() && dir.isDirectory());
+ return opened;
+ }
+
+ public boolean drop() {
+ opened = false;
+ return true;
+ }
+
+ public boolean isOpened() {
+ return opened;
+ }
+
+ public boolean exists() {
+ return dir.exists();
+ }
+
+ public boolean create() {
+ if (!dir.exists()) {
+ return dir.mkdirs();
+ } else {
+ return true;
+ }
+ }
+
+ public void flush() {
+ }
+
+ public Record readRecord(Key key) throws IndexException {
+ return readRecord(key, false);
+ }
+
+ public Record readRecord(Key key, boolean metaOnly) throws IndexException {
+ if (key == null || key.getLength() == 0) {
+ return null;
+ }
+
+ checkOpened();
+
+ String fname = key.toString();
+ if (!isExtensionValid(fname)) {
+ return null;
+ }
+
+ File file = new File(dir, fname);
+ try {
+ locks.acquireSharedLock(file);
+
+ if (metaOnly && file.exists()) {
+ return new Record(key, null);
+ } else {
+ byte[] valueData = cache.getFile(file);
+ if (valueData != null) {
+ return new Record(key, new Value(valueData));
+ }
+ }
+ } catch (IOException e) {
+ throw new IndexException(FaultCodes.DBE_CANNOT_READ,
+ "Can't read record '" + key + "': " + e.getMessage(), e);
+ } finally {
+ locks.releaseSharedLock(file);
+ }
+ return null;
+ }
+
+ public Record writeRecord(Key key, Value value) throws IndexException {
+ if (key == null || key.getLength() == 0) {
+ throw new IndexException(FaultCodes.DBE_CANNOT_CREATE, "Invalid key: '" + key + "'");
+ }
+ if (value == null) {
+ throw new IndexException(FaultCodes.DBE_CANNOT_CREATE, "Invalid null value");
+ }
+
+ checkOpened();
+ checkReadOnly();
+
+ String fname = key.toString();
+ if (!isExtensionValid(fname)) {
+ throw new IndexException(FaultCodes.DBE_CANNOT_CREATE, "Invalid extention");
+ }
+
+ File file = new File(dir, fname);
+ try {
+ locks.acquireExclusiveLock(file);
+ FileOutputStream fos = new FileOutputStream(file);
+ value.streamTo(fos);
+ fos.close();
+ return new Record(key, value);
+ } catch (IOException e) {
+ throw new IndexException(FaultCodes.DBE_CANNOT_CREATE,
+ "Can't write record '" + key + "': " + e.getMessage(), e);
+ } finally {
+ locks.releaseExclusiveLock(file);
+ }
+ }
+
+ public boolean deleteRecord(Key key) throws IndexException {
+ if (key == null || key.getLength() == 0) {
+ return false;
+ }
+ checkOpened();
+ checkReadOnly();
+
+ String fname = key.toString();
+ if (!isExtensionValid(fname)) {
+ return false;
+ }
+
+ File file = new File(dir, fname);
+ try {
+ locks.acquireExclusiveLock(file);
+ // TODO: Should Exception (SecurityException) be catched here or not?
+ return file.delete();
+ } finally {
+ locks.releaseExclusiveLock(file);
+ }
+ }
+
+ public long getRecordCount() throws IndexException {
+ checkOpened();
+
+ File[] files = dir.listFiles(new FileFilter() {
+ public boolean accept(File file) {
+ return file.isFile() && isExtensionValid(file.getName());
+ }
+ });
+ return files.length;
+ }
+
+ public RecordSet getRecordSet() throws IndexException {
+ checkOpened();
+ return new FSRecordSet();
+ }
+
+ private boolean isExtensionValid(String fname) {
+ if (extensionSet != null) {
+ int idx = fname.lastIndexOf('.');
+ if (idx == -1) {
+ return false;
+ }
+ String ext = fname.substring(idx + 1);
+ if (!extensionSet.contains(ext)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * FSRecordSet
+ */
+
+ private class FSRecordSet implements RecordSet {
+ public File[] files;
+ public int pos = 0;
+
+ public FSRecordSet() {
+ files = dir.listFiles(new FileFilter() {
+ public boolean accept(File file) {
+ return file.isFile() && isExtensionValid(file.getName());
+ }
+ });
+ }
+
+ public synchronized boolean hasMoreRecords() {
+ return pos < files.length;
+ }
+
+ public synchronized Record getNextRecord() throws IndexException {
+ File file = files[pos++];
+ return readRecord(new Key(file.getName()), false);
+ }
+
+ public synchronized Value getNextValue() throws IndexException {
+ return getNextRecord().getValue();
+ }
+
+ public synchronized Key getNextKey() {
+ return new Key(files[pos++].getName());
+ }
+ }
+
+ public File getDir() {
+ return dir;
+ }
+
+ public void setDir(File dir) {
+ this.dir = dir;
+ }
+
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ public void setReadOnly(boolean readOnly) {
+ this.readOnly = readOnly;
+ }
+
+ public Set<String> getExtensionSet() {
+ return extensionSet;
+ }
+
+ public void setExtensionSet(Set<String> extensionSet) {
+ this.extensionSet = extensionSet;
+ }
+
+ public String getExtensions() {
+ return extensions;
+ }
+
+ public void setExtensions(String extensions) {
+ this.extensions = extensions;
+ if (extensions != null && extensions.trim().length() > 0) {
+ extensionSet = new HashSet();
+ StringTokenizer st = new StringTokenizer(extensions);
+ while (st.hasMoreTokens()) {
+ extensionSet.add(st.nextToken());
+ }
+ }
+ }
+}