yacy_search_server/source/de/anomic/kelondro/kelondroTree.java
orbiter 417ed5102e redesign of database iterators:
an iteration of key elements in kelondroTree databases is no longer supported.
this is now replaced by an iteration of kelondroRow.Entry objects from the database
Iteration of keys from the database was mostly followed by retrieval of the row
from the database, whcih caused unnecessary database load.
The index selection was also redesigned to use the new row iteration methods.
This affects many funktions, most important is the DHT selection routine which is now much faster.



git-svn-id: https://svn.berlios.de/svnroot/repos/yacy/trunk@2327 6c8d7289-2bf4-0310-a012-ef5d649a1542
2006-07-26 11:21:51 +00:00

1590 lines
72 KiB
Java

// kelondroTree.java
// -----------------------
// part of The Kelondro Database
// (C) by Michael Peter Christen; mc@anomic.de
// first published on http://www.anomic.de
// Frankfurt, Germany, 2004
// last major change: 07.02.2005
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Using this software in any meaning (reading, learning, copying, compiling,
// running) means that you agree that the Author(s) is (are) not responsible
// for cost, loss of data or any harm that may be caused directly or indirectly
// by usage of this softare or this documentation. The usage of this software
// is on your own risk. The installation and usage (starting/running) of this
// software may allow other people or application to access your computer and
// any attached devices and is highly dependent on the configuration of the
// software which must be done by the user of the software; the author(s) is
// (are) also not responsible for proper configuration and usage of the
// software, even if provoked by documentation provided together with
// the software.
//
// Any changes to this file according to the GPL as documented in the file
// gpl.txt aside this file in the shipment you received can be done to the
// lines that follows this copyright notice here, but changes must not be
// done inside the copyright notive above. A re-distribution must contain
// the intact and unchanged copyright notice.
// Contributions and changes to the program code must be marked as such.
/*
This class extends the kelondroRecords and adds a tree structure
*/
package de.anomic.kelondro;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import java.util.logging.Logger;
import java.util.Map;
public class kelondroTree extends kelondroRecords implements kelondroIndex {
// logging (This probably needs someone to initialize the java.util.logging.* facilities);
public static Logger log = Logger.getLogger("KELONDRO");
// define the Over-Head-Array
private static short thisOHBytes = 2; // our record definition of two bytes
private static short thisOHHandles = 3; // and three handles overhead
private static short thisFHandles = 1; // file handles: one for a root pointer
// define pointers for OH array access
private static int magic = 0; // pointer for OHByte-array: marker for Node purpose; defaults to 1
private static int balance = 1; // pointer for OHByte-array: balance value of tree node; balanced = 0
private static int parent = 0; // pointer for OHHandle-array: handle()-Value of parent Node
private static int leftchild = 1; // pointer for OHHandle-array: handle()-Value of left child Node
private static int rightchild = 2; // pointer for OHHandle-array: handle()-Value of right child Node
private static int root = 0; // pointer for FHandles-array: pointer to root node
// calibration of cache
public static int defaultObjectCachePercent = 30;
// class variables
private Search writeSearchObj = new Search();
protected kelondroOrder objectOrder = new kelondroNaturalOrder(true);
private final kelondroOrder loopDetectionOrder = new kelondroNaturalOrder(true);
private int readAheadChunkSize = 100;
private long lastIteratorCount = readAheadChunkSize;
private kelondroObjectCache objectCache;
public kelondroTree(File file, long buffersize, long preloadTime, int objectCachePercent, int key, int value, boolean exitOnFail) {
this(file, buffersize, preloadTime, objectCachePercent, new kelondroRow(new int[] { key, value }), new kelondroNaturalOrder(true), 1, 8, exitOnFail);
}
public kelondroTree(File file, long buffersize, long preloadTime, int objectCachePercent, kelondroRow rowdef, boolean exitOnFail) {
// this creates a new tree file
this(file, buffersize, preloadTime, objectCachePercent, rowdef, new kelondroNaturalOrder(true), rowdef.columns() /* txtProps */, 80 /* txtPropWidth */, exitOnFail);
}
public kelondroTree(File file, long buffersize, long preloadTime, int objectCachePercent, kelondroRow rowdef, kelondroOrder objectOrder, int txtProps, int txtPropsWidth, boolean exitOnFail) {
// this creates a new tree file
super(file,
(100 - objectCachePercent) * buffersize / 100, preloadTime,
thisOHBytes, thisOHHandles, rowdef,
thisFHandles, txtProps, txtPropsWidth, exitOnFail);
try {
setHandle(root, null); // define the root value
} catch (IOException e) {
super.logFailure("cannot set root handle / " + e.getMessage());
if (exitOnFail) System.exit(-1);
throw new RuntimeException("cannot set root handle / " + e.getMessage());
}
this.objectOrder = objectOrder;
writeOrderType();
super.setLogger(log);
initObjectCache(buffersize, objectCachePercent);
}
public kelondroTree(kelondroRA ra, long buffersize, long preloadTime, int objectCachePercent, kelondroRow rowdef, boolean exitOnFail) {
// this creates a new tree within a kelondroRA
this(ra, buffersize, preloadTime, objectCachePercent, rowdef, new kelondroNaturalOrder(true), rowdef.columns() /* txtProps */, 80 /* txtPropWidth */, exitOnFail);
}
public kelondroTree(kelondroRA ra, long buffersize, long preloadTime, int objectCachePercent, kelondroRow rowdef, kelondroOrder objectOrder, int txtProps, int txtPropsWidth, boolean exitOnFail) {
// this creates a new tree within a kelondroRA
super(ra,
(100 - objectCachePercent) * buffersize / 100, preloadTime,
thisOHBytes, thisOHHandles, rowdef,
thisFHandles, txtProps, txtPropsWidth, exitOnFail);
try {
setHandle(root, null); // define the root value
} catch (IOException e) {
super.logFailure("cannot set root handle / " + e.getMessage());
if (exitOnFail) System.exit(-1);
throw new RuntimeException("cannot set root handle / " + e.getMessage());
}
this.objectOrder = objectOrder;
writeOrderType();
super.setLogger(log);
initObjectCache(buffersize, objectCachePercent);
}
public kelondroTree(File file, long buffersize, long preloadTime, int objectCachePercent) throws IOException {
// this opens a file with an existing tree file
super(file, (100 - objectCachePercent) * buffersize / 100, preloadTime);
readOrderType();
super.setLogger(log);
initObjectCache(buffersize, objectCachePercent);
}
public kelondroTree(kelondroRA ra, long buffersize, long preloadTime, int objectCachePercent) throws IOException {
// this opens a file with an existing tree in a kelondroRA
super(ra, (100 - objectCachePercent) * buffersize / 100, preloadTime);
readOrderType();
super.setLogger(log);
initObjectCache(buffersize, objectCachePercent);
}
private void initObjectCache(long buffersize, int objectCachePercent) {
if (objectCachePercent > 0) {
long objectbuffersize = objectCachePercent * buffersize / 100;
long nodecachesize = objectbuffersize / cacheObjectChunkSize();
this.objectCache = new kelondroObjectCache(this.filename, (int) nodecachesize, nodecachesize * 3000 , 2*1024*1024);
} else {
this.objectCache = null;
}
}
public final int cacheObjectChunkSize() {
return row().objectsize() + /* overhead */ 8 * super.row().columns();
}
public String[] cacheObjectStatus() {
if (this.objectCache == null) return null;
else return this.objectCache.status();
}
private void writeOrderType() {
try {
super.setDescription(objectOrder.signature().getBytes());
} catch (IOException e) {};
}
private void readOrderType() {
try {
byte[] d = super.getDescription();
String s = new String(d).substring(0, 2);
this.objectOrder = orderBySignature(s);
} catch (IOException e) {};
}
public static kelondroOrder orderBySignature(String signature) {
kelondroOrder oo = null;
if (oo == null) oo = kelondroNaturalOrder.bySignature(signature);
if (oo == null) oo = kelondroBase64Order.bySignature(signature);
if (oo == null) oo = new kelondroNaturalOrder(true);
return oo;
}
public kelondroOrder getOrder() {
// returns the order of this tree
return this.objectOrder;
}
public void clear() throws IOException {
super.clear();
setHandle(root, null); // reset the root value
}
private void commitNode(Node n) throws IOException {
Handle left = n.getOHHandle(leftchild);
Handle right = n.getOHHandle(rightchild);
if ((left == null) && (right == null)) n.commit(CP_LOW);
else if (left == null) n.commit(CP_MEDIUM);
else if (right == null) n.commit(CP_MEDIUM);
else n.commit(CP_HIGH);
}
// Returns the value to which this map maps the specified key.
public kelondroRow.Entry get(byte[] key) throws IOException {
// System.out.println("kelondroTree.get " + new String(key) + " in " + filename);
kelondroRow.Entry result = (objectCache == null) ? null : (kelondroRow.Entry) objectCache.get(key);
if (result != null) {
//System.out.println("cache hit in objectCache, db:" + super.filename);
return result;
}
if ((objectCache != null) && (objectCache.has(key) == -1)) return null;
synchronized (writeSearchObj) {
writeSearchObj.process(key);
if (writeSearchObj.found()) {
result = row().newEntry(writeSearchObj.getMatcher().getValueRow());
if (objectCache != null) objectCache.put(key, result);
} else {
result = null;
if (objectCache != null) objectCache.hasnot(key);
}
}
return result;
}
public class Search {
// a search object combines the results of a search in the tree, which are
// - the searched object is found, an index pointing to the node can be returned
// - the object was not found, an index pointing to an appropriate possible parent node
// can be returned, together with the information wether the new key shall
// be left or right child.
private Node thenode, parentnode;
private boolean found; // property if node was found
private byte child; // -1: left child; 0: root node; 1: right child
private Handle thisHandle;
protected Search() {
}
protected void process(byte[] key) throws IOException {
// searchs the database for the key and stores the result in the thisHandle
// if the key was found, then found=true, thisHandle and leftchild is set;
// else found=false and thisHandle and leftchild is undefined
thisHandle = getHandle(root);
parentnode = null;
if (key == null) {
child = 0;
if (thisHandle == null) {
thenode = null;
found = false;
} else {
thenode = getNode(thisHandle, null, 0);
found = true;
}
} else {
thenode = null;
child = 0;
found = false;
int c;
byte[] k;
TreeSet visitedNodeKeys = new TreeSet(loopDetectionOrder); // to detect loops
// System.out.println("Starting Compare Loop in Database " + filename); // debug
while (thisHandle != null) {
try {
parentnode = thenode;
thenode = getNode(thisHandle, thenode, (child == -1) ? leftchild : rightchild);
} catch (IllegalArgumentException e) {
logWarning("kelondroTree.Search.process: fixed a broken handle");
found = false;
return;
}
if (thenode == null) {
throw new kelondroException(filename, "kelondroTree.Search.process: thenode==null");
}
k = thenode.getKey();
if (k == null) {
found = false;
return;
} else {
if (visitedNodeKeys.contains(k)) {
// we have loops in the database.
// to fix this, all affected nodes must be patched
thenode.setOHByte(magic, (byte) 1);
thenode.setOHByte(balance, (byte) 0);
thenode.setOHHandle(parent, null);
thenode.setOHHandle(leftchild, null);
thenode.setOHHandle(rightchild, null);
thenode.commit(CP_NONE);
logWarning("kelondroTree.Search.process: database contains loops; the loop-nodes have been auto-fixed");
found = false;
return;
}
// System.out.println("Comparing key = '" + new String(key) + "' with '" + otherkey + "':"); // debug
c = objectOrder.compare(key, k);
// System.out.println(c); // debug
if (c == 0) {
found = true;
// System.out.println("DEBUG: search for " + new String(key) + " ended with status=" + ((found) ? "found" : "not-found") + ", node=" + ((thenode == null) ? "NULL" : thenode.toString()) + ", parent=" + ((parentnode == null) ? "NULL" : parentnode.toString()));
return;
} else if (c < 0) {
child = -1;
thisHandle = thenode.getOHHandle(leftchild);
} else {
child = 1;
thisHandle = thenode.getOHHandle(rightchild);
}
visitedNodeKeys.add(k);
}
}
}
// System.out.println("DEBUG: search for " + new String(key) + " ended with status=" + ((found) ? "found" : "not-found") + ", node=" + ((thenode == null) ? "NULL" : thenode.toString()) + ", parent=" + ((parentnode == null) ? "NULL" : parentnode.toString()));
// we reached a node where we must insert the new value
// the parent of this new value can be obtained by getParent()
// all values are set, just return
}
public boolean found() {
return found;
}
public Node getMatcher() {
if (found) return thenode;
else throw new IllegalArgumentException("wrong access of matcher");
}
public Node getParent() {
if (found) return parentnode; else return thenode;
}
public boolean isRoot() {
if (found) throw new IllegalArgumentException("wrong access of isRoot");
else return (child == 0);
}
public boolean isLeft() {
if (found) throw new IllegalArgumentException("wrong access of leftchild");
else return (child == -1);
}
public boolean isRight() {
if (found) throw new IllegalArgumentException("wrong access of leftchild");
else return (child == 1);
}
}
public synchronized boolean isChild(Node childn, Node parentn, int child) {
if (childn == null) throw new IllegalArgumentException("isLeftChild: Node parameter is NULL");
Handle lc = parentn.getOHHandle(child);
if (lc == null) return false;
return (lc.equals(childn.handle()));
}
// Associates the specified value with the specified key in this map
public kelondroRow.Entry put(kelondroRow.Entry newrow) throws IOException {
kelondroRow.Entry result = null;
//writeLock.stay(2000, 1000);
if (newrow.columns() != row().columns()) throw new IllegalArgumentException("put: wrong row length " + newrow.columns() + "; must be " + row().columns());
// first try to find the key element in the database
synchronized(writeSearchObj) {
if (objectCache != null) objectCache.put(newrow.getColBytes(0), newrow);
writeSearchObj.process(newrow.getColBytes(0));
if (writeSearchObj.found()) {
// a node with this key exist. simply overwrite the content and return old content
Node e = writeSearchObj.getMatcher();
result = row().newEntry(e.setValueRow(newrow.bytes()));
commitNode(e);
} else if (writeSearchObj.isRoot()) {
// a node with this key does not exist and there is no node at all
// this therefore creates the root node if an only if there was no root Node yet
if (getHandle(root) != null)
throw new kelondroException(filename, "tried to create root node twice");
// we dont have any Nodes in the file, so start here to create one
Node e = newNode();
e.setValueRow(newrow.bytes());
// write the propetries
e.setOHByte(magic, (byte) 1);
e.setOHByte(balance, (byte) 0);
e.setOHHandle(parent, null);
e.setOHHandle(leftchild, null);
e.setOHHandle(rightchild, null);
// do updates
e.commit(CP_LOW);
setHandle(root, e.handle());
result = null;
} else {
// a node with this key does not exist
// this creates a new node if there is already at least a root node
// to create the new node, it is necessary to assign it to a parent
// it must also be defined weather this new node is a left child of the
// parent or not. It is checked if the parent node already has a child on
// that side, but not if the assigned position is appropriate.
// create new node and assign values
Node parentNode = writeSearchObj.getParent();
Node theNode = newNode();
theNode.setValueRow(newrow.bytes());
theNode.setOHByte(0, (byte) 1); // fresh magic
theNode.setOHByte(1, (byte) 0); // fresh balance
theNode.setOHHandle(parent, parentNode.handle());
theNode.setOHHandle(leftchild, null);
theNode.setOHHandle(rightchild, null);
theNode.commit(CP_LOW);
// check consistency and link new node to parent node
byte parentBalance;
if (writeSearchObj.isLeft()) {
if (parentNode.getOHHandle(leftchild) != null) throw new kelondroException(filename, "tried to create leftchild node twice");
parentNode.setOHHandle(leftchild, theNode.handle());
} else if (writeSearchObj.isRight()) {
if (parentNode.getOHHandle(rightchild) != null) throw new kelondroException(filename, "tried to create rightchild node twice");
parentNode.setOHHandle(rightchild, theNode.handle());
} else {
throw new kelondroException(filename, "neither left nor right child");
}
commitNode(parentNode);
// now update recursively the node balance of the parentNode
// what do we have:
// - new Node, called 'theNode'
// - parent Node
// set balance factor in parent node(s)
boolean increasedHight = true;
String path = "";
byte prevHight;
Handle parentSideHandle;
while (increasedHight) {
// update balance
parentBalance = parentNode.getOHByte(balance); // {magic, balance}
prevHight = parentBalance;
parentSideHandle = parentNode.getOHHandle(leftchild);
if ((parentSideHandle != null) && (parentSideHandle.equals(theNode.handle()))) {
// is left child
parentBalance++;
path = "L" + path;
}
parentSideHandle =parentNode.getOHHandle(rightchild);
if ((parentSideHandle != null) && (parentSideHandle.equals(theNode.handle()))) {
// is right child
parentBalance--;
path = "R" + path;
}
increasedHight = ((java.lang.Math.abs((int) parentBalance) - java.lang.Math.abs((int) prevHight)) > 0);
parentNode.setOHByte(balance, parentBalance);
commitNode(parentNode);
// here we either stop because we had no increased hight,
// or we have a balance greater then 1 or less than -1 and we do rotation
// or we crawl up the tree and change the next balance
if (!(increasedHight)) break; // finished
// check rotation need
if (java.lang.Math.abs((int) parentBalance) > 1) {
// rotate and stop then
//System.out.println("* DB DEBUG: " + path.substring(0,2) + " ROTATION AT NODE " + parentNode.handle().toString() + ": BALANCE=" + parentOHByte[balance]);
if (path.startsWith("LL")) {
LL_RightRotation(parentNode, theNode);
break;
}
if (path.startsWith("RR")) {
RR_LeftRotation(parentNode, theNode);
break;
}
if (path.startsWith("RL")) {
Handle parentHandle = parentNode.handle();
LL_RightRotation(theNode, getNode(theNode.getOHHandle(leftchild), theNode, leftchild));
parentNode = getNode(parentHandle, null, 0); // reload the parent node
RR_LeftRotation(parentNode, getNode(parentNode.getOHHandle(rightchild), parentNode, rightchild));
break;
}
if (path.startsWith("LR")) {
Handle parentHandle = parentNode.handle();
RR_LeftRotation(theNode, getNode(theNode.getOHHandle(rightchild), theNode, rightchild));
parentNode = getNode(parentHandle, null, 0); // reload the parent node
LL_RightRotation(parentNode, getNode(parentNode.getOHHandle(leftchild), parentNode, leftchild));
break;
}
break;
} else {
// crawl up the tree
if (parentNode.getOHHandle(parent) == null) {
// root reached: stop
break;
} else {
theNode = parentNode;
parentNode = getNode(parentNode.getOHHandle(parent), null, 0);
}
}
}
result = null;; // that means: no previous stored value present
}
}
//writeLock.release();
return result;
}
private void assignChild(Node parentNode, Node childNode, int childType) throws IOException {
parentNode.setOHHandle(childType, childNode.handle());
childNode.setOHHandle(parent, parentNode.handle());
commitNode(parentNode);
commitNode(childNode);
}
private void replace(Node oldNode, Node oldNodeParent, Node newNode) throws IOException {
// this routine looks where the oldNode is connected to, and replaces
// the anchor's link to the oldNode by the newNode-link
// the new link gets the anchor as parent link assigned
// the oldNode will not be updated, so this must be done outside this routine
// distinguish case where the oldNode is the root node
if (oldNodeParent == null) {
// this is the root, update root
setHandle(root, newNode.handle());
// update new Node
newNode.setOHHandle(parent, null);
commitNode(newNode);
} else {
// not the root, find parent
// ok, we have the parent, but for updating the child link we must know
// if the oldNode was left or right child
Handle parentSideHandle = oldNodeParent.getOHHandle(leftchild);
if ((parentSideHandle != null) && (parentSideHandle.equals(oldNode.handle()))) {
// update left node from parent
oldNodeParent.setOHHandle(leftchild, newNode.handle());
}
parentSideHandle = oldNodeParent.getOHHandle(rightchild);
if ((parentSideHandle != null) && (parentSideHandle.equals(oldNode.handle()))) {
// update right node from parent
oldNodeParent.setOHHandle(rightchild, newNode.handle());
}
commitNode(oldNodeParent);
// update new Node
newNode.setOHHandle(parent, oldNodeParent.handle());
commitNode(newNode);
}
// finished. remember that we did not set the links to the oldNode
// we have also not set the children of the newNode.
// this must be done somewhere outside this function.
// if the oldNode is not needed any more, it can be disposed (check childs first).
}
private static byte max0(byte b) {
if (b > 0) return b; else return 0;
}
private static byte min0(byte b) {
if (b < 0) return b; else return 0;
}
private void LL_RightRotation(Node parentNode, Node childNode) throws IOException {
// replace the parent node; the parent is afterwards unlinked
Handle p2Handle = parentNode.getOHHandle(parent);
Node p2Node = (p2Handle == null) ? null : getNode(p2Handle, null, 0);
replace(parentNode, p2Node, childNode);
// set the left son of the parent to the right son of the childNode
Handle childOfChild = childNode.getOHHandle(rightchild);
if (childOfChild == null) {
parentNode.setOHHandle(leftchild, null);
} else {
assignChild(parentNode, getNode(childOfChild, childNode, rightchild), leftchild);
}
// link the old parent node as the right child of childNode
assignChild(childNode, parentNode, rightchild);
// - newBal(parent) = oldBal(parent) - 1 - max(oldBal(leftChild), 0)
// - newBal(leftChild) = oldBal(leftChild) - 1 + min(newBal(parent), 0)
byte parentBalance = parentNode.getOHByte(balance);
byte childBalance = childNode.getOHByte(balance);
byte oldBalParent = parentBalance;
byte oldBalChild = childBalance;
parentBalance = (byte) (oldBalParent - 1 - max0(oldBalChild));
childBalance = (byte) (oldBalChild - 1 + min0(parentBalance));
parentNode.setOHByte(balance, parentBalance);
childNode.setOHByte(balance, childBalance);
commitNode(parentNode);
commitNode(childNode);
}
private void RR_LeftRotation(Node parentNode, Node childNode) throws IOException {
// replace the parent node; the parent is afterwards unlinked
Handle p2Handle = parentNode.getOHHandle(parent);
Node p2Node = (p2Handle == null) ? null : getNode(p2Handle, null, 0);
replace(parentNode, p2Node, childNode);
// set the left son of the parent to the right son of the childNode
Handle childOfChild = childNode.getOHHandle(leftchild);
if (childOfChild == null) {
parentNode.setOHHandle(rightchild, null);
} else {
assignChild(parentNode, getNode(childOfChild, childNode, leftchild), rightchild);
}
// link the old parent node as the left child of childNode
assignChild(childNode, parentNode, leftchild);
// - newBal(parent) = oldBal(parent) + 1 - min(oldBal(rightChild), 0)
// - newBal(rightChild) = oldBal(rightChild) + 1 + max(newBal(parent), 0)
byte parentBalance = parentNode.getOHByte(balance);
byte childBalance = childNode.getOHByte(balance);
byte oldBalParent = parentBalance;
byte oldBalChild = childBalance;
parentBalance = (byte) (oldBalParent + 1 - min0(oldBalChild));
childBalance = (byte) (oldBalChild + 1 + max0(parentBalance));
parentNode.setOHByte(balance, parentBalance);
childNode.setOHByte(balance, childBalance);
commitNode(parentNode);
commitNode(childNode);
}
// Associates the specified value with the specified key in this map
public byte[] put(byte[] key, byte[] value) throws IOException {
kelondroRow.Entry row = row().newEntry(new byte[][]{key, value});
kelondroRow.Entry ret = put(row);
if (ret == null) return null; else return ret.getColBytes(0);
}
// Removes the mapping for this key from this map if present (optional operation).
public kelondroRow.Entry remove(byte[] key) throws IOException {
synchronized(writeSearchObj) {
if (objectCache != null) objectCache.remove(key);
writeSearchObj.process(key);
if (writeSearchObj.found()) {
Node result = writeSearchObj.getMatcher();
kelondroRow.Entry values = row().newEntry(result.getValueRow());
remove(result, writeSearchObj.getParent());
return values;
} else {
return null;
}
}
}
public synchronized void removeAll() throws IOException {
while (size() > 0) remove(lastNode(), null);
}
private void remove(Node node, Node parentOfNode) throws IOException {
// there are three cases when removing a node
// - the node is a leaf - it can be removed easily
// - the node has one child - the child replaces the node
// - the node has two childs - it can be replaced either
// by the greatest node of the left child or the smallest
// node of the right child
Node childnode;
if ((node.getOHHandle(leftchild) == null) && (node.getOHHandle(rightchild) == null)) {
// easy case: the node is a leaf
if (parentOfNode == null) {
// this is the root!
setHandle(root, null);
} else {
Handle h = parentOfNode.getOHHandle(leftchild);
if ((h != null) && (h.equals(node.handle()))) parentOfNode.setOHHandle(leftchild, null);
h = parentOfNode.getOHHandle(rightchild);
if ((h != null) && (h.equals(node.handle()))) parentOfNode.setOHHandle(rightchild, null);
commitNode(parentOfNode);
}
} else if ((node.getOHHandle(leftchild) != null) && (node.getOHHandle(rightchild) == null)) {
replace(node, parentOfNode, getNode(node.getOHHandle(leftchild), node, leftchild));
} else if ((node.getOHHandle(leftchild) == null) && (node.getOHHandle(rightchild) != null)) {
replace(node, parentOfNode, getNode(node.getOHHandle(rightchild), node, rightchild));
} else {
// difficult case: node has two children
Node repl = lastNode(getNode(node.getOHHandle(leftchild), node, leftchild));
//System.out.println("last node is " + repl.toString());
// we remove that replacement node and put it where the node was
// this seems to be recursive, but is not since the replacement
// node cannot have two children (it would not have been the smallest or greatest)
Node n;
Handle h;
// remove leaf
if ((repl.getOHHandle(leftchild) == null) && (repl.getOHHandle(rightchild) == null)) {
// the replacement cannot be the root, so simply remove from parent node
n = getNode(repl.getOHHandle(parent), null, 0); // parent node of replacement node
h = n.getOHHandle(leftchild);
if ((h != null) && (h.equals(repl.handle()))) n.setOHHandle(leftchild, null);
h = n.getOHHandle(rightchild);
if ((h != null) && (h.equals(repl.handle()))) n.setOHHandle(rightchild, null);
commitNode(n);
} else if ((repl.getOHHandle(leftchild) != null) && (repl.getOHHandle(rightchild) == null)) {
try {
childnode = getNode(repl.getOHHandle(leftchild), repl, leftchild);
replace(repl, getNode(repl.getOHHandle(parent), null, 0), childnode);
} catch (IllegalArgumentException e) {
// now treat the situation as if that link had been null before
n = getNode(repl.getOHHandle(parent), null, 0); // parent node of replacement node
h = n.getOHHandle(leftchild);
if ((h != null) && (h.equals(repl.handle()))) n.setOHHandle(leftchild, null);
h = n.getOHHandle(rightchild);
if ((h != null) && (h.equals(repl.handle()))) n.setOHHandle(rightchild, null);
commitNode(n);
}
} else if ((repl.getOHHandle(leftchild) == null) && (repl.getOHHandle(rightchild) != null)) {
try {
childnode = getNode(repl.getOHHandle(rightchild), repl, rightchild);
replace(repl, getNode(repl.getOHHandle(parent), null, 0), childnode);
} catch (IllegalArgumentException e) {
// now treat the situation as if that link had been null before
n = getNode(repl.getOHHandle(parent), null, 0); // parent node of replacement node
h = n.getOHHandle(leftchild);
if ((h != null) && (h.equals(repl.handle()))) n.setOHHandle(leftchild, null);
h = n.getOHHandle(rightchild);
if ((h != null) && (h.equals(repl.handle()))) n.setOHHandle(rightchild, null);
commitNode(n);
}
}
//System.out.println("node before reload is " + node.toString());
node = getNode(node.handle(), null, 0); // reload the node, it is possible that it has been changed
//System.out.println("node after reload is " + node.toString());
// now plant in the replha node
byte b = node.getOHByte(balance); // save balance of disappearing node
Handle parentHandle = node.getOHHandle(parent);
Handle leftchildHandle = node.getOHHandle(leftchild);
Handle rightchildHandle = node.getOHHandle(rightchild);
replace(node, parentOfNode, repl);
repl.setOHByte(balance, b); // restore balance
repl.setOHHandle(parent, parentHandle); // restore handles
repl.setOHHandle(leftchild, leftchildHandle);
repl.setOHHandle(rightchild, rightchildHandle);
commitNode(repl);
// last thing to do: change uplinks of children to this new node
if (leftchildHandle != null) {
n = getNode(leftchildHandle, node, leftchild);
n.setOHHandle(parent, repl.handle());
commitNode(n);
}
if (rightchildHandle != null) {
n = getNode(rightchildHandle, node, rightchild);
n.setOHHandle(parent, repl.handle());
commitNode(n);
}
}
// move node to recycling queue
deleteNode(node.handle());
}
private Node firstNode() throws IOException {
Handle h = getHandle(root);
if (h == null) return null;
return firstNode(getNode(h, null, 0));
}
private Node firstNode(Node node) throws IOException {
if (node == null) throw new IllegalArgumentException("firstNode: node=null");
Handle h = node.getOHHandle(leftchild);
HashSet visitedNodeKeys = new HashSet(); // to detect loops
String nodeKey;
while (h != null) {
try {
node = getNode(h, node, leftchild);
nodeKey = new String(node.getKey());
if (visitedNodeKeys.contains(nodeKey)) throw new kelondroException(this.filename, "firstNode: database contains loops: '" + nodeKey + "' appears twice.");
visitedNodeKeys.add(nodeKey);
} catch (IllegalArgumentException e) {
// return what we have
return node;
}
h = node.getOHHandle(leftchild);
}
return node;
}
private Node lastNode() throws IOException {
Handle h = getHandle(root);
if (h == null) return null;
return lastNode(getNode(h, null, 0));
}
private Node lastNode(Node node) throws IOException {
if (node == null) throw new IllegalArgumentException("lastNode: node=null");
Handle h = node.getOHHandle(rightchild);
HashSet visitedNodeKeys = new HashSet(); // to detect loops
String nodeKey;
while (h != null) {
try {
node = getNode(h, node, rightchild);
nodeKey = new String(node.getKey());
if (visitedNodeKeys.contains(nodeKey)) throw new kelondroException(this.filename, "lastNode: database contains loops: '" + nodeKey + "' appears twice.");
visitedNodeKeys.add(nodeKey);
} catch (IllegalArgumentException e) {
// return what we have
return node;
}
h = node.getOHHandle(rightchild);
}
return node;
}
private class nodeIterator implements Iterator {
// we implement an iteration! (not a recursive function as the structure would suggest...)
// the iterator iterates Node objects
Node nextNode = null;
boolean up, rot;
LinkedList nodeStack;
int count;
public nodeIterator(boolean up, boolean rotating) throws IOException {
this.count = 0;
this.up = up;
this.rot = rotating;
// initialize iterator
init((up) ? firstNode() : lastNode());
}
public nodeIterator(boolean up, boolean rotating, byte[] firstKey, boolean including) throws IOException {
this.count = 0;
this.up = up;
this.rot = rotating;
Search search = new Search();
search.process(firstKey);
if (search.found()) {
init(search.getMatcher());
} else {
Node nn = search.getParent();
if (nn == null) {
this.nextNode = null;
} else {
// the node nn may be greater or smaller than the firstKey
// depending on the ordering direction,
// we must find the next smaller or greater node
// this is corrected in the initializer of nodeIterator
init(nn);
}
}
// correct nextNode upon start
// this happens, if the start node was not proper, or could not be found
while ((nextNode != null) && (nextNode.getKey() != null)) {
int c = objectOrder.compare(firstKey, nextNode.getKey());
if (c == 0) {
if (including) {
break; // correct + finished
} else {
if (hasNext()) next(); else nextNode = null;
break; // corrected + finished
}
} else if (c < 0) {
if (up) {
break; // correct + finished
} else {
// firstKey < nextNode.getKey(): correct once
if (hasNext()) next(); else nextNode = null;
}
} else if (c > 0) {
if (up) {
// firstKey > nextNode.getKey(): correct once
if (hasNext()) next(); else nextNode = null;
} else {
break; // correct + finished
}
}
}
}
private void init(Node start) throws IOException {
this.nextNode = start;
// fill node stack for start node
nodeStack = new LinkedList();
Handle searchHandle = getHandle(root);
if (searchHandle == null) {nextNode = null; return;}
Node searchNode = getNode(searchHandle, null, 0);
byte[] startKey = start.getKey();
int c, ct;
while ((c = objectOrder.compare(startKey, searchNode.getKey())) != 0) {
// the current 'thisNode' is not the start node, put it on the stack
ct = (c < 0) ? leftchild : rightchild;
nodeStack.addLast(new Object[]{searchNode, new Integer(ct)});
// go to next node
searchHandle = searchNode.getOHHandle(ct);
if (searchHandle == null) throw new kelondroException(filename, "nodeIterator.init: start node does not exist (handle null)");
searchNode = getNode(searchHandle, searchNode, ct);
if (searchNode == null) throw new kelondroException(filename, "nodeIterator.init: start node does not exist (node null)");
}
// now every parent node to the start node is on the stack
}
public void finalize() {
nextNode = null;
nodeStack = null;
}
public boolean hasNext() {
return (rot && (size() > 0)) || (nextNode != null);
}
public Object next() {
count++;
if ((rot) && (nextNode == null)) try {
init((up) ? firstNode() : lastNode());
} catch (IOException e) {
throw new kelondroException(filename, "io-error while rot");
}
if (nextNode == null) throw new kelondroException(filename, "nodeIterator.next: no more entries available");
if ((count > size()) && (!(rot))) throw new kelondroException(filename, "nodeIterator.next: internal loopback; database corrupted");
Object ret = nextNode;
// middle-case
try {
int childtype = (up) ? rightchild : leftchild;
Handle childHandle = nextNode.getOHHandle(childtype);
if (childHandle != null) {
//System.out.println("go to other leg, stack size=" + nodeStack.size());
// we have walked one leg of the tree; now go to the other one: step down to next child
HashSet visitedNodeHandles = new HashSet(); // to detect loops
nodeStack.addLast(new Object[]{nextNode, new Integer(childtype)});
nextNode = getNode(childHandle, nextNode, childtype);
childtype = (up) ? leftchild : rightchild;
while ((childHandle = nextNode.getOHHandle(childtype)) != null) {
if (visitedNodeHandles.contains(childHandle)) {
// try to repair the nextNode
nextNode.setOHHandle(childtype, null);
nextNode.commit(CP_NONE);
logWarning("nodeIterator.next: internal loopback; fixed loop and try to go on");
break;
}
visitedNodeHandles.add(childHandle);
try {
nodeStack.addLast(new Object[]{nextNode, new Integer(childtype)});
nextNode = getNode(childHandle, nextNode, childtype);
} catch (IllegalArgumentException e) {
// return what we have
nodeStack.removeLast();
return ret;
}
}
// thats it: we are at a place where we can't go further
// nextNode is correct
} else {
//System.out.println("go up");
// we have walked along both legs of the child-trees.
// Now step up.
if (nodeStack.size() == 0) {
nextNode = null;
} else {
Object[] stacktop;
Node parentNode = null;
int parentpointer = (up) ? rightchild : leftchild;
while ((nodeStack.size() != 0) && (parentpointer == ((up) ? rightchild : leftchild))) {
//System.out.println("step up");
// go on, walk up further
stacktop = (Object[]) nodeStack.removeLast(); // top of stack: Node/parentpointer pair
parentNode = (Node) stacktop[0];
parentpointer = ((Integer) stacktop[1]).intValue();
}
if ((nodeStack.size() == 0) && (parentpointer == ((up) ? rightchild : leftchild))) {
nextNode = null;
} else {
nextNode = parentNode;
}
}
}
} catch (IOException e) {
nextNode = null;
}
return ret;
}
public void remove() {
throw new java.lang.UnsupportedOperationException("kelondroTree: remove in kelondro node iterator not yet supported");
}
}
public TreeMap rowMap(boolean up, boolean rotating, byte[] firstKey, boolean including, int count) throws IOException {
// returns an ordered map of keys/row relations; key objects are of type String, value objects are of type byte[][]
kelondroOrder setOrder = (kelondroOrder) objectOrder.clone();
setOrder.direction(up);
setOrder.rotate(firstKey);
TreeMap rows = new TreeMap(setOrder);
Node n;
String key;
synchronized (this) {
Iterator i = (firstKey == null) ? new nodeIterator(up, rotating) : new nodeIterator(up, rotating, firstKey, including);
while ((rows.size() < count) && (i.hasNext())) {
n = (Node) i.next();
if (n == null) return rows;
key = new String(n.getKey());
if (rows.put(key, row().newEntry(n.getValueRow())) != null) return rows; // protection against loops
}
}
return rows;
}
public TreeSet keySet(boolean up, boolean rotating, byte[] firstKey, boolean including, int count) throws IOException {
// returns an ordered set of keys; objects are of type String
kelondroOrder setOrder = (kelondroOrder) objectOrder.clone();
setOrder.direction(up);
setOrder.rotate(firstKey);
TreeSet set = new TreeSet(setOrder);
Node n;
synchronized (this) {
Iterator i = (firstKey == null) ? new nodeIterator(up, rotating) : new nodeIterator(up, rotating, firstKey, including);
while ((set.size() < count) && (i.hasNext())) {
n = (Node) i.next();
if ((n != null) && (n.getKey() != null)) set.add(new String(n.getKey()));
}
}
return set;
}
public Iterator rows(boolean up, boolean rotating, byte[] firstKey) throws IOException {
// iterates the rows of the Nodes
// enumerated objects are of type byte[][]
// iterates the elements in a sorted way.
// if iteration should start at smallest element, set firstKey to null
return new rowIterator(up, rotating, firstKey, this.size());
}
public class rowIterator implements Iterator {
int chunkSize;
byte[] start;
boolean inc, rot;
long count;
byte[] lastKey;
TreeMap rowBuffer;
Iterator bufferIterator;
public rowIterator(boolean up, boolean rotating, byte[] firstKey, long guessedCountLimit) throws IOException {
start = firstKey;
inc = up;
rot = rotating;
count = 0;
lastKey = null;
//System.out.println("*** rowIterator: " + filename + ": readAheadChunkSize = " + readAheadChunkSize + ", lastIteratorCount = " + lastIteratorCount);
readAheadChunkSize = Math.min(1000, 3 + (int) ((3 * readAheadChunkSize + lastIteratorCount) / 4));
chunkSize = (int) Math.min(readAheadChunkSize / 3, guessedCountLimit);
rowBuffer = rowMap(inc, rot, start, true, chunkSize);
bufferIterator = rowBuffer.entrySet().iterator();
lastIteratorCount = 0;
}
public boolean hasNext() {
return ((bufferIterator != null) && (bufferIterator.hasNext()) && ((rot) || (count < size())));
}
public Object next() {
if (!(bufferIterator.hasNext())) return null;
Map.Entry entry = (Map.Entry) bufferIterator.next();
lastKey = ((String) entry.getKey()).getBytes();
// check if this was the last entry in the rowBuffer
if (!(bufferIterator.hasNext())) {
// assign next buffer chunk
try {
lastKey[lastKey.length - 1]++;
rowBuffer = rowMap(inc, rot, lastKey, false, chunkSize);
bufferIterator = rowBuffer.entrySet().iterator();
} catch (IOException e) {
rowBuffer = null;
bufferIterator = null;
}
}
// return the row
count++;
lastIteratorCount++;
return entry.getValue();
}
public void remove() {
if (lastKey != null) try {
kelondroTree.this.remove(lastKey);
} catch (IOException e) {
// do nothing
}
}
}
public int imp(File file, String separator) throws IOException {
// imports a value-separated file, returns number of records that have been read
RandomAccessFile f = new RandomAccessFile(file,"r");
String s;
StringTokenizer st;
int recs = 0;
kelondroRow.Entry buffer = row().newEntry();
int c;
int line = 0;
while ((s = f.readLine()) != null) {
s = s.trim();
line++;
if ((s.length() > 0) && (!(s.startsWith("#")))) {
st = new StringTokenizer(s, separator);
// buffer the entry
c = 0;
while ((c < row().columns()) && (st.hasMoreTokens())) {
buffer.setCol(c++, st.nextToken().trim().getBytes());
}
if ((st.hasMoreTokens()) || (c != row().columns())) {
System.err.println("inapropriate number of entries in line " + line);
} else {
put(buffer);
recs++;
}
}
}
return recs;
}
public synchronized int height() {
try {
Handle h = getHandle(root);
if (h == null) return 0;
return height(getNode(h, null, 0));
} catch (IOException e) {
return 0;
}
}
private int height(Node node) throws IOException {
if (node == null) return 0;
Handle h = node.getOHHandle(leftchild);
int hl = (h == null) ? 0 : height(getNode(h, node, leftchild));
h = node.getOHHandle(rightchild);
int hr = (h == null) ? 0 : height(getNode(h, node, rightchild));
if (hl > hr) return hl + 1; else return hr + 1;
}
public String np(Object n) {
if (n == null) return "NULL"; else return n.toString();
}
public void print() throws IOException {
super.print(false);
int height = height();
System.out.println("HEIGHT = " + height);
Vector thisline = new Vector();
thisline.add(getHandle(root));
Vector nextline;
Handle handle;
Node node;
int linelength, width = (1 << (height - 1)) * (row().width(0) + 1);
String key;
for (int h = 1; h < height; h++) {
linelength = width / (thisline.size() * 2);
nextline = new Vector();
for (int i = 0; i < thisline.size(); i++) {
handle = (Handle) thisline.elementAt(i);
if (handle == null) {
node = null;
key = "[..]";
} else {
node = getNode(handle, null, 0);
if (node == null) key = "NULL"; else key = new String(node.getKey());
}
System.out.print(key);
for (int j = 0; j < (linelength - key.length()); j++) System.out.print("-");
System.out.print("+");
for (int j = 0; j < (linelength - 1); j++) System.out.print(" ");
if (node == null) {
nextline.add(null);
nextline.add(null);
} else {
nextline.add(node.getOHHandle(leftchild));
nextline.add(node.getOHHandle(rightchild));
}
}
System.out.println();
for (int i = 0; i < thisline.size(); i++) {
System.out.print("|");
for (int j = 0; j < (linelength - 1); j++) System.out.print(" ");
System.out.print("|");
for (int j = 0; j < (linelength - 1); j++) System.out.print(" ");
}
System.out.println();
thisline = nextline;
nextline = null;
}
// now print last line
if ((thisline != null) && (width >= 0)) {
linelength = width / thisline.size();
for (int i = 0; i < thisline.size(); i++) {
handle = (Handle) thisline.elementAt(i);
if (handle == null) {
node = null;
key = "NULL";
} else {
node = getNode(handle, null, 0);
if (node == null) key = "NULL"; else key = new String(node.getKey());
}
System.out.print(key);
for (int j = 0; j < (linelength - key.length()); j++) System.out.print(" ");
}
}
System.out.println();
}
public static void cmd(String[] args) {
System.out.print("kelondroTree ");
for (int i = 0; i < args.length; i++) System.out.print(args[i] + " ");
System.out.println("");
byte[] ret = null;
try {
if ((args.length > 4) || (args.length < 1)) {
System.err.println("usage: kelondroTree -c|-u|-v|-g|-d|-i|-s [file]|[key [value]] <db-file>");
System.err.println("( create, update, view, get, delete, imp, shell)");
System.exit(0);
} else if (args.length == 1) {
if (args[0].equals("-t")) {
// test script
File testFile = new File("test.db");
while (testFile.exists()) testFile.delete();
kelondroTree fm = new kelondroTree(testFile, 0x100000, 0, 10, 4, 4, true);
byte[] dummy = "".getBytes();
fm.put("abc0".getBytes(), dummy); fm.put("bcd0".getBytes(), dummy);
fm.put("def0".getBytes(), dummy); fm.put("bab0".getBytes(), dummy);
fm.put("abc1".getBytes(), dummy); fm.put("bcd1".getBytes(), dummy);
fm.put("def1".getBytes(), dummy); fm.put("bab1".getBytes(), dummy);
fm.put("abc2".getBytes(), dummy); fm.put("bcd2".getBytes(), dummy);
fm.put("def2".getBytes(), dummy); fm.put("bab2".getBytes(), dummy);
fm.put("abc3".getBytes(), dummy); fm.put("bcd3".getBytes(), dummy);
fm.put("def3".getBytes(), dummy); fm.put("bab3".getBytes(), dummy);
fm.print();
fm.remove("def1".getBytes()); fm.remove("bab1".getBytes());
fm.remove("abc2".getBytes()); fm.remove("bcd2".getBytes());
fm.remove("def2".getBytes()); fm.remove("bab2".getBytes());
fm.put("def1".getBytes(), dummy); fm.put("bab1".getBytes(), dummy);
fm.put("abc2".getBytes(), dummy); fm.put("bcd2".getBytes(), dummy);
fm.put("def2".getBytes(), dummy); fm.put("bab2".getBytes(), dummy);
fm.print();
fm.close();
ret = null;
}
} else if (args.length == 2) {
kelondroTree fm = new kelondroTree(new File(args[1]), 0x100000, 0, 10);
if (args[0].equals("-v")) {
fm.print();
ret = null;
}
fm.close();
} else if (args.length == 3) {
if (args[0].equals("-d")) {
kelondroTree fm = new kelondroTree(new File(args[1]), 0x100000, 0, 10);
fm.remove(args[2].getBytes());
fm.close();
} else if (args[0].equals("-i")) {
kelondroTree fm = new kelondroTree(new File(args[1]), 0x100000, 0, 10);
int i = fm.imp(new File(args[1]),";");
fm.close();
ret = (i + " records imported").getBytes();
} else if (args[0].equals("-s")) {
String db = args[2];
BufferedReader f = null;
try {
f = new BufferedReader(new FileReader(args[1]));
String m;
while (true) {
m = f.readLine();
if (m == null) break;
if ((m.length() > 1) && (!m.startsWith("#"))) {
m = m + " " + db;
cmd(line2args(m));
}
}
ret = null;
} finally {
if (f != null) try {f.close();}catch(Exception e){}
}
} else if (args[0].equals("-g")) {
kelondroTree fm = new kelondroTree(new File(args[1]), 0x100000, 0, 10);
kelondroRow.Entry ret2 = fm.get(args[2].getBytes());
ret = ((ret2 == null) ? null : ret2.getColBytes(1));
fm.close();
} else if (args[0].equals("-n")) {
kelondroTree fm = new kelondroTree(new File(args[1]), 0x100000, 0, 10);
//byte[][] keys = fm.getSequentialKeys(args[2].getBytes(), 500, true);
Iterator rowIt = fm.rows(true, false, (args[2].length() == 0) ? null : args[2].getBytes());
Vector v = new Vector();
while (rowIt.hasNext()) v.add(new String(((byte[][]) rowIt.next())[0]));
ret = v.toString().getBytes();
fm.close();
}
} else if (args.length == 4) {
if (args[0].equals("-c")) {
// create <keylen> <valuelen> <filename>
File f = new File(args[3]);
if (f.exists()) f.delete();
kelondroRow lens = new kelondroRow(new int[]{Integer.parseInt(args[1]), Integer.parseInt(args[2])});
kelondroTree fm = new kelondroTree(f, 0x100000, 0, 10, lens, true);
fm.close();
} else if (args[0].equals("-u")) {
kelondroTree fm = new kelondroTree(new File(args[3]), 0x100000, 0, 10);
ret = fm.put(args[1].getBytes(), args[2].getBytes());
fm.close();
}
}
if (ret == null)
System.out.println("NULL");
else
System.out.println(new String(ret));
} catch (Exception e) {
e.printStackTrace();
}
}
public kelondroOrder order() {
return this.objectOrder;
}
public static void main(String[] args) {
//cmd(args);
//iterationtest();
//bigtest(Integer.parseInt(args[0]));
//randomtest(Integer.parseInt(args[0]));
smalltest();
}
public static String[] permutations(int letters) {
String p = "";
for (int i = 0; i < letters; i++) p = p + ((char) (((int)'A') + i));
return permutations(p);
}
public static String[] permutations(String source) {
if (source.length() == 0) return new String[0];
if (source.length() == 1) return new String[]{source};
char c = source.charAt(0);
String[] recres = permutations(source.substring(1));
String[] result = new String[source.length() * recres.length];
for (int perm = 0; perm < recres.length; perm++) {
result[perm * source.length()] = c + recres[perm];
for (int pos = 1; pos < source.length() - 1; pos++) {
result[perm * source.length() + pos] = recres[perm].substring(0, pos) + c + recres[perm].substring(pos);
}
result[perm * source.length() + source.length() - 1] = recres[perm] + c;
}
return result;
}
public static byte[] testWord(char c) {
return new byte[]{(byte) c, 32, 32, 32};
}
public static void randomtest(int elements) {
System.out.println("random " + elements + ":");
String s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".substring(0, elements);
String t, d;
char c;
kelondroTree tt = null;
File testFile = new File("test.db");
byte[] b;
try {
int steps = 0;
while (true) {
if (testFile.exists()) testFile.delete();
tt = new kelondroTree(testFile, 200, 0, 10, 4 ,4, true);
steps = 10 + ((int) System.currentTimeMillis() % 7) * (((int) System.currentTimeMillis() + 17) % 11);
t = s;
d = "";
System.out.println("NEW SESSION");
for (int i = 0; i < steps; i++) {
if ((d.length() < 3) || ((t.length() > 0) && (((int) System.currentTimeMillis() % 7) < 2))) {
// add one
c = t.charAt((int) (System.currentTimeMillis() % t.length()));
b = testWord(c);
tt.put(b, b);
d = d + c;
t = t.substring(0, t.indexOf(c)) + t.substring(t.indexOf(c) + 1);
System.out.println("added " + new String(b));
} else {
// delete one
c = d.charAt((int) (System.currentTimeMillis() % d.length()));
b = testWord(c);
tt.remove(b);
d = d.substring(0, d.indexOf(c)) + d.substring(d.indexOf(c) + 1);
t = t + c;
System.out.println("removed " + new String(b));
}
//tt.printCache();
//tt.print();
if (countElements(tt) != tt.size()) {
System.out.println("wrong size for this table:");
tt.print();
}
// check all words within
for (int j = 0; j < d.length(); j++) {
if (tt.get(testWord(d.charAt(j))) == null) {
System.out.println("missing entry " + d.charAt(j) + " in this table:");
tt.print();
}
}
// check all words outside
for (int j = 0; j < t.length(); j++) {
if (tt.get(testWord(t.charAt(j))) != null) {
System.out.println("superfluous entry " + t.charAt(j) + " in this table:");
tt.print();
}
}
if (tt.get(testWord('z')) != null) {
System.out.println("superfluous entry z in this table:");
tt.print();
}
}
//tt.print();
tt.close();
}
} catch (Exception e) {
e.printStackTrace();
try {tt.print();} catch (IOException ee) {}
System.out.println("TERMINATED");
}
}
public static void smalltest() {
File f = new File("test.db");
if (f.exists()) f.delete();
try {
kelondroTree tt = new kelondroTree(f, 1000, 0, 10, 4, 4, true);
byte[] b;
b = testWord('B'); tt.put(b, b); //tt.print();
b = testWord('C'); tt.put(b, b); //tt.print();
b = testWord('D'); tt.put(b, b); //tt.print();
b = testWord('A'); tt.put(b, b); //tt.print();
b = testWord('D'); tt.remove(b); //tt.print();
b = testWord('B'); tt.remove(b); //tt.print();
b = testWord('B'); tt.put(b, b); //tt.print();
b = testWord('D'); tt.put(b, b);
b = testWord('E'); tt.put(b, b);
b = testWord('F'); tt.put(b, b);
b = testWord('G'); tt.put(b, b);
b = testWord('H'); tt.put(b, b);
b = testWord('I'); tt.put(b, b);
b = testWord('J'); tt.put(b, b);
b = testWord('K'); tt.put(b, b);
b = testWord('L'); tt.put(b, b);
int c = countElements(tt);
System.out.println("elements: " + c);
Iterator i = tt.rows(true, true, testWord('G'));
for (int j = 0; j < c; j++) {
System.out.println("Row " + j + ": " + new String(((kelondroRow.Entry) i.next()).getColBytes(0)));
}
System.out.println("TERMINATED");
} catch (IOException e) {
e.printStackTrace();
}
}
/*
public static void iterationtest() {
File f = new File("test.db");
if (f.exists()) f.delete();
try {
kelondroTree tt = new kelondroTree(f, 0, 0, 10, 4, 4, true);
byte[] b;
for (int i = 0; i < 100; i++) {
b = ("T" + i).getBytes(); tt.put(b, b);
}
Iterator i = tt.keys(true, false, null);
while (i.hasNext()) System.out.print((String) i.next() + ", ");
System.out.println();
i = tt.keys(true, false, "T80".getBytes());
while (i.hasNext()) System.out.print((String) i.next() + ", ");
System.out.println();
i = tt.keys(true, true, "T80".getBytes());
for (int j = 0; j < 40; j++) System.out.print((String) i.next() + ", ");
System.out.println();
i = tt.keys(false, true, "T20".getBytes());
for (int j = 0; j < 40; j++) System.out.print((String) i.next() + ", ");
System.out.println();
tt.close();
} catch (IOException e) {
e.printStackTrace();
}
}
*/
public static kelondroTree testTree(File f, String testentities) throws IOException {
if (f.exists()) f.delete();
kelondroTree tt = new kelondroTree(f, 0, 0, 10, 4, 4, true);
byte[] b;
for (int i = 0; i < testentities.length(); i++) {
b = testWord(testentities.charAt(i));
tt.put(b, b);
}
return tt;
}
public static void bigtest(int elements) {
System.out.println("starting big test with " + elements + " elements:");
long start = System.currentTimeMillis();
String[] s = permutations(elements);
kelondroTree tt;
File testFile = new File("test.db");
try {
for (int i = 0; i < s.length; i++) {
System.out.println("*** probing tree " + i + " for permutation " + s[i]);
// generate tree and delete elements
tt = testTree(testFile, s[i]);
//tt.print();
if (countElements(tt) != tt.size()) {
System.out.println("wrong size for " + s[i]);
tt.print();
}
tt.close();
for (int j = 0; j < s.length; j++) {
tt = testTree(testFile, s[i]);
//tt.print();
// delete by permutation j
for (int elt = 0; elt < s[j].length(); elt++) {
tt.remove(testWord(s[j].charAt(elt)));
//tt.print();
if (countElements(tt) != tt.size()) {
System.out.println("ERROR! wrong size for probe tree " + s[i] + "; probe delete " + s[j] + "; position " + elt);
tt.print();
}
}
// add another one
//tt.print();
/*
b = testWord('0'); tt.put(b, b);
b = testWord('z'); tt.put(b, b);
b = testWord('G'); tt.put(b, b);
b = testWord('t'); tt.put(b, b);
if (countElements(tt) != tt.size()) {
System.out.println("ERROR! wrong size for probe tree " + s[i] + "; probe delete " + s[j] + "; final add");
tt.print();
}
tt.print();
*/
// close this
tt.close();
}
}
System.out.println("FINISHED test after " + ((System.currentTimeMillis() - start) / 1000) + " seconds.");
} catch (Exception e) {
e.printStackTrace();
System.out.println("TERMINATED");
}
}
public static int countElements(kelondroTree t) {
int count = 0;
try {
Iterator iter = t.rows(true, false, null);
kelondroRow.Entry row;
while (iter.hasNext()) {
count++;
row = (kelondroRow.Entry) iter.next();
if (row == null) System.out.println("ERROR! null element found");
// else System.out.println("counted element: " + new
// String(n.getKey()));
}
} catch (IOException e) {
}
return count;
}
}