mirror of
https://github.com/yacy/yacy_search_server.git
synced 2024-09-21 00:00:13 +02:00
8444357291
without an order by the primary key. The result is a very fast enumeration of the Eco table data structure. Other table data types are not affected. The new enumerator is used for the url export function that can be accessed from the online interface (Index Administration -> URL References -> Export). This export should now be much faster, if all url database files are from type Eco The new enumeration is also used at other functions in YaCy, i.e. the initialization of the crawl balancer and the initialization of YaCy News. git-svn-id: https://svn.berlios.de/svnroot/repos/yacy/trunk@5647 6c8d7289-2bf4-0310-a012-ef5d649a1542
1637 lines
75 KiB
Java
1637 lines
75 KiB
Java
// kelondroTree.java
|
|
// -----------------------
|
|
// part of The Kelondro Database
|
|
// (C) by Michael Peter Christen; mc@yacy.net
|
|
// 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
|
|
|
|
/*
|
|
This class extends the kelondroRecords and adds a tree structure
|
|
*/
|
|
|
|
package de.anomic.kelondro.table;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileReader;
|
|
import java.io.IOException;
|
|
import java.io.RandomAccessFile;
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.Date;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.StringTokenizer;
|
|
import java.util.TreeMap;
|
|
import java.util.TreeSet;
|
|
import java.util.Vector;
|
|
import java.util.logging.Logger;
|
|
|
|
import de.anomic.kelondro.index.Row;
|
|
import de.anomic.kelondro.index.RowCollection;
|
|
import de.anomic.kelondro.index.ObjectIndex;
|
|
import de.anomic.kelondro.index.Row.Entry;
|
|
import de.anomic.kelondro.order.ByteOrder;
|
|
import de.anomic.kelondro.order.CloneableIterator;
|
|
import de.anomic.kelondro.order.NaturalOrder;
|
|
import de.anomic.kelondro.util.kelondroException;
|
|
import de.anomic.kelondro.util.Log;
|
|
|
|
public class Tree extends CachedRecords implements ObjectIndex {
|
|
|
|
// logging (This probably needs someone to initialize the java.util.logging.* facilities);
|
|
public static final Logger log = Logger.getLogger("KELONDRO");
|
|
|
|
// define the Over-Head-Array
|
|
protected static final short thisOHBytes = 2; // our record definition of two bytes
|
|
protected static final short thisOHHandles = 3; // and three handles overhead
|
|
protected static final short thisFHandles = 1; // file handles: one for a root pointer
|
|
|
|
// define pointers for OH array access
|
|
protected static final int magic = 0; // pointer for OHByte-array: marker for Node purpose; defaults to 1
|
|
protected static final int balance = 1; // pointer for OHByte-array: balance value of tree node; balanced = 0
|
|
|
|
protected static final int parent = 0; // pointer for OHHandle-array: handle()-Value of parent Node
|
|
protected static final int leftchild = 1; // pointer for OHHandle-array: handle()-Value of left child Node
|
|
protected static final int rightchild = 2; // pointer for OHHandle-array: handle()-Value of right child Node
|
|
|
|
protected static final int root = 0; // pointer for FHandles-array: pointer to root node
|
|
|
|
// class variables
|
|
private final Search writeSearchObj = new Search();
|
|
protected Comparator<String> loopDetectionOrder;
|
|
protected int readAheadChunkSize = 100;
|
|
protected long lastIteratorCount = readAheadChunkSize;
|
|
|
|
/**
|
|
* Deprecated Class. Please use kelondroEcoTable instead
|
|
*/
|
|
@Deprecated
|
|
public Tree(final File file, final boolean useNodeCache, final long preloadTime, final Row rowdef) throws IOException {
|
|
this(file, useNodeCache, preloadTime, rowdef, rowdef.columns() /* txtProps */, 80 /* txtPropWidth */);
|
|
}
|
|
|
|
@Deprecated
|
|
public Tree(final File file, final boolean useNodeCache, final long preloadTime, final Row rowdef, final int txtProps, final int txtPropsWidth) throws IOException {
|
|
// opens an existing tree file or creates a new tree file
|
|
super(file, useNodeCache, preloadTime,
|
|
thisOHBytes, thisOHHandles, rowdef,
|
|
thisFHandles, txtProps, txtPropsWidth);
|
|
if (!fileExisted) {
|
|
// create new file structure
|
|
setHandle(root, null); // define the root value
|
|
}
|
|
super.setLogger(log);
|
|
this.loopDetectionOrder = new ByteOrder.StringOrder(rowdef.objectOrder);
|
|
}
|
|
|
|
@Deprecated
|
|
public static final Tree open(final File file, final boolean useNodeCache, final long preloadTime, final Row rowdef, final int txtProps, final int txtPropsWidth) {
|
|
// opens new or existing file; in case that any error occur the file is deleted again and it is tried to create the file again
|
|
// if that fails, the method returns null
|
|
try {
|
|
return new Tree(file, useNodeCache, preloadTime, rowdef, txtProps, txtPropsWidth);
|
|
} catch (final IOException e) {
|
|
file.delete();
|
|
try {
|
|
return new Tree(file, useNodeCache, preloadTime, rowdef, txtProps, txtPropsWidth);
|
|
} catch (final IOException ee) {
|
|
log.severe("cannot open or create file " + file.toString());
|
|
e.printStackTrace();
|
|
ee.printStackTrace();
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void clear() throws IOException {
|
|
super.clear();
|
|
setHandle(root, null);
|
|
}
|
|
|
|
private void commitNode(final Node n) throws IOException {
|
|
//kelondroHandle left = n.getOHHandle(leftchild);
|
|
//kelondroHandle right = n.getOHHandle(rightchild);
|
|
n.commit();
|
|
}
|
|
|
|
// the has-property in kelondroTree should not be used, because it has the effect of doubling the IO activity in case that
|
|
// the result is 'true'. Whenever possible, please use the get method, store the result in a dummy value and test the result
|
|
// by comparing it with null.
|
|
public boolean has(final byte[] key) {
|
|
boolean result;
|
|
synchronized (writeSearchObj) {
|
|
try {
|
|
writeSearchObj.process(key);
|
|
result = writeSearchObj.found();
|
|
} catch (final IOException e) {
|
|
result = false;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Returns the value to which this map maps the specified key.
|
|
public Row.Entry get(final byte[] key) throws IOException {
|
|
Row.Entry result;
|
|
synchronized (writeSearchObj) {
|
|
writeSearchObj.process(key);
|
|
if (writeSearchObj.found()) {
|
|
result = row().newEntry(writeSearchObj.getMatcher().getValueRow());
|
|
} else {
|
|
result = null;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public ArrayList<RowCollection> removeDoubles() {
|
|
// this data structure cannot have doubles; return empty array
|
|
return new ArrayList<RowCollection>();
|
|
}
|
|
|
|
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 CacheNode thenode, parentnode;
|
|
private boolean found; // property if node was found
|
|
private byte child; // -1: left child; 0: root node; 1: right child
|
|
|
|
// temporary variables
|
|
private RecordHandle thisHandle;
|
|
String keybuffer;
|
|
|
|
protected Search() {
|
|
}
|
|
|
|
protected void process(final 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) {
|
|
throw new kelondroException("startet search process with key == null");
|
|
/*
|
|
child = 0;
|
|
if (thisHandle == null) {
|
|
thenode = null;
|
|
found = false;
|
|
} else {
|
|
thenode = getNode(thisHandle, null, 0);
|
|
found = true;
|
|
}
|
|
return;
|
|
*/
|
|
}
|
|
thenode = null;
|
|
child = 0;
|
|
found = false;
|
|
int c;
|
|
|
|
final TreeSet<String> visitedNodeKeys = new TreeSet<String>(loopDetectionOrder); // to detect loops
|
|
// System.out.println("Starting Compare Loop in Database " + filename); // debug
|
|
while (thisHandle != null) {
|
|
try {
|
|
parentnode = thenode;
|
|
thenode = new CacheNode(thisHandle, thenode, (child == -1) ? leftchild : rightchild, true);
|
|
} catch (final 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");
|
|
|
|
keybuffer = new String(thenode.getKey());
|
|
if (keybuffer == null) {
|
|
// this is an error. distinguish two cases:
|
|
// 1. thenode is a leaf node. Then this error can be fixed if we can consider this node as a good node to be replaced with a new value
|
|
// 2. thenode is not a leaf node. An exception must be thrown
|
|
if ((thenode.getOHHandle(leftchild) == null) && (thenode.getOHHandle(rightchild) == null)) {
|
|
// case 1: recover
|
|
deleteNode(thisHandle);
|
|
thenode = parentnode;
|
|
found = false;
|
|
return;
|
|
} else {
|
|
// case 2: fail
|
|
throw new kelondroException("found key during search process with key == null");
|
|
}
|
|
}
|
|
if (visitedNodeKeys.contains(keybuffer)) {
|
|
// 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();
|
|
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 = row().objectOrder.compare(key, keybuffer.getBytes());
|
|
// 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(keybuffer);
|
|
}
|
|
// 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 CacheNode getMatcher() {
|
|
if (found) return thenode;
|
|
throw new IllegalArgumentException("wrong access of matcher");
|
|
}
|
|
|
|
public CacheNode getParent() {
|
|
if (found) return parentnode;
|
|
return thenode;
|
|
}
|
|
|
|
public boolean isRoot() {
|
|
if (found) throw new IllegalArgumentException("wrong access of isRoot");
|
|
return (child == 0);
|
|
}
|
|
|
|
public boolean isLeft() {
|
|
if (found) throw new IllegalArgumentException("wrong access of leftchild");
|
|
return (child == -1);
|
|
}
|
|
|
|
public boolean isRight() {
|
|
if (found) throw new IllegalArgumentException("wrong access of leftchild");
|
|
return (child == 1);
|
|
}
|
|
}
|
|
|
|
public synchronized boolean isChild(final Node childn, final Node parentn, final int child) {
|
|
if (childn == null) throw new IllegalArgumentException("isLeftChild: Node parameter is NULL");
|
|
final RecordHandle lc = parentn.getOHHandle(child);
|
|
if (lc == null) return false;
|
|
return (lc.equals(childn.handle()));
|
|
}
|
|
|
|
public synchronized void putMultiple(final List<Entry> rows) throws IOException {
|
|
final Iterator<Entry> i = rows.iterator();
|
|
while (i.hasNext()) put(i.next());
|
|
}
|
|
|
|
public Row.Entry put(final Row.Entry row, final Date entryDate) throws IOException {
|
|
return put(row);
|
|
}
|
|
|
|
public Row.Entry put(final Row.Entry newrow) throws IOException {
|
|
assert (newrow != null);
|
|
assert (newrow.columns() == row().columns());
|
|
assert (!(Log.allZero(newrow.getPrimaryKeyBytes())));
|
|
assert newrow.objectsize() <= super.row().objectsize;
|
|
// Associates the specified value with the specified key in this map
|
|
Row.Entry result = null;
|
|
//writeLock.stay(2000, 1000);
|
|
// first try to find the key element in the database
|
|
synchronized(writeSearchObj) {
|
|
writeSearchObj.process(newrow.getColBytes(0));
|
|
if (writeSearchObj.found()) {
|
|
// a node with this key exist. simply overwrite the content and return old content
|
|
final Node e = writeSearchObj.getMatcher();
|
|
result = row().newEntry(e.getValueRow());
|
|
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
|
|
final Node e = new CacheNode(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();
|
|
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
|
|
CacheNode parentNode = writeSearchObj.getParent();
|
|
CacheNode theNode = new CacheNode(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();
|
|
|
|
// 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. parent=" + new String(parentNode.getKey()) + ", leftchild=" + new String(new CacheNode(parentNode.getOHHandle(leftchild), (CacheNode) null, 0, true).getKey()));
|
|
parentNode.setOHHandle(leftchild, theNode.handle());
|
|
} else if (writeSearchObj.isRight()) {
|
|
if (parentNode.getOHHandle(rightchild) != null) throw new kelondroException(filename, "tried to create rightchild node twice. parent=" + new String(parentNode.getKey()) + ", rightchild=" + new String(new CacheNode(parentNode.getOHHandle(rightchild), (CacheNode) null, 0, true).getKey()));
|
|
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;
|
|
RecordHandle 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(parentBalance) - java.lang.Math.abs(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(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")) {
|
|
final RecordHandle parentHandle = parentNode.handle();
|
|
LL_RightRotation(theNode, new CacheNode(theNode.getOHHandle(leftchild), theNode, leftchild, false));
|
|
parentNode = new CacheNode(parentHandle, null, 0, false); // reload the parent node
|
|
RR_LeftRotation(parentNode, new CacheNode(parentNode.getOHHandle(rightchild), parentNode, rightchild, false));
|
|
break;
|
|
}
|
|
if (path.startsWith("LR")) {
|
|
final RecordHandle parentHandle = parentNode.handle();
|
|
RR_LeftRotation(theNode, new CacheNode(theNode.getOHHandle(rightchild), theNode, rightchild, false));
|
|
parentNode = new CacheNode(parentHandle, null, 0, false); // reload the parent node
|
|
LL_RightRotation(parentNode, new CacheNode(parentNode.getOHHandle(leftchild), parentNode, leftchild, false));
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
// crawl up the tree
|
|
if (parentNode.getOHHandle(parent) == null) break; // root reached: stop
|
|
theNode = parentNode;
|
|
parentNode = new CacheNode(parentNode.getOHHandle(parent), null, 0, false);
|
|
}
|
|
|
|
result = null; // that means: no previous stored value present
|
|
}
|
|
}
|
|
//writeLock.release();
|
|
return result;
|
|
}
|
|
|
|
public synchronized void addUnique(final Row.Entry row) throws IOException {
|
|
this.put(row);
|
|
}
|
|
|
|
public synchronized void addUnique(final Row.Entry row, final Date entryDate) throws IOException {
|
|
this.put(row, entryDate);
|
|
}
|
|
|
|
public synchronized void addUniqueMultiple(final List<Row.Entry> rows) throws IOException {
|
|
final Iterator<Row.Entry> i = rows.iterator();
|
|
while (i.hasNext()) addUnique(i.next());
|
|
}
|
|
|
|
private void assignChild(final Node parentNode, final Node childNode, final int childType) throws IOException {
|
|
parentNode.setOHHandle(childType, childNode.handle());
|
|
childNode.setOHHandle(parent, parentNode.handle());
|
|
commitNode(parentNode);
|
|
commitNode(childNode);
|
|
}
|
|
|
|
private void replace(final Node oldNode, final Node oldNodeParent, final 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
|
|
RecordHandle 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(final byte b) {
|
|
if (b > 0) return b;
|
|
return 0;
|
|
}
|
|
|
|
private static byte min0(final byte b) {
|
|
if (b < 0) return b;
|
|
return 0;
|
|
}
|
|
|
|
private void LL_RightRotation(final Node parentNode, final CacheNode childNode) throws IOException {
|
|
// replace the parent node; the parent is afterwards unlinked
|
|
final RecordHandle p2Handle = parentNode.getOHHandle(parent);
|
|
final Node p2Node = (p2Handle == null) ? null : new CacheNode(p2Handle, null, 0, false);
|
|
replace(parentNode, p2Node, childNode);
|
|
|
|
// set the left son of the parent to the right son of the childNode
|
|
final RecordHandle childOfChild = childNode.getOHHandle(rightchild);
|
|
if (childOfChild == null) {
|
|
parentNode.setOHHandle(leftchild, null);
|
|
} else {
|
|
assignChild(parentNode, new CacheNode(childOfChild, childNode, rightchild, false), 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);
|
|
final byte oldBalParent = parentBalance;
|
|
final 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(final Node parentNode, final CacheNode childNode) throws IOException {
|
|
// replace the parent node; the parent is afterwards unlinked
|
|
final RecordHandle p2Handle = parentNode.getOHHandle(parent);
|
|
final Node p2Node = (p2Handle == null) ? null : new CacheNode(p2Handle, null, 0, false);
|
|
replace(parentNode, p2Node, childNode);
|
|
|
|
// set the left son of the parent to the right son of the childNode
|
|
final RecordHandle childOfChild = childNode.getOHHandle(leftchild);
|
|
if (childOfChild == null) {
|
|
parentNode.setOHHandle(rightchild, null);
|
|
} else {
|
|
assignChild(parentNode, new CacheNode(childOfChild, childNode, leftchild, false), 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);
|
|
final byte oldBalParent = parentBalance;
|
|
final 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(final byte[] key, final byte[] value) throws IOException {
|
|
final Row.Entry row = row().newEntry(new byte[][]{key, value});
|
|
final Row.Entry ret = put(row);
|
|
if (ret == null) return null;
|
|
return ret.getColBytes(0);
|
|
}
|
|
|
|
// Removes the mapping for this key from this map if present (optional operation).
|
|
public Row.Entry remove(final byte[] key) throws IOException {
|
|
// the order inside the database file cannot be maintained, but iteration over objects will always maintain the object order
|
|
// therefore keepOrder should be true, because the effect is always given, while the data structure does not maintain order
|
|
// delete from database
|
|
synchronized(writeSearchObj) {
|
|
writeSearchObj.process(key);
|
|
if (writeSearchObj.found()) {
|
|
final CacheNode result = writeSearchObj.getMatcher();
|
|
final Row.Entry values = row().newEntry(result.getValueRow());
|
|
remove(result, writeSearchObj.getParent());
|
|
return values;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public Row.Entry removeOne() throws IOException {
|
|
// removes just any entry and removes that entry
|
|
synchronized(writeSearchObj) {
|
|
final CacheNode theOne = lastNode();
|
|
final Row.Entry values = row().newEntry(theOne.getValueRow());
|
|
remove(theOne, null);
|
|
return values;
|
|
}
|
|
}
|
|
|
|
public synchronized void removeAll() throws IOException {
|
|
while (size() > 0) remove(lastNode(), null);
|
|
}
|
|
|
|
private void remove(CacheNode node, final 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 {
|
|
RecordHandle 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, new CacheNode(node.getOHHandle(leftchild), node, leftchild, false));
|
|
} else if ((node.getOHHandle(leftchild) == null) && (node.getOHHandle(rightchild) != null)) {
|
|
replace(node, parentOfNode, new CacheNode(node.getOHHandle(rightchild), node, rightchild, false));
|
|
} else {
|
|
// difficult case: node has two children
|
|
final CacheNode repl = lastNode(new CacheNode(node.getOHHandle(leftchild), node, leftchild, false));
|
|
//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;
|
|
RecordHandle 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 = new CacheNode(repl.getOHHandle(parent), null, 0, false); // 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 = new CacheNode(repl.getOHHandle(leftchild), repl, leftchild, false);
|
|
replace(repl, new CacheNode(repl.getOHHandle(parent), null, 0, false), childnode);
|
|
} catch (final IllegalArgumentException e) {
|
|
// now treat the situation as if that link had been null before
|
|
n = new CacheNode(repl.getOHHandle(parent), null, 0, false); // 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 = new CacheNode(repl.getOHHandle(rightchild), repl, rightchild, false);
|
|
replace(repl, new CacheNode(repl.getOHHandle(parent), null, 0, false), childnode);
|
|
} catch (final IllegalArgumentException e) {
|
|
// now treat the situation as if that link had been null before
|
|
n = new CacheNode(repl.getOHHandle(parent), null, 0, false); // 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 = new CacheNode(node.handle(), null, 0, false); // 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
|
|
final byte b = node.getOHByte(balance); // save balance of disappearing node
|
|
final RecordHandle parentHandle = node.getOHHandle(parent);
|
|
final RecordHandle leftchildHandle = node.getOHHandle(leftchild);
|
|
final RecordHandle 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 = new CacheNode(leftchildHandle, node, leftchild, false);
|
|
n.setOHHandle(parent, repl.handle());
|
|
commitNode(n);
|
|
}
|
|
if (rightchildHandle != null) {
|
|
n = new CacheNode(rightchildHandle, node, rightchild, false);
|
|
n.setOHHandle(parent, repl.handle());
|
|
commitNode(n);
|
|
}
|
|
}
|
|
// move node to recycling queue
|
|
synchronized (this) {
|
|
deleteNode(node.handle());
|
|
}
|
|
}
|
|
|
|
protected CacheNode firstNode() throws IOException {
|
|
final RecordHandle h = getHandle(root);
|
|
if (h == null) return null;
|
|
return firstNode(new CacheNode(h, null, 0, true));
|
|
}
|
|
|
|
protected CacheNode firstNode(CacheNode node) throws IOException {
|
|
if (node == null) throw new IllegalArgumentException("firstNode: node=null");
|
|
RecordHandle h = node.getOHHandle(leftchild);
|
|
final HashSet<String> visitedNodeKeys = new HashSet<String>(); // to detect loops
|
|
String nodeKey;
|
|
while (h != null) {
|
|
try {
|
|
node = new CacheNode(h, node, leftchild, true);
|
|
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 (final IllegalArgumentException e) {
|
|
// return what we have
|
|
return node;
|
|
}
|
|
h = node.getOHHandle(leftchild);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
protected CacheNode lastNode() throws IOException {
|
|
final RecordHandle h = getHandle(root);
|
|
if (h == null) return null;
|
|
return lastNode(new CacheNode(h, null, 0, true));
|
|
}
|
|
|
|
protected CacheNode lastNode(CacheNode node) throws IOException {
|
|
if (node == null) throw new IllegalArgumentException("lastNode: node=null");
|
|
RecordHandle h = node.getOHHandle(rightchild);
|
|
final HashSet<String> visitedNodeKeys = new HashSet<String>(); // to detect loops
|
|
String nodeKey;
|
|
while (h != null) {
|
|
try {
|
|
node = new CacheNode(h, node, rightchild, true);
|
|
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 (final IllegalArgumentException e) {
|
|
// return what we have
|
|
return node;
|
|
}
|
|
h = node.getOHHandle(rightchild);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
private class nodeIterator implements Iterator<CacheNode> {
|
|
// we implement an iteration! (not a recursive function as the structure would suggest...)
|
|
// the iterator iterates Node objects
|
|
CacheNode nextNode = null;
|
|
boolean up, rot;
|
|
LinkedList<Object[]> nodeStack;
|
|
int count;
|
|
|
|
public nodeIterator(final boolean up, final boolean rotating) throws IOException {
|
|
this.count = 0;
|
|
this.up = up;
|
|
this.rot = rotating;
|
|
|
|
// initialize iterator
|
|
init((up) ? firstNode() : lastNode());
|
|
}
|
|
|
|
public nodeIterator(final boolean up, final boolean rotating, final byte[] firstKey, final boolean including) throws IOException {
|
|
this.count = 0;
|
|
this.up = up;
|
|
this.rot = rotating;
|
|
|
|
final Search search = new Search();
|
|
search.process(firstKey);
|
|
if (search.found()) {
|
|
init(search.getMatcher());
|
|
} else {
|
|
final CacheNode 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)) {
|
|
final int c = row().objectOrder.compare(firstKey, nextNode.getKey());
|
|
if (c == 0) {
|
|
if (including) {
|
|
break; // correct + finished
|
|
}
|
|
if (hasNext()) next(); else nextNode = null;
|
|
break; // corrected + finished
|
|
} else if (c < 0) {
|
|
if (up) {
|
|
break; // correct + finished
|
|
}
|
|
// 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(final CacheNode start) throws IOException {
|
|
this.nextNode = start;
|
|
|
|
// fill node stack for start node
|
|
nodeStack = new LinkedList<Object[]>();
|
|
|
|
RecordHandle searchHandle = getHandle(root);
|
|
if (searchHandle == null) {nextNode = null; return;}
|
|
|
|
CacheNode searchNode = new CacheNode(searchHandle, null, 0, false);
|
|
final byte[] startKey = start.getKey();
|
|
int c, ct;
|
|
while ((c = row().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, Integer.valueOf(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 = new CacheNode(searchHandle, searchNode, ct, false);
|
|
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 boolean hasNext() {
|
|
return (rot && (size() > 0)) || (nextNode != null);
|
|
}
|
|
|
|
public CacheNode next() {
|
|
count++;
|
|
if ((rot) && (nextNode == null)) try {
|
|
init((up) ? firstNode() : lastNode());
|
|
} catch (final 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");
|
|
final CacheNode ret = nextNode;
|
|
|
|
// middle-case
|
|
try {
|
|
int childtype = (up) ? rightchild : leftchild;
|
|
RecordHandle 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
|
|
final HashSet<RecordHandle> visitedNodeHandles = new HashSet<RecordHandle>(); // to detect loops
|
|
nodeStack.addLast(new Object[]{nextNode, Integer.valueOf(childtype)});
|
|
nextNode = new CacheNode(childHandle, nextNode, childtype, false);
|
|
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();
|
|
logWarning("nodeIterator.next: internal loopback; fixed loop and try to go on");
|
|
break;
|
|
}
|
|
visitedNodeHandles.add(childHandle);
|
|
try {
|
|
nodeStack.addLast(new Object[]{nextNode, Integer.valueOf(childtype)});
|
|
nextNode = new CacheNode(childHandle, nextNode, childtype, false);
|
|
} catch (final 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;
|
|
CacheNode 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 = nodeStack.removeLast(); // top of stack: Node/parentpointer pair
|
|
parentNode = (CacheNode) stacktop[0];
|
|
parentpointer = ((Integer) stacktop[1]).intValue();
|
|
}
|
|
if ((nodeStack.size() == 0) && (parentpointer == ((up) ? rightchild : leftchild))) {
|
|
nextNode = null;
|
|
} else {
|
|
nextNode = parentNode;
|
|
}
|
|
}
|
|
}
|
|
} catch (final 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<String, Row.Entry> rowMap(final boolean up, final byte[] firstKey, final boolean including, final int count) throws IOException {
|
|
// returns an ordered map of keys/row relations; key objects are of type String, value objects are of type byte[][]
|
|
final ByteOrder setOrder = (ByteOrder) row().objectOrder.clone();
|
|
setOrder.direction(up);
|
|
setOrder.rotate(firstKey);
|
|
final TreeMap<String, Row.Entry> rows = new TreeMap<String, Row.Entry>(this.loopDetectionOrder);
|
|
CacheNode n;
|
|
String key;
|
|
synchronized (this) {
|
|
final Iterator<CacheNode> i = (firstKey == null) ? new nodeIterator(up, false) : new nodeIterator(up, false, firstKey, including);
|
|
while ((rows.size() < count) && (i.hasNext())) {
|
|
n = 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<String> keySet(final boolean up, final boolean rotating, final byte[] firstKey, final boolean including, final int count) throws IOException {
|
|
// returns an ordered set of keys; objects are of type String
|
|
final ByteOrder setOrder = (ByteOrder) row().objectOrder.clone();
|
|
setOrder.direction(up);
|
|
setOrder.rotate(firstKey);
|
|
final TreeSet<String> set = new TreeSet<String>(this.loopDetectionOrder);
|
|
Node n;
|
|
synchronized (this) {
|
|
final Iterator<CacheNode> i = (firstKey == null) ? new nodeIterator(up, rotating) : new nodeIterator(up, rotating, firstKey, including);
|
|
while ((set.size() < count) && (i.hasNext())) {
|
|
n = i.next();
|
|
if ((n != null) && (n.getKey() != null)) set.add(new String(n.getKey()));
|
|
}
|
|
}
|
|
return set;
|
|
}
|
|
|
|
public CloneableIterator<Row.Entry> rows(final boolean up, final 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, firstKey, this.size());
|
|
}
|
|
|
|
public CloneableIterator<Row.Entry> rows() throws IOException {
|
|
return new rowIterator(true, null, this.size());
|
|
}
|
|
|
|
public class rowIterator implements CloneableIterator<Row.Entry> {
|
|
|
|
int chunkSize;
|
|
boolean inc;
|
|
long count;
|
|
byte[] lastKey;
|
|
TreeMap<String, Row.Entry> rowBuffer;
|
|
Iterator<Map.Entry<String, Row.Entry>> bufferIterator;
|
|
long guessedCountLimit;
|
|
|
|
public rowIterator(final boolean up, final byte[] firstKey, final long guessedCountLimit) throws IOException {
|
|
this.guessedCountLimit = guessedCountLimit;
|
|
inc = up;
|
|
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, firstKey, true, chunkSize);
|
|
bufferIterator = rowBuffer.entrySet().iterator();
|
|
lastIteratorCount = 0;
|
|
}
|
|
|
|
public rowIterator clone(final Object secondStart) {
|
|
try {
|
|
return new rowIterator(inc, (byte[]) secondStart, guessedCountLimit);
|
|
} catch (final IOException e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public boolean hasNext() {
|
|
return ((bufferIterator != null) && (bufferIterator.hasNext()) && (count < size()));
|
|
}
|
|
|
|
public Row.Entry next() {
|
|
if (!(bufferIterator.hasNext())) return null;
|
|
final Map.Entry<String, Row.Entry> entry = bufferIterator.next();
|
|
lastKey = 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]++; // ***BUG??? FIXME
|
|
rowBuffer = rowMap(inc, lastKey, false, chunkSize);
|
|
bufferIterator = rowBuffer.entrySet().iterator();
|
|
} catch (final IOException e) {
|
|
rowBuffer = null;
|
|
bufferIterator = null;
|
|
}
|
|
}
|
|
|
|
// return the row
|
|
count++;
|
|
lastIteratorCount++;
|
|
return entry.getValue();
|
|
}
|
|
|
|
public void remove() {
|
|
if (lastKey != null) try {
|
|
Tree.this.remove(lastKey);
|
|
} catch (final IOException e) {
|
|
// do nothing
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public CloneableIterator<byte[]> keys(final boolean up, final byte[] firstKey) throws IOException {
|
|
return new keyIterator(up, firstKey, this.size());
|
|
}
|
|
|
|
public class keyIterator implements CloneableIterator<byte[]> {
|
|
|
|
int chunkSize;
|
|
boolean inc;
|
|
long count;
|
|
byte[] lastKey;
|
|
TreeSet<String> keyBuffer;
|
|
Iterator<String> bufferIterator;
|
|
long guessedCountLimit;
|
|
|
|
public keyIterator(final boolean up, final byte[] firstKey, final long guessedCountLimit) throws IOException {
|
|
this.guessedCountLimit = guessedCountLimit;
|
|
inc = up;
|
|
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);
|
|
keyBuffer = keySet(inc, false, firstKey, true, chunkSize);
|
|
bufferIterator = keyBuffer.iterator();
|
|
lastIteratorCount = 0;
|
|
}
|
|
|
|
public keyIterator clone(final Object secondStart) {
|
|
try {
|
|
return new keyIterator(inc, (byte[]) secondStart, guessedCountLimit);
|
|
} catch (final IOException e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public boolean hasNext() {
|
|
return ((bufferIterator != null) && (bufferIterator.hasNext()) && (count < size()));
|
|
}
|
|
|
|
public byte[] next() {
|
|
if (!(bufferIterator.hasNext())) return null;
|
|
lastKey = bufferIterator.next().getBytes();
|
|
|
|
// check if this was the last entry in the rowBuffer
|
|
if (!(bufferIterator.hasNext())) {
|
|
// assign next buffer chunk
|
|
try {
|
|
lastKey[lastKey.length - 1]++; // ***BUG??? FIXME
|
|
keyBuffer = keySet(inc, false, lastKey, false, chunkSize);
|
|
bufferIterator = keyBuffer.iterator();
|
|
} catch (final IOException e) {
|
|
keyBuffer = null;
|
|
bufferIterator = null;
|
|
}
|
|
}
|
|
|
|
// return the row
|
|
count++;
|
|
lastIteratorCount++;
|
|
return lastKey;
|
|
}
|
|
|
|
public void remove() {
|
|
if (lastKey != null) try {
|
|
Tree.this.remove(lastKey);
|
|
} catch (final IOException e) {
|
|
// do nothing
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public int imp(final File file, final String separator) throws IOException {
|
|
// imports a value-separated file, returns number of records that have been read
|
|
|
|
final RandomAccessFile f = new RandomAccessFile(file,"r");
|
|
String s;
|
|
StringTokenizer st;
|
|
int recs = 0;
|
|
final Row.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 {
|
|
final RecordHandle h = getHandle(root);
|
|
if (h == null) return 0;
|
|
return height(new CacheNode(h, null, 0, false));
|
|
} catch (final IOException e) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private int height(final CacheNode node) throws IOException {
|
|
if (node == null) return 0;
|
|
RecordHandle h = node.getOHHandle(leftchild);
|
|
final int hl = (h == null) ? 0 : height(new CacheNode(h, node, leftchild, false));
|
|
h = node.getOHHandle(rightchild);
|
|
final int hr = (h == null) ? 0 : height(new CacheNode(h, node, rightchild, false));
|
|
if (hl > hr) return hl + 1;
|
|
return hr + 1;
|
|
}
|
|
|
|
public void print() throws IOException {
|
|
super.print();
|
|
final int height = height();
|
|
System.out.println("HEIGHT = " + height);
|
|
Vector<RecordHandle> thisline = new Vector<RecordHandle>();
|
|
thisline.add(getHandle(root));
|
|
Vector<RecordHandle> nextline;
|
|
RecordHandle handle;
|
|
Node node;
|
|
int linelength;
|
|
final int 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<RecordHandle>();
|
|
for (int i = 0; i < thisline.size(); i++) {
|
|
handle = thisline.elementAt(i);
|
|
if (handle == null) {
|
|
node = null;
|
|
key = "[..]";
|
|
} else {
|
|
node = new CacheNode(handle, null, 0, false);
|
|
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 = thisline.elementAt(i);
|
|
if (handle == null) {
|
|
node = null;
|
|
key = "NULL";
|
|
} else {
|
|
node = new CacheNode(handle, null, 0, false);
|
|
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(final 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
|
|
final File testFile = new File("test.db");
|
|
while (testFile.exists()) testFile.delete();
|
|
final Tree fm = new Tree(testFile, true, 10, new Row("byte[] a-4, byte[] b-4", NaturalOrder.naturalOrder, 0));
|
|
final 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) {
|
|
final Tree fm = new Tree(new File(args[1]), true, 10, new Row("byte[] a-4, byte[] b-4", NaturalOrder.naturalOrder, 0));
|
|
if (args[0].equals("-v")) {
|
|
fm.print();
|
|
ret = null;
|
|
}
|
|
fm.close();
|
|
} else if (args.length == 3) {
|
|
if (args[0].equals("-d")) {
|
|
final Tree fm = new Tree(new File(args[1]), true, 10, new Row("byte[] a-4, byte[] b-4", NaturalOrder.naturalOrder, 0));
|
|
fm.remove(args[2].getBytes());
|
|
fm.close();
|
|
} else if (args[0].equals("-i")) {
|
|
final Tree fm = new Tree(new File(args[1]), true, 10, new Row("byte[] a-4, byte[] b-4", NaturalOrder.naturalOrder, 0));
|
|
final int i = fm.imp(new File(args[1]),";");
|
|
fm.close();
|
|
ret = (i + " records imported").getBytes();
|
|
} else if (args[0].equals("-s")) {
|
|
final 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(final Exception e){}
|
|
}
|
|
} else if (args[0].equals("-g")) {
|
|
final Tree fm = new Tree(new File(args[1]), true, 10, new Row("byte[] a-4, byte[] b-4", NaturalOrder.naturalOrder, 0));
|
|
final Row.Entry ret2 = fm.get(args[2].getBytes());
|
|
ret = ((ret2 == null) ? null : ret2.getColBytes(1));
|
|
fm.close();
|
|
} else if (args[0].equals("-n")) {
|
|
final Tree fm = new Tree(new File(args[1]), true, 10, new Row("byte[] a-4, byte[] b-4", NaturalOrder.naturalOrder, 0));
|
|
//byte[][] keys = fm.getSequentialKeys(args[2].getBytes(), 500, true);
|
|
final Iterator<Row.Entry> rowIt = fm.rows(true, (args[2].length() == 0) ? null : args[2].getBytes());
|
|
final Vector<String> v = new Vector<String>();
|
|
while (rowIt.hasNext()) v.add(rowIt.next().getColString(0, null));
|
|
ret = v.toString().getBytes();
|
|
fm.close();
|
|
}
|
|
} else if (args.length == 4) {
|
|
if (args[0].equals("-c")) {
|
|
// create <keylen> <valuelen> <filename>
|
|
final File f = new File(args[3]);
|
|
if (f.exists()) f.delete();
|
|
final Row lens = new Row("byte[] key-" + Integer.parseInt(args[1]) + ", byte[] value-" + Integer.parseInt(args[2]), NaturalOrder.naturalOrder, 0);
|
|
final Tree fm = new Tree(f, true, 10, lens);
|
|
fm.close();
|
|
} else if (args[0].equals("-u")) {
|
|
final Tree fm = new Tree(new File(args[1]), true, 10, new Row("byte[] a-4, byte[] b-4", NaturalOrder.naturalOrder, 0));
|
|
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 (final Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
public static void main(final String[] args) {
|
|
//cmd(args);
|
|
//iterationtest();
|
|
bigtest(Integer.parseInt(args[0]));
|
|
//randomtest(Integer.parseInt(args[0]));
|
|
//smalltest();
|
|
}
|
|
|
|
public static String[] permutations(final int letters) {
|
|
String p = "";
|
|
for (int i = 0; i < letters; i++) p = p + ((char) (('A') + i));
|
|
return permutations(p);
|
|
}
|
|
public static String[] permutations(final String source) {
|
|
if (source.length() == 0) return new String[0];
|
|
if (source.length() == 1) return new String[]{source};
|
|
final char c = source.charAt(0);
|
|
final String[] recres = permutations(source.substring(1));
|
|
final 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(final char c) {
|
|
return new byte[]{(byte) c, 32, 32, 32};
|
|
}
|
|
|
|
public static void randomtest(final int elements) {
|
|
System.out.println("random " + elements + ":");
|
|
final String s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".substring(0, elements);
|
|
String t, d;
|
|
char c;
|
|
Tree tt = null;
|
|
final File testFile = new File("test.db");
|
|
byte[] b;
|
|
try {
|
|
int steps = 0;
|
|
while (true) {
|
|
if (testFile.exists()) testFile.delete();
|
|
tt = new Tree(testFile, true, 10, new Row("byte[] a-4, byte[] b-4", NaturalOrder.naturalOrder, 0));
|
|
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 (final Exception e) {
|
|
e.printStackTrace();
|
|
if (tt != null) try {tt.print();} catch (final IOException ee) {}
|
|
System.out.println("TERMINATED");
|
|
}
|
|
}
|
|
|
|
public static void smalltest() {
|
|
final File f = new File("test.db");
|
|
if (f.exists()) f.delete();
|
|
try {
|
|
final Tree tt = new Tree(f, true, 10, new Row("byte[] a-4, byte[] b-4", NaturalOrder.naturalOrder, 0));
|
|
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);
|
|
final int c = countElements(tt);
|
|
System.out.println("elements: " + c);
|
|
final Iterator<Row.Entry> i = tt.rows(true, testWord('G'));
|
|
for (int j = 0; j < c; j++) {
|
|
System.out.println("Row " + j + ": " + new String((i.next()).getColBytes(0)));
|
|
}
|
|
System.out.println("TERMINATED");
|
|
} catch (final 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 Tree testTree(final File f, final String testentities) throws IOException {
|
|
if (f.exists()) f.delete();
|
|
final Tree tt = new Tree(f, false, 10, new Row("byte[] a-4, byte[] b-4", NaturalOrder.naturalOrder, 0));
|
|
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(final int elements) {
|
|
System.out.println("starting big test with " + elements + " elements:");
|
|
final long start = System.currentTimeMillis();
|
|
final String[] s = permutations(elements);
|
|
Tree tt;
|
|
final 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 (final Exception e) {
|
|
e.printStackTrace();
|
|
System.out.println("TERMINATED");
|
|
}
|
|
}
|
|
|
|
public static int countElements(final ObjectIndex t) {
|
|
int count = 0;
|
|
try {
|
|
final Iterator<Row.Entry> iter = t.rows();
|
|
Row.Entry row;
|
|
while (iter.hasNext()) {
|
|
count++;
|
|
row = iter.next();
|
|
if (row == null) System.out.println("ERROR! null element found");
|
|
// else System.out.println("counted element: " + new
|
|
// String(n.getKey()));
|
|
}
|
|
} catch (final IOException e) {
|
|
}
|
|
return count;
|
|
}
|
|
|
|
}
|