package com.sleepycat.collections;
import com.sleepycat.bind.EntityBinding;
import com.sleepycat.bind.EntryBinding;
import com.sleepycat.compat.DbCompat;
import com.sleepycat.db.CursorConfig;
import com.sleepycat.db.Database;
import com.sleepycat.db.DatabaseConfig;
import com.sleepycat.db.DatabaseEntry;
import com.sleepycat.db.DatabaseException;
import com.sleepycat.db.Environment;
import com.sleepycat.db.JoinConfig;
import com.sleepycat.db.OperationStatus;
import com.sleepycat.db.SecondaryConfig;
import com.sleepycat.db.SecondaryDatabase;
import com.sleepycat.db.SecondaryKeyCreator;
import com.sleepycat.db.Transaction;
import com.sleepycat.util.RuntimeExceptionWrapper;
import com.sleepycat.util.keyrange.KeyRange;
import com.sleepycat.util.keyrange.KeyRangeException;
final class DataView implements Cloneable {
Database db;
SecondaryDatabase secDb;
CurrentTransaction currentTxn;
KeyRange range;
EntryBinding keyBinding;
EntryBinding valueBinding;
EntityBinding entityBinding;
PrimaryKeyAssigner keyAssigner;
SecondaryKeyCreator secKeyCreator;
CursorConfig cursorConfig; boolean writeAllowed; boolean ordered; boolean keyRangesAllowed; boolean recNumAllowed; boolean recNumAccess; boolean btreeRecNumDb; boolean btreeRecNumAccess; boolean recNumRenumber; boolean keysRenumbered; boolean dupsAllowed; boolean dupsOrdered; boolean transactional; boolean readUncommittedAllowed;
DatabaseEntry dupsKey;
boolean dupsView;
KeyRange dupsRange;
DataView(Database database, EntryBinding keyBinding,
EntryBinding valueBinding, EntityBinding entityBinding,
boolean writeAllowed, PrimaryKeyAssigner keyAssigner)
throws IllegalArgumentException {
if (database == null) {
throw new IllegalArgumentException("database is null");
}
db = database;
try {
currentTxn =
CurrentTransaction.getInstanceInternal(db.getEnvironment());
DatabaseConfig dbConfig;
if (db instanceof SecondaryDatabase) {
secDb = (SecondaryDatabase) database;
SecondaryConfig secConfig = secDb.getSecondaryConfig();
secKeyCreator = secConfig.getKeyCreator();
dbConfig = secConfig;
} else {
dbConfig = db.getConfig();
}
ordered = !DbCompat.isTypeHash(dbConfig);
keyRangesAllowed = DbCompat.isTypeBtree(dbConfig);
recNumAllowed = DbCompat.isTypeQueue(dbConfig) ||
DbCompat.isTypeRecno(dbConfig) ||
DbCompat.getBtreeRecordNumbers(dbConfig);
recNumRenumber = DbCompat.getRenumbering(dbConfig);
dupsAllowed = DbCompat.getSortedDuplicates(dbConfig) ||
DbCompat.getUnsortedDuplicates(dbConfig);
dupsOrdered = DbCompat.getSortedDuplicates(dbConfig);
transactional = currentTxn.isTxnMode() &&
dbConfig.getTransactional();
readUncommittedAllowed = DbCompat.getReadUncommitted(dbConfig);
btreeRecNumDb = recNumAllowed && DbCompat.isTypeBtree(dbConfig);
range = new KeyRange(dbConfig.getBtreeComparator());
} catch (DatabaseException e) {
throw new RuntimeExceptionWrapper(e);
}
this.writeAllowed = writeAllowed;
this.keyBinding = keyBinding;
this.valueBinding = valueBinding;
this.entityBinding = entityBinding;
this.keyAssigner = keyAssigner;
cursorConfig = CursorConfig.DEFAULT;
if (valueBinding != null && entityBinding != null)
throw new IllegalArgumentException(
"both valueBinding and entityBinding are non-null");
if (keyBinding instanceof com.sleepycat.bind.RecordNumberBinding) {
if (!recNumAllowed) {
throw new IllegalArgumentException(
"RecordNumberBinding requires DB_BTREE/DB_RECNUM, " +
"DB_RECNO, or DB_QUEUE");
}
recNumAccess = true;
if (btreeRecNumDb) {
btreeRecNumAccess = true;
}
}
keysRenumbered = recNumRenumber || btreeRecNumAccess;
}
private DataView cloneView() {
try {
return (DataView) super.clone();
} catch (CloneNotSupportedException willNeverOccur) {
throw new IllegalStateException();
}
}
DataView keySetView() {
if (keyBinding == null) {
throw new UnsupportedOperationException("must have keyBinding");
}
DataView view = cloneView();
view.valueBinding = null;
view.entityBinding = null;
return view;
}
DataView valueSetView() {
if (valueBinding == null && entityBinding == null) {
throw new UnsupportedOperationException(
"must have valueBinding or entityBinding");
}
DataView view = cloneView();
view.keyBinding = null;
return view;
}
DataView valueSetView(Object singleKey)
throws DatabaseException, KeyRangeException {
KeyRange singleKeyRange = subRange(range, singleKey);
DataView view = valueSetView();
view.range = singleKeyRange;
return view;
}
DataView subView(Object beginKey, boolean beginInclusive,
Object endKey, boolean endInclusive,
EntryBinding keyBinding)
throws DatabaseException, KeyRangeException {
DataView view = cloneView();
view.setRange(beginKey, beginInclusive, endKey, endInclusive);
if (keyBinding != null) view.keyBinding = keyBinding;
return view;
}
DataView duplicatesView(Object secondaryKey,
EntryBinding primaryKeyBinding)
throws DatabaseException, KeyRangeException {
if (!isSecondary()) {
throw new UnsupportedOperationException
("Only allowed for maps on secondary databases");
}
if (dupsView) {
throw new IllegalStateException();
}
DataView view = cloneView();
view.range = subRange(view.range, secondaryKey);
view.dupsKey = view.range.getSingleKey();
view.dupsView = true;
view.keyBinding = primaryKeyBinding;
return view;
}
DataView configuredView(CursorConfig config) {
DataView view = cloneView();
view.cursorConfig = (config != null) ?
DbCompat.cloneCursorConfig(config) : CursorConfig.DEFAULT;
return view;
}
CurrentTransaction getCurrentTxn() {
return transactional ? currentTxn : null;
}
private void setRange(Object beginKey, boolean beginInclusive,
Object endKey, boolean endInclusive)
throws DatabaseException, KeyRangeException {
if ((beginKey != null || endKey != null) && !keyRangesAllowed) {
throw new UnsupportedOperationException
("Key ranges allowed only for BTREE databases");
}
KeyRange useRange = useSubRange();
useRange = subRange
(useRange, beginKey, beginInclusive, endKey, endInclusive);
if (dupsView) {
dupsRange = useRange;
} else {
range = useRange;
}
}
DatabaseEntry getSingleKeyThang() {
return range.getSingleKey();
}
final Environment getEnv() {
return currentTxn.getEnvironment();
}
final boolean isSecondary() {
return (secDb != null);
}
boolean isEmpty()
throws DatabaseException {
DataCursor cursor = new DataCursor(this, false);
try {
return cursor.getFirst(false) != OperationStatus.SUCCESS;
} finally {
cursor.close();
}
}
OperationStatus append(Object value, Object[] retPrimaryKey,
Object[] retValue)
throws DatabaseException {
DatabaseEntry keyThang = new DatabaseEntry();
DatabaseEntry valueThang = new DatabaseEntry();
useValue(value, valueThang, null);
OperationStatus status;
if (keyAssigner != null) {
keyAssigner.assignKey(keyThang);
if (!range.check(keyThang)) {
throw new IllegalArgumentException(
"assigned key out of range");
}
DataCursor cursor = new DataCursor(this, true);
try {
status = cursor.getCursor().putNoOverwrite(keyThang,
valueThang);
} finally {
cursor.close();
}
} else {
if (currentTxn.isCDBCursorOpen(db)) {
throw new IllegalStateException(
"cannot open CDB write cursor when read cursor is open");
}
status = DbCompat.append(db, useTransaction(),
keyThang, valueThang);
if (status == OperationStatus.SUCCESS && !range.check(keyThang)) {
db.delete(useTransaction(), keyThang);
throw new IllegalArgumentException(
"appended record number out of range");
}
}
if (status == OperationStatus.SUCCESS) {
returnPrimaryKeyAndValue(keyThang, valueThang,
retPrimaryKey, retValue);
}
return status;
}
Transaction useTransaction() {
return transactional ? currentTxn.getTransaction() : null;
}
void clear()
throws DatabaseException {
DataCursor cursor = new DataCursor(this, true);
try {
OperationStatus status = OperationStatus.SUCCESS;
while (status == OperationStatus.SUCCESS) {
if (keysRenumbered) {
status = cursor.getFirst(true);
} else {
status = cursor.getNext(true);
}
if (status == OperationStatus.SUCCESS) {
cursor.delete();
}
}
} finally {
cursor.close();
}
}
DataCursor join(DataView[] indexViews, Object[] indexKeys,
JoinConfig joinConfig)
throws DatabaseException {
DataCursor joinCursor = null;
DataCursor[] indexCursors = new DataCursor[indexViews.length];
try {
for (int i = 0; i < indexViews.length; i += 1) {
indexCursors[i] = new DataCursor(indexViews[i], false);
indexCursors[i].getSearchKey(indexKeys[i], null, false);
}
joinCursor = new DataCursor(this, indexCursors, joinConfig, true);
return joinCursor;
} finally {
if (joinCursor == null) {
for (int i = 0; i < indexCursors.length; i += 1) {
if (indexCursors[i] != null) {
try { indexCursors[i].close(); }
catch (Exception e) {
}
}
}
}
}
}
DataCursor join(DataCursor[] indexCursors, JoinConfig joinConfig)
throws DatabaseException {
return new DataCursor(this, indexCursors, joinConfig, false);
}
private void returnPrimaryKeyAndValue(DatabaseEntry keyThang,
DatabaseEntry valueThang,
Object[] retPrimaryKey,
Object[] retValue)
throws DatabaseException {
if (retPrimaryKey != null) {
if (keyBinding == null) {
throw new IllegalArgumentException(
"returning key requires primary key binding");
} else if (isSecondary()) {
throw new IllegalArgumentException(
"returning key requires unindexed view");
} else {
retPrimaryKey[0] = keyBinding.entryToObject(keyThang);
}
}
if (retValue != null) {
retValue[0] = makeValue(keyThang, valueThang);
}
}
boolean useKey(Object key, Object value, DatabaseEntry keyThang,
KeyRange checkRange)
throws DatabaseException {
if (key != null) {
if (keyBinding == null) {
throw new IllegalArgumentException(
"non-null key with null key binding");
}
keyBinding.objectToEntry(key, keyThang);
} else {
if (value == null) {
throw new IllegalArgumentException(
"null key and null value");
}
if (entityBinding == null) {
throw new IllegalStateException(
"EntityBinding required to derive key from value");
}
if (!dupsView && isSecondary()) {
DatabaseEntry primaryKeyThang = new DatabaseEntry();
entityBinding.objectToKey(value, primaryKeyThang);
DatabaseEntry valueThang = new DatabaseEntry();
entityBinding.objectToData(value, valueThang);
secKeyCreator.createSecondaryKey(secDb, primaryKeyThang,
valueThang, keyThang);
} else {
entityBinding.objectToKey(value, keyThang);
}
}
if (recNumAccess && DbCompat.getRecordNumber(keyThang) <= 0) {
return false;
}
if (checkRange != null && !checkRange.check(keyThang)) {
return false;
}
return true;
}
final boolean canDeriveKeyFromValue() {
return (entityBinding != null);
}
void useValue(Object value, DatabaseEntry valueThang,
DatabaseEntry checkKeyThang)
throws DatabaseException {
if (value != null) {
if (valueBinding != null) {
valueBinding.objectToEntry(value, valueThang);
} else if (entityBinding != null) {
entityBinding.objectToData(value, valueThang);
if (checkKeyThang != null) {
DatabaseEntry thang = new DatabaseEntry();
entityBinding.objectToKey(value, thang);
if (!KeyRange.equalBytes(thang, checkKeyThang)) {
throw new IllegalArgumentException(
"cannot change primary key");
}
}
} else {
throw new IllegalArgumentException(
"non-null value with null value/entity binding");
}
} else {
valueThang.setData(KeyRange.ZERO_LENGTH_BYTE_ARRAY);
valueThang.setOffset(0);
valueThang.setSize(0);
}
}
Object makeKey(DatabaseEntry keyThang, DatabaseEntry priKeyThang) {
if (keyBinding == null) {
throw new UnsupportedOperationException();
} else {
DatabaseEntry thang = dupsView ? priKeyThang : keyThang;
if (thang.getSize() == 0) {
return null;
} else {
return keyBinding.entryToObject(thang);
}
}
}
Object makeValue(DatabaseEntry primaryKeyThang, DatabaseEntry valueThang) {
Object value;
if (valueBinding != null) {
value = valueBinding.entryToObject(valueThang);
} else if (entityBinding != null) {
value = entityBinding.entryToObject(primaryKeyThang,
valueThang);
} else {
throw new UnsupportedOperationException(
"requires valueBinding or entityBinding");
}
return value;
}
KeyRange subRange(KeyRange useRange, Object singleKey)
throws DatabaseException, KeyRangeException {
return useRange.subRange(makeRangeKey(singleKey));
}
KeyRange subRange(KeyRange useRange,
Object beginKey, boolean beginInclusive,
Object endKey, boolean endInclusive)
throws DatabaseException, KeyRangeException {
if (beginKey == endKey && beginInclusive && endInclusive) {
return subRange(useRange, beginKey);
}
if (!ordered) {
throw new UnsupportedOperationException(
"Cannot use key ranges on an unsorted database");
}
DatabaseEntry beginThang =
(beginKey != null) ? makeRangeKey(beginKey) : null;
DatabaseEntry endThang =
(endKey != null) ? makeRangeKey(endKey) : null;
return useRange.subRange(beginThang, beginInclusive,
endThang, endInclusive);
}
KeyRange useSubRange()
throws DatabaseException {
if (dupsView) {
synchronized (this) {
if (dupsRange == null) {
DatabaseConfig config =
secDb.getPrimaryDatabase().getConfig();
dupsRange = new KeyRange(config.getBtreeComparator());
}
}
return dupsRange;
} else {
return range;
}
}
private DatabaseEntry makeRangeKey(Object key)
throws DatabaseException {
DatabaseEntry thang = new DatabaseEntry();
if (keyBinding != null) {
useKey(key, null, thang, null);
} else {
useKey(null, key, thang, null);
}
return thang;
}
}