/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2002-2006
 *      Sleepycat Software.  All rights reserved.
 *
 * $Id: LNLogEntry.java,v 1.34 2006/01/03 21:55:50 bostic Exp $
 */

package com.sleepycat.je.log.entry;

import java.nio.ByteBuffer;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.dbi.DatabaseId;
import com.sleepycat.je.log.LogEntryType;
import com.sleepycat.je.log.LogUtils;
import com.sleepycat.je.log.LoggableObject;
import com.sleepycat.je.tree.Key;
import com.sleepycat.je.tree.LN;
import com.sleepycat.je.txn.Txn;
import com.sleepycat.je.utilint.DbLsn;

/**
 * LNLogEntry embodies all LN transactional log entries.
 * These entries contain:
 * <pre>
 *   ln
 *   databaseid
 *   key            
 *   abortLsn       -- if transactional
 *   abortKnownDeleted -- if transactional
 *   txn            -- if transactional
 * </pre>
 */
public class LNLogEntry implements LogEntry, LoggableObject, NodeLogEntry {

    /* Objects contained in an LN entry */
    private LN ln;
    private DatabaseId dbId;
    private byte[] key;
    private long abortLsn = DbLsn.NULL_LSN;
    private boolean abortKnownDeleted;
    private Txn txn;
    
    private static final byte ABORT_KNOWN_DELETED_MASK = (byte) 1;

    /* Class used to instantiate the main item in this entry */
    private Class logClass;           // used for reading a log entry
    private LogEntryType entryType; // used for writing a log entry
    private long nodeId;

    /* 
     * Note: used this flag instead of splitting this class into a txnal and
     * non-txnal version because we want to subclass it with the duplicate
     * deleted entry, which also comes in txnal and non-txnal form.
     */
    private boolean isTransactional; 

    /* Constructor to read an entry. */
    public LNLogEntry(Class logClass, boolean isTransactional) {
        this.logClass = logClass;
        this.isTransactional = isTransactional;
    }

    /* Constructor to write an entry. */
    public LNLogEntry(LogEntryType entryType,
                      LN ln,
		      DatabaseId dbId,
		      byte[] key,
                      long abortLsn,
		      boolean abortKnownDeleted,
		      Txn txn) {
        this.entryType = entryType;
        this.ln = ln;
        this.dbId = dbId;
        this.key = key;
        this.abortLsn = abortLsn;
	this.abortKnownDeleted = abortKnownDeleted;
        this.txn = txn;
        this.isTransactional = (txn != null);
        this.logClass = ln.getClass();
        this.nodeId = ln.getNodeId();
    }

    /**
     * @see LogEntry#readEntry
     */
    public void readEntry(ByteBuffer entryBuffer,
			  int entrySize,
                          byte entryTypeVersion,
			  boolean readFullItem)
        throws DatabaseException {

        try {
            if (readFullItem) {
                /* Read LN and get node ID. */
                ln = (LN) logClass.newInstance();
                ln.readFromLog(entryBuffer, entryTypeVersion);
                nodeId = ln.getNodeId();

                /* DatabaseImpl Id */
                dbId = new DatabaseId();
                dbId.readFromLog(entryBuffer, entryTypeVersion);

                /* Key */
                key = LogUtils.readByteArray(entryBuffer);

                if (isTransactional) {

                    /*
                     * AbortLsn. If it was a marker LSN that was used to fill
                     * in a create, mark it null.
                     */
		    abortLsn = LogUtils.readLong(entryBuffer);
                    if (DbLsn.getFileNumber(abortLsn) ==
                        DbLsn.getFileNumber(DbLsn.NULL_LSN)) {
                        abortLsn = DbLsn.NULL_LSN;
                    }

                    abortKnownDeleted =
                        ((entryBuffer.get() & ABORT_KNOWN_DELETED_MASK) != 0) ?
                        true : false;

                    /* Locker */
                    txn = new Txn();
                    txn.readFromLog(entryBuffer, entryTypeVersion);
                }
            } else {

                /*
                 * Read node ID and position to end.
                 * We currently do not support getting the db and txn ID in
                 * this mode, and we may want to change the log format to do
                 * that efficiently.
                 */
                int endPosition = entryBuffer.position() + entrySize;
                nodeId = LogUtils.readLong(entryBuffer);
                entryBuffer.position(endPosition);
                ln = null;
            }
        } catch (IllegalAccessException e) {
            throw new DatabaseException(e);
        } catch (InstantiationException e) {
            throw new DatabaseException(e);
        }
    }

