package com.sleepycat.bdb;
import com.sleepycat.bdb.bind.DataBinding;
import com.sleepycat.bdb.bind.DataFormat;
import com.sleepycat.bdb.bind.EntityBinding;
import com.sleepycat.bdb.bind.KeyExtractor;
import com.sleepycat.db.Db;
import com.sleepycat.db.Dbc;
import com.sleepycat.db.DbEnv;
import com.sleepycat.db.DbException;
import java.io.IOException;
import java.util.Collection;
public final class DataView implements Cloneable {
private static final KeyRange NULL_RANGE = new KeyRange();
DataDb db;
DataStore store;
DataIndex index;
KeyRange range;
boolean writeAllowed;
boolean dirtyRead;
boolean transactional;
boolean dirtyReadAllowed;
boolean autoCommit;
DataBinding keyBinding;
DataBinding valueBinding;
EntityBinding entityBinding;
boolean recNumAccess;
boolean btreeRecNumAccess;
public DataView(DataStore store, DataIndex index,
DataBinding keyBinding, DataBinding valueBinding,
EntityBinding entityBinding, boolean writeAllowed)
throws IllegalArgumentException {
if (index != null) {
this.db = index.db;
this.index = index;
this.store = index.store;
} else {
if (store == null)
throw new IllegalArgumentException(
"both store and index are null");
this.db = store.db;
this.store = store;
}
this.writeAllowed = writeAllowed;
this.range = NULL_RANGE;
this.keyBinding = keyBinding;
this.valueBinding = valueBinding;
this.entityBinding = entityBinding;
this.transactional = db.isTransactional();
this.dirtyReadAllowed = transactional &&
(store == null || store.db.isDirtyReadAllowed()) &&
(index == null || index.db.isDirtyReadAllowed());
if (valueBinding != null && entityBinding != null)
throw new IllegalArgumentException(
"both valueBinding and entityBinding are non-null");
if (keyBinding != null &&
keyBinding.getDataFormat() instanceof RecordNumberFormat) {
if (!db.hasRecNumAccess()) {
throw new IllegalArgumentException(
"RecordNumberFormat requires DB_BTREE/DB_RECNUM, " +
"DB_RECNO, or DB_QUEUE");
}
recNumAccess = true;
if (db.type == Db.DB_BTREE) {
btreeRecNumAccess = true;
}
}
checkBindingFormats();
}
private DataView cloneView() {
try {
return (DataView) super.clone();
}
catch (CloneNotSupportedException willNeverOccur) { return null; }
}
public DataView keySetView() {
if (keyBinding == null) {
throw new UnsupportedOperationException("must have keyBinding");
}
DataView view = cloneView();
view.valueBinding = null;
view.entityBinding = null;
return view;
}
public DataView valueSetView() {
if (valueBinding == null && entityBinding == null) {
throw new UnsupportedOperationException(
"must have valueBinding or entityBinding");
}
DataView view = cloneView();
view.keyBinding = null;
return view;
}
public DataView valueSetView(Object singleKey)
throws DbException, IOException, KeyRangeException {
KeyRange singleKeyRange = subRange(singleKey);
DataView view = valueSetView();
view.range = singleKeyRange;
return view;
}
public DataView subView(Object beginKey, boolean beginInclusive,
Object endKey, boolean endInclusive,
DataBinding keyBinding)
throws DbException, IOException, KeyRangeException {
DataView view = cloneView();
view.setRange(beginKey, beginInclusive, endKey, endInclusive);
if (keyBinding != null) view.keyBinding = keyBinding;
return view;
}
public DataView dirtyReadView(boolean enable) {
if (!isDirtyReadAllowed())
return this;
DataView view = cloneView();
view.dirtyRead = enable;
return view;
}
public DataView autoCommitView(boolean enable) {
if (!isTransactional())
return this;
DataView view = cloneView();
view.autoCommit = enable;
return view;
}
public CurrentTransaction getCurrentTxn() {
return isTransactional() ? db.env : null;
}
private void setRange(Object beginKey, boolean beginInclusive,
Object endKey, boolean endInclusive)
throws DbException, IOException, KeyRangeException {
range = subRange(beginKey, beginInclusive, endKey, endInclusive);
}
public DataThang getSingleKeyThang() {
return range.getSingleKey();
}
public DataDb getDb() {
return db;
}
public final DbEnv getEnv() {
return db.env.getEnv();
}
public final boolean isAutoCommit() {
return autoCommit || db.env.isAutoCommit();
}
public final DataStore getStore() {
return store;
}
public final DataIndex getIndex() {
return index;
}
public final DataBinding getKeyBinding() {
return keyBinding;
}
public final DataBinding getValueBinding() {
return valueBinding;
}
public final EntityBinding getValueEntityBinding() {
return entityBinding;
}
public final boolean areDuplicatesAllowed() {
return db.areDuplicatesAllowed();
}
public final boolean areDuplicatesOrdered() {
return db.areDuplicatesOrdered();
}
public final boolean areKeysRenumbered() {
return btreeRecNumAccess || db.areKeysRenumbered();
}
public final boolean isOrdered() {
return db.isOrdered();
}
public final boolean isWriteAllowed() {
return writeAllowed;
}
public final boolean isDirtyReadAllowed() {
return dirtyReadAllowed;
}
public final boolean isDirtyReadEnabled() {
return dirtyRead;
}
public final boolean isTransactional() {
return transactional;
}
public boolean isEmpty()
throws DbException, IOException {
Dbc cursor = db.openCursor(false);
try {
DataThang val = DataThang.getDiscardDataThang();
DataThang key = (range.hasBound()) ? (new DataThang()) : val;
int flags = Db.DB_FIRST;
if (dirtyRead) flags |= Db.DB_DIRTY_READ;
int err = range.get(db, cursor, key, val, flags);
return (err != 0 && err != DataDb.ENOMEM);
} finally {
db.closeCursor(cursor);
}
}
public int get(Object key, Object value, int flags, boolean lockForWrite,
Object[] retValue)
throws DbException, IOException {
if (flags == 0) {
flags = Db.DB_SET;
} else if (flags != Db.DB_SET && flags != Db.DB_GET_BOTH) {
throw new IllegalArgumentException("flag not allowed");
}
DataCursor cursor = null;
try {
cursor = new DataCursor(this, false);
int err = cursor.get(key, value, flags, lockForWrite);
if (err == 0 && retValue != null) {
retValue[0] = cursor.getCurrentValue();
}
return err;
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public int consume(int flags, Object[] retPrimaryKey, Object[] retValue)
throws DbException, IOException {
if (!writeAllowed) {
throw new UnsupportedOperationException("write not allowed");
}
if (flags != Db.DB_CONSUME && flags != Db.DB_CONSUME_WAIT) {
throw new IllegalArgumentException("flag not allowed");
}
DataThang keyThang = new DataThang();
DataThang valueThang = new DataThang();
int err = store.db.get(keyThang, valueThang, flags);
if (err == 0) {
store.applyChange(keyThang, valueThang, null);
returnPrimaryKeyAndValue(keyThang, valueThang,
retPrimaryKey, retValue);
}
return err;
}
public int put(Object primaryKey, Object value, int flags,
Object[] oldValue)
throws DbException, IOException {
if (!writeAllowed) {
throw new UnsupportedOperationException("write not allowed");
}
if (flags != 0 && flags != Db.DB_NOOVERWRITE &&
flags != Db.DB_NODUPDATA) {
throw new IllegalArgumentException("flags not allowed: " + flags);
}
if (index != null) {
throw new UnsupportedOperationException("cannot put() with index");
}
if (oldValue != null) {
oldValue[0] = null;
}
DataThang keyThang = new DataThang();
DataThang valueThang = new DataThang();
DataThang oldValueThang = null;
int err = useKey(primaryKey, value, keyThang, range);
if (err != 0) {
throw new IllegalArgumentException("primaryKey out of range " +
keyThang + range);
}
if (flags == 0 && !areDuplicatesAllowed()) {
oldValueThang = new DataThang();
err = store.db.get(keyThang, oldValueThang,
db.env.getWriteLockFlag());
if (err == 0) {
if (oldValue != null) {
oldValue[0] = makeValue(keyThang, oldValueThang);
}
} else {
oldValueThang = null;
}
}
useValue(value, valueThang, null);
err = store.db.put(keyThang, valueThang, flags);
if (err == 0) {
store.applyChange(keyThang, oldValueThang, valueThang);
}
return err;
}
public int addValue(DataThang primaryKeyThang, Object value, int flags)
throws DbException, IOException {
if (!writeAllowed) {
throw new UnsupportedOperationException("write not allowed");
}
if (!areDuplicatesAllowed()) {
throw new UnsupportedOperationException("duplicates required");
}
if (flags != 0 && flags != Db.DB_NODUPDATA &&
flags != Db.DB_KEYFIRST && flags != Db.DB_KEYLAST) {
throw new IllegalArgumentException("flags not allowed: " + flags);
}
DataThang valueThang = new DataThang();
if (!range.check(primaryKeyThang)) {
throw new IllegalArgumentException("primaryKey out of range");
}
useValue(value, valueThang, null);
int err = store.db.put(primaryKeyThang, valueThang, flags);
if (err == 0) {
store.applyChange(primaryKeyThang, null, valueThang);
}
return err;
}
public int append(Object value, Object[] retPrimaryKey, Object[] retValue)
throws DbException, IOException {
if (!writeAllowed) {
throw new UnsupportedOperationException("write not allowed");
}
DataThang keyThang = new DataThang();
DataThang valueThang = new DataThang();
int flags;
if (store.keyAssigner != null) {
store.keyAssigner.assignKey(keyThang);
if (!range.check(keyThang)) {
throw new IllegalArgumentException(
"assigned key out of range");
}
flags = Db.DB_NOOVERWRITE;
} else {
if (db.type != Db.DB_QUEUE && db.type != Db.DB_RECNO) {
throw new UnsupportedOperationException(
"DB_QUEUE or DB_RECNO type is required");
}
flags = Db.DB_APPEND; }
useValue(value, valueThang, null);
int err = store.db.put(keyThang, valueThang, flags);
if (err == 0) {
if (store.keyAssigner == null && !range.check(keyThang)) {
store.db.delete(keyThang, 0);
throw new IllegalArgumentException(
"appended record number out of range");
}
store.applyChange(keyThang, null, valueThang);
returnPrimaryKeyAndValue(keyThang, valueThang,
retPrimaryKey, retValue);
}
return err;
}
public void clear(Collection oldValues)
throws DbException, IOException {
DataCursor cursor = null;
try {
cursor = new DataCursor(this, true);
int op = areKeysRenumbered() ? Db.DB_FIRST : Db.DB_NEXT;
int err = 0;
while (err == 0) {
err = cursor.get(null, null, op, true);
if (err == 0) {
if (oldValues != null) {
oldValues.add(cursor.getCurrentValue());
}
err = cursor.delete();
if (err != 0)
throw new DbException(
"Unexpected error on delete", err);
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public DataCursor join(DataView[] indexViews, Object[] indexKeys,
boolean presorted)
throws DbException, IOException {
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].get(indexKeys[i], null, Db.DB_SET, false);
}
joinCursor = new DataCursor(this, indexCursors, presorted, 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) {}
}
}
}
}
}
public DataCursor join(DataCursor[] indexCursors, boolean presorted)
throws DbException, IOException {
return new DataCursor(this, indexCursors, presorted, false);
}
private void returnPrimaryKeyAndValue(DataThang keyThang,
DataThang valueThang,
Object[] retPrimaryKey,
Object[] retValue)
throws DbException, IOException {
if (retPrimaryKey != null) {
if (keyBinding == null) {
throw new IllegalArgumentException(
"returning key requires primary key binding");
} else if (index != null) {
throw new IllegalArgumentException(
"returning key requires unindexed view");
} else {
retPrimaryKey[0] = keyBinding.dataToObject(keyThang);
}
}
if (retValue != null) {
retValue[0] = makeValue(keyThang, valueThang);
}
}
int useKey(Object key, Object value, DataThang keyThang,
KeyRange checkRange)
throws DbException, IOException {
if (key != null) {
if (keyBinding == null) {
throw new IllegalArgumentException(
"non-null key with null key binding");
}
keyBinding.objectToData(key, keyThang);
} else if (value == null) {
throw new IllegalArgumentException(
"null key and null value");
} else if (index == null) {
if (entityBinding == null) {
throw new UnsupportedOperationException(
"null key, null index, and null entity binding");
}
entityBinding.objectToKey(value, keyThang);
} else {
KeyExtractor extractor = index.getKeyExtractor();
DataThang primaryKeyThang = null;
DataThang valueThang = null;
if (entityBinding != null) {
if (extractor.getPrimaryKeyFormat() != null) {
primaryKeyThang = new DataThang();
entityBinding.objectToKey(value, primaryKeyThang);
}
if (extractor.getValueFormat() != null) {
valueThang = new DataThang();
entityBinding.objectToValue(value, valueThang);
}
} else {
if (extractor.getPrimaryKeyFormat() != null) {
throw new IllegalStateException(
"primary key needed by index extractor");
}
if (extractor.getValueFormat() != null) {
valueThang = new DataThang();
valueBinding.objectToData(value, valueThang);
}
}
extractor.extractIndexKey(primaryKeyThang, valueThang, keyThang);
}
if (checkRange != null) {
return checkRange.check(keyThang) ? 0 : Db.DB_NOTFOUND;
} else {
return 0;
}
}
public boolean canDeriveKeyFromValue() {
if (index == null) {
return (entityBinding != null);
} else {
KeyExtractor extractor = index.getKeyExtractor();
if (extractor.getPrimaryKeyFormat() != null &&
entityBinding == null) {
return false;
} else if (extractor.getValueFormat() != null &&
entityBinding == null && valueBinding == null) {
return false;
} else {
return true;
}
}
}
void useValue(Object value, DataThang valueThang, DataThang checkKeyThang)
throws DbException, IOException {
if (value != null) {
if (valueBinding != null) {
valueBinding.objectToData(value, valueThang);
} else if (entityBinding != null) {
entityBinding.objectToValue(value, valueThang);
if (checkKeyThang != null) {
DataThang thang = new DataThang();
entityBinding.objectToKey(value, thang);
if (!thang.equals(checkKeyThang)) {
throw new IllegalArgumentException(
"cannot change primary key");
}
}
} else {
throw new IllegalArgumentException(
"non-null value with null value/entity binding");
}
} else {
valueThang.set_data(new byte[0]);
valueThang.set_offset(0);
valueThang.set_size(0);
}
}
Object makeKey(DataThang keyThang)
throws DbException, IOException {
if (keyThang.get_size() == 0) return null;
return keyBinding.dataToObject(keyThang);
}
Object makeValue(DataThang primaryKeyThang, DataThang valueThang)
throws DbException, IOException {
Object value;
if (valueBinding != null) {
value = valueBinding.dataToObject(valueThang);
} else if (entityBinding != null) {
value = entityBinding.dataToObject(primaryKeyThang,
valueThang);
} else {
throw new UnsupportedOperationException(
"requires valueBinding or entityBinding");
}
return value;
}
KeyRange subRange(Object singleKey)
throws DbException, IOException, KeyRangeException {
return range.subRange(makeRangeKey(singleKey));
}
KeyRange subRange(Object beginKey, boolean beginInclusive,
Object endKey, boolean endInclusive)
throws DbException, IOException, KeyRangeException {
if (beginKey == endKey && beginInclusive && endInclusive) {
return subRange(beginKey);
}
if (!isOrdered()) {
throw new UnsupportedOperationException(
"Cannot use key ranges on an unsorted database");
}
DataThang beginThang =
(beginKey != null) ? makeRangeKey(beginKey) : null;
DataThang endThang =
(endKey != null) ? makeRangeKey(endKey) : null;
return range.subRange(beginThang, beginInclusive,
endThang, endInclusive);
}
private void checkBindingFormats() {
if (keyBinding != null && !recNumAccess) {
DataFormat keyFormat = (index != null) ? index.keyFormat
: store.keyFormat;
if (!keyFormat.equals(keyBinding.getDataFormat())) {
throw new IllegalArgumentException(
db.toString() + " key binding format mismatch");
}
}
if (valueBinding != null) {
if (!store.valueFormat.equals(valueBinding.getDataFormat())) {
throw new IllegalArgumentException(
store.toString() + " value binding format mismatch");
}
}
if (entityBinding != null) {
if (!store.keyFormat.equals(entityBinding.getKeyFormat())) {
throw new IllegalArgumentException(store.toString() +
" value entity binding keyFormat mismatch");
}
if (!store.valueFormat.equals(entityBinding.getValueFormat())) {
throw new IllegalArgumentException(store.toString() +
" value entity binding valueFormat mismatch");
}
}
}
private DataThang makeRangeKey(Object key)
throws DbException, IOException {
DataThang thang = new DataThang();
if (keyBinding != null) {
useKey(key, null, thang, null);
} else {
useKey(null, key, thang, null);
}
return thang;
}
}