/*
* This file is part of the HyperGraphDB source distribution. This is copyrighted software. For permitted
* uses, licensing options and redistribution, please see the LicensingInformation file at the root level of
* the distribution.
*
* Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved.
*/
package org.hypergraphdb.storage.bje;
import java.util.NoSuchElementException;
import org.hypergraphdb.HGException;
import org.hypergraphdb.HGRandomAccessResult;
import org.hypergraphdb.storage.ByteArrayConverter;
import org.hypergraphdb.util.CountMe;
import org.hypergraphdb.util.HGUtils;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
/**
* <p>
* An <code>IndexResultSet</code> is based on a cursor over an indexed set of values. Implementation of
* complex query execution may move the cursor position based on some index key to speed up query processing.
* </p>
*
* @author Borislav Iordanov
*/
@SuppressWarnings("unchecked")
public abstract class IndexResultSet<T> implements HGRandomAccessResult<T>, CountMe {
private static final Object UNKNOWN = new Object();
protected BJETxCursor cursor;
protected Object current = UNKNOWN, prev = UNKNOWN, next = UNKNOWN;
protected DatabaseEntry key;
protected DatabaseEntry data = new DatabaseEntry();
protected ByteArrayConverter<T> converter;
protected int lookahead = 0;
protected final void closeNoException() {
try {
close();
}
catch (Throwable t) {
}
}
protected final void checkCursor() {
if (!cursor.isOpen())
throw new HGException(
"DefaultIndexImpl.IndexResultSet: attempt to perform an operation on a closed or invalid cursor.");
}
/**
*
* <p>
* Copy <code>data</code> into the <code>entry</code>. Adjust <code>entry</code>'s byte buffer if needed.
* </p>
*
* @param entry
* @param data
*/
protected void assignData(DatabaseEntry entry, byte[] data) {
byte[] dest = entry.getData();
if (dest == null || dest.length != data.length) {
dest = new byte[data.length];
entry.setData(dest);
}
System.arraycopy(data, 0, dest, 0, data.length);
}
protected final void moveNext() {
// checkCursor();
prev = current;
current = next;
next = UNKNOWN;
lookahead--;
/*
* while (true) { next = advance(); if (next == null) break; if (++lookahead == 1) break; }
*/
}
protected final void movePrev() {
// checkCursor();
next = current;
current = prev;
prev = UNKNOWN;
lookahead++;
/*
* while (true) { prev = back(); if (prev == null) break; if (--lookahead == -1) break; }
*/
}
protected abstract T advance();
protected abstract T back();
/**
* <p>
* Construct an empty result set.
* </p>
*/
protected IndexResultSet() {
}
// static int idcounter = 0;
// int id = 0;
/**
* <p>
* Construct a result set matching a specific key.
* </p>
*
* @param cursor
* @param key
*/
public IndexResultSet(BJETxCursor cursor, DatabaseEntry keyIn, ByteArrayConverter<T> converter) {
/*
* id = idcounter++; System.out.println("Constructing index set with id " + id); StackTraceElement
* e[]=Thread.currentThread().getStackTrace(); for (int i=0; i <e.length; i++) { System.out.println(e[i]);
* }
*/
this.converter = converter;
this.cursor = cursor;
this.key = new DatabaseEntry();
if (keyIn != null) {
assignData(this.key, keyIn.getData());
}
try {
cursor.cursor().getCurrent(key, data, LockMode.DEFAULT);
next = converter.fromByteArray(data.getData(), data.getOffset(), data.getSize());
lookahead = 1;
}
catch (Throwable t) {
throw new HGException(t);
}
}
protected void positionToCurrent(byte[] data, int offset, int length) {
current = converter.fromByteArray(data, offset, length);
lookahead = 0;
prev = next = UNKNOWN;
}
public void goBeforeFirst() {
try {
if (cursor.cursor().getFirst(key, data, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
current = UNKNOWN;
prev = null;
next = converter.fromByteArray(data.getData(), data.getOffset(), data.getSize());
lookahead = 1;
}
else {
prev = next = null;
current = UNKNOWN;
lookahead = 0;
}
}
catch (Throwable t) {
closeNoException();
throw new HGException(t);
}
}
public void goAfterLast() {
try {
if (cursor.cursor().getLast(key, data, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
current = UNKNOWN;
next = null;
prev = converter.fromByteArray(data.getData(), data.getOffset(), data.getSize());
lookahead = -1;
}
else {
prev = next = null;
current = UNKNOWN;
lookahead = 0;
}
}
catch (Throwable t) {
closeNoException();
throw new HGException(t);
}
}
public GotoResult goTo(T value, boolean exactMatch) {
byte[] B = converter.toByteArray(value);
assignData(data, B);
try {
OperationStatus status = null;
if (exactMatch) {
status = cursor.cursor().getSearchBoth(key, data, LockMode.DEFAULT);
if (status == OperationStatus.SUCCESS) {
positionToCurrent(data.getData(), data.getOffset(), data.getSize());
return GotoResult.found;
}
else
return GotoResult.nothing;
}
else {
status = cursor.cursor().getSearchBothRange(key, data, LockMode.DEFAULT);
if (status == OperationStatus.SUCCESS) {
GotoResult result = HGUtils.eq(B, data.getData()) ? GotoResult.found : GotoResult.close;
positionToCurrent(data.getData(), data.getOffset(), data.getSize());
return result;
}
else
return GotoResult.nothing;
}
}
catch (Throwable t) {
closeNoException();
throw new HGException(t);
}
}
public final void close() {
if (cursor == null)
return;
try {
current = next = prev = UNKNOWN;
key = null;
cursor.close();
}
catch (Throwable t) {
throw new HGException("Exception while closing a DefaultIndexImpl cursor: " + t.toString(), t);
}
finally {
cursor = null;
}
}
public final T current() {
if (current == UNKNOWN)
throw new NoSuchElementException();
return (T)current;
}
public final boolean hasPrev() {
if (prev == UNKNOWN) {
while (lookahead > -1) {
prev = back();
if (prev == null)
break;
lookahead--;
}
// prev = back();
}
return prev != null;
}
public final boolean hasNext() {
if (next == UNKNOWN) {
while (lookahead < 1) {
next = advance();
if (next == null)
break;
lookahead++;
}
// next = advance();
}
return next != null;
}
public final T prev() {
if (!hasPrev())
throw new NoSuchElementException();
movePrev();
return current();
}
public final T next() {
if (!hasNext())
throw new NoSuchElementException();
moveNext();
return current();
}
public final void remove() {
throw new UnsupportedOperationException("HG - IndexResultSet does not implement remove.");
}
protected void finalize() {
/*
* if (cursor != null) {
*
* System.out.print("WARNING: set id " + id + " closing unclosed cursor in finalizer method -- DB is: ");
* try { System.out.println(cursor.getDatabase().getDatabaseName()); } catch (Exception ex) {
* ex.printStackTrace(System.err); } }
*/
closeNoException();
}
public int count() {
try {
return cursor.cursor().count();
}
catch (DatabaseException ex) {
throw new HGException(ex);
}
}
/**
* Remove current element. After that cursor becomes invalid, so next(), prev() operations will fail.
* However, a goTo operation should work.
*/
public void removeCurrent() {
try {
cursor.cursor().delete();
}
catch (DatabaseException ex) {
throw new HGException(ex);
}
}
}