    /**
     * @see LogEntry#dumpEntry
     */
    public StringBuffer dumpEntry(StringBuffer sb, boolean verbose) {
        ln.dumpLog(sb, verbose);
        dbId.dumpLog(sb, verbose);
        sb.append(Key.dumpString(key, 0));
        if (isTransactional) {
            if (abortLsn != DbLsn.NULL_LSN) {
		sb.append(DbLsn.toString(abortLsn));
            }
	    sb.append("<knownDeleted val=\"");
	    sb.append(abortKnownDeleted ? "true" : "false");
	    sb.append("\"/>");
            txn.dumpLog(sb, verbose);
        }
        return sb;
    }

    /**
     * @see LogEntry#getMainItem
     */
    public Object getMainItem() {
        return ln;
    }

    /**
     * @see LogEntry#clone
     */
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    /**
     * @see LogEntry#isTransactional
     */
    public boolean isTransactional() {
	return isTransactional;
    }

    /**
     * @see LogEntry#getTransactionId
     */
    public long getTransactionId() {
	if (isTransactional) {
	    return txn.getId();
	} else {
	    return 0;
	}
    }

    /**
     * @see NodeLogEntry#getNodeId
     */
    public long getNodeId() {
        return nodeId;
    }

    /*
     * Writing support
     */

    /**
     * @see LoggableObject#getLogType
     */
    public LogEntryType getLogType() {
        return entryType;
    }

    /**
     * @see LoggableObject#marshallOutsideWriteLatch
     * Ask the ln if it can be marshalled outside the log write latch.
     */
    public boolean marshallOutsideWriteLatch() {
        return ln.marshallOutsideWriteLatch();
    }

    /**
     * Returns true for a deleted LN to count it immediately as obsolete.
     * @see LoggableObject#countAsObsoleteWhenLogged
     */
    public boolean countAsObsoleteWhenLogged() {
        return ln.isDeleted();
    }

    /**
     * For LN entries, we need to record the latest LSN for that node with the
     * owning transaction, within the protection of the log latch. This is a
     * callback for the log manager to do that recording.
     *
     * @see LoggableObject#postLogWork
     */
    public void postLogWork(long justLoggedLsn)
        throws DatabaseException {

        if (isTransactional) {
            txn.addLogInfo(justLoggedLsn);
        }
    }

    /**
     * @see LoggableObject#getLogSize
     */
    public int getLogSize() {
        int size = ln.getLogSize() +
            dbId.getLogSize() +
            LogUtils.getByteArrayLogSize(key);
        if (isTransactional) {
	    size += LogUtils.getLongLogSize();
	    size++;   // abortKnownDeleted
            size += txn.getLogSize();
        }
        return size;
    }

    /**
     * @see LoggableObject#writeToLog
     */
    public void writeToLog(ByteBuffer destBuffer) {
        ln.writeToLog(destBuffer);
        dbId.writeToLog(destBuffer);
        LogUtils.writeByteArray(destBuffer, key);

        if (isTransactional) {
	    LogUtils.writeLong(destBuffer, abortLsn);
	    byte aKD = 0;
	    if (abortKnownDeleted) {
		aKD |= ABORT_KNOWN_DELETED_MASK;
	    }
	    destBuffer.put(aKD);
            txn.writeToLog(destBuffer);
        }
    }

    /*
     * Accessors
     */
    public LN getLN() {
        return ln;
    }

    public DatabaseId getDbId() {
        return dbId;
    }

    public byte[] getKey() {
        return key;
    }

    public byte[] getDupKey() {
        if (ln.isDeleted()) {
            return null;
        } else {
            return ln.getData();
        }
    }

    public long getAbortLsn() {
        return abortLsn;
    }

    public boolean getAbortKnownDeleted() {
	return abortKnownDeleted;
    }

    public Long getTxnId() {
        if (isTransactional) {
            return new Long(txn.getId());
        } else {
            return null;
        }
    }

    public Txn getUserTxn() {
        if (isTransactional) {
            return txn;
        } else {
            return null;
        }
    }
}
