package com.sleepycat.je;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sleepycat.je.log.LogUtils;
import com.sleepycat.je.txn.Locker;
import com.sleepycat.je.txn.LockerFactory;
import de.ovgu.cide.jakutil.*;
/**
* Javadoc for this public class is generated via
* the doc templates in the doc_src directory.
*/
public class Sequence {
private static final byte FLAG_INCR=((byte)0x1);
private static final byte FLAG_WRAP=((byte)0x2);
private static final byte FLAG_OVER=((byte)0x4);
private static final int MAX_DATA_SIZE=50;
private static final byte CURRENT_VERSION=0;
private Database db;
private DatabaseEntry key;
private boolean wrapAllowed;
private boolean increment;
private boolean overflow;
private long rangeMin;
private long rangeMax;
private long storedValue;
private int cacheSize;
private long cacheValue;
private long cacheLast;
private TransactionConfig autoCommitConfig;
/**
* Opens a sequence handle, adding the sequence record if appropriate.
*/
Sequence( Database db, Transaction txn, DatabaseEntry key, SequenceConfig config) throws DatabaseException {
if (db.getDatabaseImpl().getSortedDuplicates()) {
throw new IllegalArgumentException("Sequences not supported in databases configured for " + "duplicates");
}
SequenceConfig useConfig=(config != null) ? config : SequenceConfig.DEFAULT;
if (useConfig.getRangeMin() >= useConfig.getRangeMax()) {
throw new IllegalArgumentException("Minimum sequence value must be less than the maximum");
}
if (useConfig.getInitialValue() > useConfig.getRangeMax() || useConfig.getInitialValue() < useConfig.getRangeMin()) {
throw new IllegalArgumentException("Initial sequence value is out of range");
}
if (useConfig.getRangeMin() > useConfig.getRangeMax() - useConfig.getCacheSize()) {
throw new IllegalArgumentException("The cache size is larger than the sequence range");
}
if (config.getAutoCommitNoSync()) {
autoCommitConfig=new TransactionConfig();
autoCommitConfig.setNoSync(true);
}
else {
autoCommitConfig=null;
}
this.db=db;
this.key=copyEntry(key);
this.hook84(db);
Locker locker=null;
Cursor cursor=null;
OperationStatus status=OperationStatus.NOTFOUND;
try {
locker=LockerFactory.getWritableLocker(db.getEnvironment(),txn,db.isTransactional(),false,autoCommitConfig);
cursor=new Cursor(db,locker,null);
if (useConfig.getAllowCreate()) {
rangeMin=useConfig.getRangeMin();
rangeMax=useConfig.getRangeMax();
increment=!useConfig.getDecrement();
wrapAllowed=useConfig.getWrap();
storedValue=useConfig.getInitialValue();
status=cursor.putNoOverwrite(key,makeData());
if (status == OperationStatus.KEYEXIST) {
if (useConfig.getExclusiveCreate()) {
throw new DatabaseException("ExclusiveCreate=true and the sequence record " + "already exists.");
}
if (!readData(cursor,null)) {
throw new DatabaseException("Sequence record removed during openSequence.");
}
status=OperationStatus.SUCCESS;
}
}
else {
if (!readData(cursor,null)) {
throw new DatabaseException("AllowCreate=false and the sequence record " + "does not exist.");
}
status=OperationStatus.SUCCESS;
}
}
finally {
if (cursor != null) {
cursor.close();
}
if (locker != null) {
locker.operationEnd(status);
}
}
cacheSize=useConfig.getCacheSize();
cacheValue=storedValue;
cacheLast=increment ? (storedValue - 1) : (storedValue + 1);
}
/**
* Javadoc for this public method is generated via
* the doc templates in the doc_src directory.
*/
public void close() throws DatabaseException {
}
/**
* Javadoc for this public method is generated via
* the doc templates in the doc_src directory.
* <p>This method is synchronized to protect updating of the cached value,
* since multiple threads may share a single handle. Multiple handles
* for the same database/key may be used to increase concurrency.</p>
*/
public synchronized long get( Transaction txn, int delta) throws DatabaseException {
if (delta <= 0) {
throw new IllegalArgumentException("Sequence delta must be greater than zero");
}
if (rangeMin > rangeMax - delta) {
throw new IllegalArgumentException("Sequence delta is larger than the range");
}
boolean cached=true;
boolean wrapped=false;
if ((increment && delta > ((cacheLast - cacheValue) + 1)) || (!increment && delta > ((cacheValue - cacheLast) + 1))) {
cached=false;
int adjust=(delta > cacheSize) ? delta : cacheSize;
Locker locker=null;
Cursor cursor=null;
OperationStatus status=OperationStatus.NOTFOUND;
try {
locker=LockerFactory.getWritableLocker(db.getEnvironment(),txn,db.isTransactional(),false,autoCommitConfig);
cursor=new Cursor(db,locker,null);
readDataRequired(cursor,LockMode.RMW);
if (overflow) {
throw new DatabaseException("Sequence overflow " + storedValue);
}
BigInteger availBig;
if (increment) {
availBig=BigInteger.valueOf(rangeMax).subtract(BigInteger.valueOf(storedValue));
}
else {
availBig=BigInteger.valueOf(storedValue).subtract(BigInteger.valueOf(rangeMin));
}
if (availBig.compareTo(BigInteger.valueOf(adjust)) < 0) {
int availInt=(int)availBig.longValue();
if (availInt < delta) {
if (wrapAllowed) {
storedValue=increment ? rangeMin : rangeMax;
wrapped=true;
}
else {
overflow=true;
adjust=0;
}
}
else {
adjust=availInt;
}
}
if (!increment) {
adjust=-adjust;
}
storedValue+=adjust;
cursor.put(key,makeData());
status=OperationStatus.SUCCESS;
}
finally {
if (cursor != null) {
cursor.close();
}
if (locker != null) {
locker.operationEnd(status);
}
}
cacheValue=storedValue - adjust;
cacheLast=storedValue + (increment ? (-1) : 1);
}
long retVal=cacheValue;
if (increment) {
cacheValue+=delta;
}
else {
cacheValue-=delta;
}
this.hook83(cached);
this.hook82(cached,wrapped,retVal);
return retVal;
}
/**
* Javadoc for this public method is generated via
* the doc templates in the doc_src directory.
*/
public Database getDatabase() throws DatabaseException {
return db;
}
/**
* Javadoc for this public method is generated via
* the doc templates in the doc_src directory.
*/
public DatabaseEntry getKey() throws DatabaseException {
return copyEntry(key);
}
/**
* Reads persistent fields from the sequence record.
* Throws an exception if the key is not present in the database.
*/
private void readDataRequired( Cursor cursor, LockMode lockMode) throws DatabaseException {
if (!readData(cursor,lockMode)) {
throw new DatabaseException("The sequence record has been deleted while it is open.");
}
}
/**
* Reads persistent fields from the sequence record.
* Returns false if the key is not present in the database.
*/
private boolean readData( Cursor cursor, LockMode lockMode) throws DatabaseException {
DatabaseEntry data=new DatabaseEntry();
OperationStatus status=cursor.getSearchKey(key,data,lockMode);
if (status != OperationStatus.SUCCESS) {
return false;
}
ByteBuffer buf=ByteBuffer.wrap(data.getData());
byte ignoreVersionForNow=buf.get();
byte flags=buf.get();
rangeMin=LogUtils.readLong(buf);
rangeMax=LogUtils.readLong(buf);
storedValue=LogUtils.readLong(buf);
increment=(flags & FLAG_INCR) != 0;
wrapAllowed=(flags & FLAG_WRAP) != 0;
overflow=(flags & FLAG_OVER) != 0;
return true;
}
/**
* Makes a storable database entry from the persistent fields.
*/
private DatabaseEntry makeData(){
byte[] data=new byte[MAX_DATA_SIZE];
ByteBuffer buf=ByteBuffer.wrap(data);
byte flags=0;
if (increment) {
flags|=FLAG_INCR;
}
if (wrapAllowed) {
flags|=FLAG_WRAP;
}
if (overflow) {
flags|=FLAG_OVER;
}
buf.put(CURRENT_VERSION);
buf.put(flags);
LogUtils.writeLong(buf,rangeMin);
LogUtils.writeLong(buf,rangeMax);
LogUtils.writeLong(buf,storedValue);
return new DatabaseEntry(data,0,buf.position());
}
/**
* Returns a deep copy of the given database entry.
*/
private DatabaseEntry copyEntry( DatabaseEntry entry){
int len=entry.getSize();
byte[] data;
if (len == 0) {
data=LogUtils.ZERO_LENGTH_BYTE_ARRAY;
}
else {
data=new byte[len];
System.arraycopy(entry.getData(),entry.getOffset(),data,0,data.length);
}
return new DatabaseEntry(data);
}
protected void hook82( boolean cached, boolean wrapped, long retVal) throws DatabaseException {
}
protected void hook83( boolean cached) throws DatabaseException {
}
protected void hook84( Database db) throws DatabaseException {
}
}