package com.sleepycat.collections;
import com.sleepycat.compat.DbCompat;
import com.sleepycat.db.Cursor;
import com.sleepycat.db.CursorConfig;
import com.sleepycat.db.DatabaseEntry;
import com.sleepycat.db.DatabaseException;
import com.sleepycat.db.JoinConfig;
import com.sleepycat.db.JoinCursor;
import com.sleepycat.db.LockMode;
import com.sleepycat.db.OperationStatus;
import com.sleepycat.util.keyrange.KeyRange;
import com.sleepycat.util.keyrange.RangeCursor;
final class DataCursor implements Cloneable {
static final int REPOS_EXACT = 0;
static final int REPOS_NEXT = 1;
static final int REPOS_EOF = 2;
private RangeCursor cursor;
private JoinCursor joinCursor;
private DataView view;
private KeyRange range;
private boolean writeAllowed;
private boolean readUncommitted;
private DatabaseEntry keyThang;
private DatabaseEntry valueThang;
private DatabaseEntry primaryKeyThang;
private DatabaseEntry otherThang;
private DataCursor[] indexCursorsToClose;
DataCursor(DataView view, boolean writeAllowed)
throws DatabaseException {
init(view, writeAllowed, null, null);
}
DataCursor(DataView view, boolean writeAllowed, CursorConfig config)
throws DatabaseException {
init(view, writeAllowed, config, null);
}
DataCursor(DataView view, boolean writeAllowed, Object singleKey)
throws DatabaseException {
init(view, writeAllowed, null, view.subRange(view.range, singleKey));
}
DataCursor(DataView view, boolean writeAllowed,
Object beginKey, boolean beginInclusive,
Object endKey, boolean endInclusive)
throws DatabaseException {
init(view, writeAllowed, null,
view.subRange
(view.range, beginKey, beginInclusive, endKey, endInclusive));
}
DataCursor(DataView view, DataCursor[] indexCursors,
JoinConfig joinConfig, boolean closeIndexCursors)
throws DatabaseException {
if (view.isSecondary()) {
throw new IllegalArgumentException(
"The primary collection in a join must not be a secondary " +
"database");
}
Cursor[] cursors = new Cursor[indexCursors.length];
for (int i = 0; i < cursors.length; i += 1) {
cursors[i] = indexCursors[i].cursor.getCursor();
}
joinCursor = view.db.join(cursors, joinConfig);
init(view, false, null, null);
if (closeIndexCursors) {
indexCursorsToClose = indexCursors;
}
}
DataCursor cloneCursor()
throws DatabaseException {
checkNoJoinCursor();
DataCursor o;
try {
o = (DataCursor) super.clone();
} catch (CloneNotSupportedException neverHappens) {
return null;
}
o.initThangs();
KeyRange.copy(keyThang, o.keyThang);
KeyRange.copy(valueThang, o.valueThang);
if (primaryKeyThang != keyThang) {
KeyRange.copy(primaryKeyThang, o.primaryKeyThang);
}
o.cursor = cursor.dup(true);
return o;
}
RangeCursor getCursor() {
return cursor;
}
private void init(DataView view,
boolean writeAllowed,
CursorConfig config,
KeyRange range)
throws DatabaseException {
if (config == null) {
config = view.cursorConfig;
}
this.view = view;
this.writeAllowed = writeAllowed && view.writeAllowed;
this.range = (range != null) ? range : view.range;
readUncommitted = config.getReadUncommitted() ||
view.currentTxn.isReadUncommitted();
initThangs();
if (joinCursor == null) {
cursor = new MyRangeCursor
(this.range, config, view, this.writeAllowed);
}
}
private void initThangs()
throws DatabaseException {
keyThang = new DatabaseEntry();
primaryKeyThang = view.isSecondary() ? (new DatabaseEntry())
: keyThang;
valueThang = new DatabaseEntry();
}
private void setThangs(byte[] keyBytes,
byte[] priKeyBytes,
byte[] valueBytes) {
keyThang.setData(KeyRange.copyBytes(keyBytes));
if (keyThang != primaryKeyThang) {
primaryKeyThang.setData(KeyRange.copyBytes(priKeyBytes));
}
valueThang.setData(KeyRange.copyBytes(valueBytes));
}
void close()
throws DatabaseException {
if (joinCursor != null) {
JoinCursor toClose = joinCursor;
joinCursor = null;
toClose.close();
}
if (cursor != null) {
Cursor toClose = cursor.getCursor();
cursor = null;
view.currentTxn.closeCursor(toClose );
}
if (indexCursorsToClose != null) {
DataCursor[] toClose = indexCursorsToClose;
indexCursorsToClose = null;
for (int i = 0; i < toClose.length; i += 1) {
toClose[i].close();
}
}
}
int repositionRange(byte[] keyBytes,
byte[] priKeyBytes,
byte[] valueBytes,
boolean lockForWrite)
throws DatabaseException {
LockMode lockMode = getLockMode(lockForWrite);
OperationStatus status = null;
setThangs(keyBytes, priKeyBytes, valueBytes);
if (view.dupsAllowed) {
status = cursor.getSearchBothRange(keyThang, primaryKeyThang,
valueThang, lockMode);
}
if (status != OperationStatus.SUCCESS) {
status = cursor.getSearchKeyRange(keyThang, primaryKeyThang,
valueThang, lockMode);
}
if (status == OperationStatus.SUCCESS) {
if (!KeyRange.equalBytes(keyBytes, 0, keyBytes.length,
keyThang.getData(),
keyThang.getOffset(),
keyThang.getSize())) {
return REPOS_NEXT;
}
if (view.dupsAllowed) {
DatabaseEntry thang = view.isSecondary() ? primaryKeyThang
: valueThang;
byte[] bytes = view.isSecondary() ? priKeyBytes
: valueBytes;
if (!KeyRange.equalBytes(bytes, 0, bytes.length,
thang.getData(),
thang.getOffset(),
thang.getSize())) {
return REPOS_NEXT;
}
}
return REPOS_EXACT;
} else {
return REPOS_EOF;
}
}
boolean repositionExact(byte[] keyBytes,
byte[] priKeyBytes,
byte[] valueBytes,
boolean lockForWrite)
throws DatabaseException {
LockMode lockMode = getLockMode(lockForWrite);
OperationStatus status = null;
setThangs(keyBytes, priKeyBytes, valueBytes);
if (view.recNumRenumber) {
status = cursor.getSearchKey(keyThang, primaryKeyThang,
valueThang, lockMode);
} else {
status = cursor.getSearchBoth(keyThang, primaryKeyThang,
valueThang, lockMode);
}
return (status == OperationStatus.SUCCESS);
}
DataView getView() {
return view;
}
KeyRange getRange() {
return range;
}
boolean isWriteAllowed() {
return writeAllowed;
}
Object getCurrentKey()
throws DatabaseException {
return view.makeKey(keyThang, primaryKeyThang);
}
Object getCurrentValue()
throws DatabaseException {
return view.makeValue(primaryKeyThang, valueThang);
}
DatabaseEntry getKeyThang() {
return keyThang;
}
DatabaseEntry getPrimaryKeyThang() {
return primaryKeyThang;
}
DatabaseEntry getValueThang() {
return valueThang;
}
boolean hasRecNumAccess() {
return view.recNumAccess;
}
int getCurrentRecordNumber()
throws DatabaseException {
if (view.btreeRecNumDb) {
if (otherThang == null) {
otherThang = new DatabaseEntry();
}
DbCompat.getCurrentRecordNumber(cursor.getCursor(), otherThang,
getLockMode(false));
return DbCompat.getRecordNumber(otherThang);
} else {
return DbCompat.getRecordNumber(keyThang);
}
}
OperationStatus getCurrent(boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
return cursor.getCurrent(keyThang, primaryKeyThang, valueThang,
getLockMode(lockForWrite));
}
OperationStatus getFirst(boolean lockForWrite)
throws DatabaseException {
LockMode lockMode = getLockMode(lockForWrite);
if (joinCursor != null) {
return joinCursor.getNext(keyThang, valueThang, lockMode);
} else {
return cursor.getFirst(keyThang, primaryKeyThang, valueThang,
lockMode);
}
}
OperationStatus getNext(boolean lockForWrite)
throws DatabaseException {
LockMode lockMode = getLockMode(lockForWrite);
if (joinCursor != null) {
return joinCursor.getNext(keyThang, valueThang, lockMode);
} else {
return cursor.getNext(keyThang, primaryKeyThang, valueThang,
lockMode);
}
}
OperationStatus getNextNoDup(boolean lockForWrite)
throws DatabaseException {
LockMode lockMode = getLockMode(lockForWrite);
if (joinCursor != null) {
return joinCursor.getNext(keyThang, valueThang, lockMode);
} else if (view.dupsView) {
return cursor.getNext
(keyThang, primaryKeyThang, valueThang, lockMode);
} else {
return cursor.getNextNoDup
(keyThang, primaryKeyThang, valueThang, lockMode);
}
}
OperationStatus getNextDup(boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
if (view.dupsView) {
return null;
} else {
return cursor.getNextDup
(keyThang, primaryKeyThang, valueThang,
getLockMode(lockForWrite));
}
}
OperationStatus getLast(boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
return cursor.getLast(keyThang, primaryKeyThang, valueThang,
getLockMode(lockForWrite));
}
OperationStatus getPrev(boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
return cursor.getPrev(keyThang, primaryKeyThang, valueThang,
getLockMode(lockForWrite));
}
OperationStatus getPrevNoDup(boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
LockMode lockMode = getLockMode(lockForWrite);
if (view.dupsView) {
return null;
} else if (view.dupsView) {
return cursor.getPrev
(keyThang, primaryKeyThang, valueThang, lockMode);
} else {
return cursor.getPrevNoDup
(keyThang, primaryKeyThang, valueThang, lockMode);
}
}
OperationStatus getPrevDup(boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
if (view.dupsView) {
return null;
} else {
return cursor.getPrevDup
(keyThang, primaryKeyThang, valueThang,
getLockMode(lockForWrite));
}
}
OperationStatus getSearchKey(Object key, Object value,
boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
if (view.dupsView) {
if (view.useKey(key, value, primaryKeyThang, view.dupsRange)) {
KeyRange.copy(view.dupsKey, keyThang);
return cursor.getSearchBoth
(keyThang, primaryKeyThang, valueThang,
getLockMode(lockForWrite));
}
} else {
if (view.useKey(key, value, keyThang, range)) {
return doGetSearchKey(lockForWrite);
}
}
return OperationStatus.NOTFOUND;
}
private OperationStatus doGetSearchKey(boolean lockForWrite)
throws DatabaseException {
LockMode lockMode = getLockMode(lockForWrite);
if (view.btreeRecNumAccess) {
return cursor.getSearchRecordNumber(keyThang, primaryKeyThang,
valueThang, lockMode);
} else {
return cursor.getSearchKey(keyThang, primaryKeyThang,
valueThang, lockMode);
}
}
OperationStatus getSearchKeyRange(Object key, Object value,
boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
LockMode lockMode = getLockMode(lockForWrite);
if (view.dupsView) {
if (view.useKey(key, value, primaryKeyThang, view.dupsRange)) {
KeyRange.copy(view.dupsKey, keyThang);
return cursor.getSearchBothRange
(keyThang, primaryKeyThang, valueThang, lockMode);
}
} else {
if (view.useKey(key, value, keyThang, range)) {
return cursor.getSearchKeyRange
(keyThang, primaryKeyThang, valueThang, lockMode);
}
}
return OperationStatus.NOTFOUND;
}
OperationStatus findBoth(Object key, Object value, boolean lockForWrite)
throws DatabaseException {
checkNoJoinCursor();
LockMode lockMode = getLockMode(lockForWrite);
view.useValue(value, valueThang, null);
if (view.dupsView) {
if (view.useKey(key, value, primaryKeyThang, view.dupsRange)) {
KeyRange.copy(view.dupsKey, keyThang);
if (otherThang == null) {
otherThang = new DatabaseEntry();
}
OperationStatus status = cursor.getSearchBoth
(keyThang, primaryKeyThang, otherThang, lockMode);
if (status == OperationStatus.SUCCESS &&
KeyRange.equalBytes(otherThang, valueThang)) {
return status;
}
}
} else if (view.useKey(key, value, keyThang, range)) {
if (view.isSecondary()) {
if (otherThang == null) {
otherThang = new DatabaseEntry();
}
OperationStatus status = cursor.getSearchKey(keyThang,
primaryKeyThang,
otherThang,
lockMode);
while (status == OperationStatus.SUCCESS) {
if (KeyRange.equalBytes(otherThang, valueThang)) {
return status;
}
status = cursor.getNextDup(keyThang, primaryKeyThang,
otherThang, lockMode);
}
} else {
return cursor.getSearchBoth(keyThang, null, valueThang,
lockMode);
}
}
return OperationStatus.NOTFOUND;
}
OperationStatus findValue(Object value, boolean findFirst)
throws DatabaseException {
checkNoJoinCursor();
if (view.entityBinding != null && !view.isSecondary() &&
(findFirst || !view.dupsAllowed)) {
return findBoth(null, value, false);
} else {
if (otherThang == null) {
otherThang = new DatabaseEntry();
}
view.useValue(value, otherThang, null);
OperationStatus status = findFirst ? getFirst(false)
: getLast(false);
while (status == OperationStatus.SUCCESS) {
if (KeyRange.equalBytes(valueThang, otherThang)) {
break;
}
status = findFirst ? getNext(false) : getPrev(false);
}
return status;
}
}
int count()
throws DatabaseException {
checkNoJoinCursor();
if (view.dupsView) {
return 1;
} else {
return cursor.count();
}
}
OperationStatus putCurrent(Object value)
throws DatabaseException {
checkWriteAllowed(false);
view.useValue(value, valueThang, keyThang);
boolean hashWorkaround = (view.dupsOrdered && !view.ordered);
if (hashWorkaround) {
if (otherThang == null) {
otherThang = new DatabaseEntry();
}
cursor.getCurrent(keyThang, primaryKeyThang, otherThang,
LockMode.DEFAULT);
if (KeyRange.equalBytes(valueThang, otherThang)) {
return OperationStatus.SUCCESS;
} else {
throw new IllegalArgumentException(
"Current data differs from put data with sorted duplicates");
}
}
return cursor.putCurrent(valueThang);
}
OperationStatus putAfter(Object value)
throws DatabaseException {
checkWriteAllowed(false);
view.useValue(value, valueThang, null);
return cursor.putAfter(keyThang, valueThang);
}
OperationStatus putBefore(Object value)
throws DatabaseException {
checkWriteAllowed(false);
view.useValue(value, valueThang, keyThang);
return cursor.putBefore(keyThang, valueThang);
}
OperationStatus put(Object key, Object value, Object[] oldValue,
boolean useCurrentKey)
throws DatabaseException {
initForPut(key, value, oldValue, useCurrentKey);
return cursor.put(keyThang, valueThang);
}
OperationStatus putNoOverwrite(Object key, Object value,
boolean useCurrentKey)
throws DatabaseException {
initForPut(key, value, null, useCurrentKey);
return cursor.putNoOverwrite(keyThang, valueThang);
}
OperationStatus putNoDupData(Object key, Object value, Object[] oldValue,
boolean useCurrentKey)
throws DatabaseException {
initForPut(key, value, oldValue, useCurrentKey);
if (view.dupsOrdered) {
return cursor.putNoDupData(keyThang, valueThang);
} else {
if (view.dupsAllowed) {
OperationStatus status =
cursor.getSearchBoth(keyThang, primaryKeyThang,
valueThang,
getLockMode(false));
if (status == OperationStatus.SUCCESS) {
return OperationStatus.KEYEXIST;
} else {
return cursor.put(keyThang, valueThang);
}
} else {
return cursor.putNoOverwrite(keyThang, valueThang);
}
}
}
private void initForPut(Object key, Object value, Object[] oldValue,
boolean useCurrentKey)
throws DatabaseException {
checkWriteAllowed(false);
if (!useCurrentKey && !view.useKey(key, value, keyThang, range)) {
throw new IllegalArgumentException("key out of range");
}
if (oldValue != null) {
oldValue[0] = null;
if (!view.dupsAllowed) {
OperationStatus status = doGetSearchKey(true);
if (status == OperationStatus.SUCCESS) {
oldValue[0] = getCurrentValue();
}
}
}
view.useValue(value, valueThang, keyThang);
}
void useRangeKey() {
if (!range.isSingleKey()) {
throw new IllegalStateException();
}
KeyRange.copy(range.getSingleKey(), keyThang);
}
OperationStatus delete()
throws DatabaseException {
checkWriteAllowed(true);
return cursor.delete();
}
LockMode getLockMode(boolean lockForWrite) {
if (readUncommitted) {
return LockMode.READ_UNCOMMITTED;
} else if (lockForWrite) {
return view.currentTxn.getWriteLockMode();
} else {
return LockMode.DEFAULT;
}
}
private void checkNoJoinCursor() {
if (joinCursor != null) {
throw new UnsupportedOperationException
("Not allowed with a join cursor");
}
}
private void checkWriteAllowed(boolean allowSecondary) {
checkNoJoinCursor();
if (!writeAllowed || (!allowSecondary && view.isSecondary())) {
throw new UnsupportedOperationException
("Writing is not allowed");
}
}
}