package com.sleepycat.je.tree;
import java.nio.ByteBuffer;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.log.LogFileNotFoundException;
import com.sleepycat.je.log.LogReadable;
import com.sleepycat.je.log.LogUtils;
import com.sleepycat.je.log.LogWritable;
import com.sleepycat.je.utilint.DbLsn;
import de.ovgu.cide.jakutil.*;
/**
* A ChildReference is a reference in the tree from parent to child. It
* contains a node reference, key, and LSN.
*/
public class ChildReference implements LogWritable, LogReadable {
private Node target;
private long lsn;
private byte[] key;
private byte state;
private static final byte KNOWN_DELETED_BIT=0x1;
private static final byte DIRTY_BIT=0x2;
private static final byte CLEAR_DIRTY_BIT=~0x2;
private static final byte MIGRATE_BIT=0x4;
private static final byte CLEAR_MIGRATE_BIT=~0x4;
private static final byte PENDING_DELETED_BIT=0x8;
/**
* Construct an empty child reference, for reading from the log.
*/
ChildReference(){
init(null,Key.EMPTY_KEY,DbLsn.NULL_LSN,0);
}
/**
* Construct a ChildReference for inserting a new entry.
*/
public ChildReference( Node target, byte[] key, long lsn){
init(target,key,lsn,DIRTY_BIT);
}
/**
* Construct a ChildReference for inserting an existing entry.
*/
public ChildReference( Node target, byte[] key, long lsn, byte existingState){
init(target,key,lsn,existingState | DIRTY_BIT);
}
private void init( Node target, byte[] key, long lsn, int state){
this.target=target;
this.key=key;
this.lsn=lsn;
this.state=(byte)state;
}
/**
* Return the key for this ChildReference.
*/
public byte[] getKey(){
return key;
}
/**
* Set the key for this ChildReference.
*/
public void setKey( byte[] key){
this.key=key;
state|=DIRTY_BIT;
}
/**
* Fetch the target object that this ChildReference refers to. If the
* object is already in VM, then just return the reference to it. If the
* object is not in VM, then read the object from the log. If the object
* has been faulted in and the in arg is supplied, then the total memory
* size cache in the IN is invalidated.
* @param database The database that this ChildReference resides in.
* @param in The IN that this ChildReference lives in. If
* the target is fetched (i.e. it is null on entry), then the
* total in memory count is invalidated in the IN. May be null.
* For example, the root is a ChildReference and there is no parent IN
* when the rootIN is fetched in.
* @return the Node object representing the target node in the tree, or
* null if there is no target of this ChildReference, or null if a
* pendingDelete or knownDeleted entry has been cleaned.
*/
public Node fetchTarget( DatabaseImpl database, IN in) throws DatabaseException {
if (target == null) {
if (lsn == DbLsn.NULL_LSN) {
if (!isKnownDeleted()) {
throw new DatabaseException(IN.makeFetchErrorMsg("NULL_LSN without KnownDeleted",in,lsn,state));
}
}
else {
try {
EnvironmentImpl env=database.getDbEnvironment();
Node node=(Node)env.getLogManager().get(lsn);
node.postFetchInit(database,lsn);
target=node;
this.hook613(in);
}
catch ( LogFileNotFoundException LNFE) {
if (!isKnownDeleted() && !isPendingDeleted()) {
throw new DatabaseException(IN.makeFetchErrorMsg(LNFE.toString(),in,lsn,state),LNFE);
}
}
catch ( Exception e) {
throw new DatabaseException(IN.makeFetchErrorMsg(e.toString(),in,lsn,state),e);
}
}
}
return target;
}
byte getState(){
return state;
}
/**
* Return the target for this ChildReference.
*/
public Node getTarget(){
return target;
}
/**
* Sets the target for this ChildReference. No need to make dirty, that
* state only applies to key and LSN.
*/
public void setTarget( Node target){
this.target=target;
}
/**
* Clear the target for this ChildReference. No need to make dirty, that
* state only applies to key and LSN. This method is public because it's
* safe and used by RecoveryManager. This can't corrupt the tree.
*/
public void clearTarget(){
this.target=null;
}
/**
* Return the LSN for this ChildReference.
* @return the LSN for this ChildReference.
*/
public long getLsn(){
return lsn;
}
/**
* Sets the target LSN for this ChildReference.
* @param the target LSN.
*/
public void setLsn( long lsn){
this.lsn=lsn;
state|=DIRTY_BIT;
}
/**
* @return true if the entry has been deleted, although the transaction the
* performed the deletion may not be committed.
*/
private boolean isPendingDeleted(){
return ((state & PENDING_DELETED_BIT) != 0);
}
/**
* @return true if entry is deleted for sure.
*/
public boolean isKnownDeleted(){
return ((state & KNOWN_DELETED_BIT) != 0);
}
/**
* @return true if the object is dirty.
*/
private boolean isDirty(){
return ((state & DIRTY_BIT) != 0);
}
/**
* Get the entry migrate status.
*/
public boolean getMigrate(){
return (state & MIGRATE_BIT) != 0;
}
/**
* Set the entry migrate status.
*/
public void setMigrate( boolean migrate){
if (migrate) {
state|=MIGRATE_BIT;
}
else {
state&=CLEAR_MIGRATE_BIT;
}
}
/**
* @see LogWritable#getLogSize
*/
public int getLogSize(){
return LogUtils.getByteArrayLogSize(key) + LogUtils.getLongLogSize() + 1;
}
/**
* @see LogWritable#writeToLog
*/
public void writeToLog( ByteBuffer logBuffer){
LogUtils.writeByteArray(logBuffer,key);
assert lsn != DbLsn.NULL_LSN;
LogUtils.writeLong(logBuffer,lsn);
logBuffer.put(state);
state&=CLEAR_DIRTY_BIT;
}
/**
* @see LogReadable#readFromLog
*/
public void readFromLog( ByteBuffer itemBuffer, byte entryTypeVersion){
key=LogUtils.readByteArray(itemBuffer);
lsn=LogUtils.readLong(itemBuffer);
state=itemBuffer.get();
state&=CLEAR_DIRTY_BIT;
}
/**
* @see LogReadable#dumpLog
*/
public void dumpLog( StringBuffer sb, boolean verbose){
sb.append("<ref knownDeleted=\"").append(isKnownDeleted());
sb.append("\" pendingDeleted=\"").append(isPendingDeleted());
sb.append("\">");
sb.append(Key.dumpString(key,0));
sb.append(DbLsn.toString(lsn));
sb.append("</ref>");
}
/**
* @see LogReadable#logEntryIsTransactional
*/
public boolean logEntryIsTransactional(){
return false;
}
/**
* @see LogReadable#getTransactionId
*/
public long getTransactionId(){
return 0;
}
String dumpString( int nspaces, boolean dumpTags){
StringBuffer sb=new StringBuffer();
if (lsn == DbLsn.NULL_LSN) {
sb.append(TreeUtils.indent(nspaces));
sb.append("<lsn/>");
}
else {
sb.append(DbLsn.dumpString(lsn,nspaces));
}
sb.append('\n');
if (key == null) {
sb.append(TreeUtils.indent(nspaces));
sb.append("<key/>");
}
else {
sb.append(Key.dumpString(key,nspaces));
}
sb.append('\n');
if (target == null) {
sb.append(TreeUtils.indent(nspaces));
sb.append("<target/>");
}
else {
sb.append(target.dumpString(nspaces,true));
}
sb.append('\n');
sb.append(TreeUtils.indent(nspaces));
sb.append("<knownDeleted val=\"");
sb.append(isKnownDeleted()).append("\"/>");
sb.append("<pendingDeleted val=\"");
sb.append(isPendingDeleted()).append("\"/>");
sb.append("<dirty val=\"").append(isDirty()).append("\"/>");
return sb.toString();
}
public String toString(){
return dumpString(0,false);
}
protected void hook613( IN in) throws DatabaseException, LogFileNotFoundException, Exception {
}
}