namespace com.sleepycat.je.log;

class FileManager
{
  depend de.ovgu.cide.jakutil.*;
  depend com.sleepycat.je.utilint.HexFormatter;
  depend com.sleepycat.je.utilint.DbLsn;
  depend com.sleepycat.je.utilint.Adler32;
  depend com.sleepycat.je.log.entry.LogEntry;
  depend com.sleepycat.je.dbi.EnvironmentImpl;
  depend com.sleepycat.je.dbi.DbConfigManager;
  depend com.sleepycat.je.config.EnvironmentParams;
  depend com.sleepycat.je.RunRecoveryException;
  depend com.sleepycat.je.DatabaseException;
  depend java.util.zip.Checksum;
  depend java.util.Set;
  depend java.util.Random;
  depend java.util.Map;
  depend java.util.LinkedList;
  depend java.util.Iterator;
  depend java.util.Hashtable;
  depend java.util.HashMap;
  depend java.util.Arrays;
  depend java.nio.channels.OverlappingFileLockException;
  depend java.nio.channels.FileLock;
  depend java.nio.channels.FileChannel;
  depend java.nio.channels.ClosedChannelException;
  depend java.nio.ByteBuffer;
  depend java.io.RandomAccessFile;
  depend java.io.IOException;
  depend java.io.FileNotFoundException;
  depend java.io.File;
    static boolean IO_EXCEPTION_TESTING = false;

    private static final String DEBUG_NAME = FileManager.class.getName();

    private static long writeCount = 0;

    private static long stopOnWriteCount = Long.MAX_VALUE;

    public static final String JE_SUFFIX = ".jdb";

    public static final String CIF_SUFFIX = ".cif";

    public static final String DEL_SUFFIX = ".del";

    public static final String BAD_SUFFIX = ".bad";

    public static final String LOCK_SUFFIX = ".lck";

    static final String[] DEL_SUFFIXES = { DEL_SUFFIX };

    static final String[] JE_SUFFIXES = { JE_SUFFIX };

    private static final String[] JE_AND_DEL_SUFFIXES = { JE_SUFFIX, DEL_SUFFIX };

    private boolean syncAtFileEnd = true;

    private EnvironmentImpl envImpl;

    private long maxFileSize;

    private File dbEnvHome;

    private boolean includeDeletedFiles = false;

    private boolean readOnly;

    private long currentFileNum;

    private long nextAvailableLsn;

    private long lastUsedLsn;

    private long prevOffset;

    private boolean forceNewFile;

    private long savedCurrentFileNum;

    private long savedNextAvailableLsn;

    private long savedLastUsedLsn;

    private long savedPrevOffset;

    private boolean savedForceNewFile;

    private LogEndFileDescriptor endOfLog;

    private Map perFileLastUsedLsn;

    /** 
     * The LogEndFileDescriptor is used to write and fsync the end of the log. Because the JE log is append only, there is only one logical R/W file descriptor for the whole environment. This class actually implements two RandomAccessFile instances, one for writing and one for fsyncing, so the two types of operations don't block each other. The write file descriptor is considered the master. Manipulation of this class is done under the log write latch. Here's an explanation of why the log write latch is sufficient to safeguard all operations. There are two types of callers who may use this file descriptor: the thread that is currently writing to the end of the log and any threads that are fsyncing on behalf of the FSyncManager. The writing thread appends data to the file and fsyncs the file when we flip over to a new log file. The file is only instantiated at the point that it must do so -- which is either when the first fsync is required by JE or when the log file is full and we flip files. Therefore, the writing thread has two actions that change this descriptor -- we initialize the file descriptor for the given log file at the first write to the file, and we close the file descriptor when the log file is full. Therefore is a period when there is no log descriptor -- when we have not yet written a log buffer into a given log file. The fsyncing threads ask for the log end file descriptor asynchronously, but will never modify it. These threads may arrive at the point when the file descriptor is null, and therefore skip their fysnc, but that is fine because it means a writing thread already flipped that target file and has moved on to the next file. Time Activity 10 thread 1 writes log entry A into file 0x0, issues fsync outside of log write latch, yields the processor 20 thread 2 writes log entry B, piggybacks off thread 1 30 thread 3 writes log entry C, but no room left in that file, so it flips the log, and fsyncs file 0x0, all under the log write latch. It nulls out endOfLogRWFile, moves onto file 0x1, but doesn't create the file yet. 40 thread 1 finally comes along, but endOfLogRWFile is null-- no need to fsync in that case, 0x0 got fsynced.
     */
    static boolean RUNRECOVERY_EXCEPTION_TESTING = false;

    private static final int RUNRECOVERY_EXCEPTION_MAX = 100;

    private int runRecoveryExceptionCounter = 0;

    private boolean runRecoveryExceptionThrown = false;

    private Random runRecoveryExceptionRandom = null;

    /** 
     * Set up the file cache and initialize the file manager to point to the beginning of the log.
     * @param configManager
     * @param dbEnvHomeenvironment home directory
     */
    public FileManager(EnvironmentImpl envImpl, File dbEnvHome, boolean readOnly) throws DatabaseException {
	this.envImpl = envImpl;
	this.dbEnvHome = dbEnvHome;
	this.readOnly = readOnly;
	DbConfigManager configManager = envImpl.getConfigManager();
	maxFileSize = configManager.getLong(EnvironmentParams.LOG_FILE_MAX);
	this.hook456(configManager);
	Label467:
  Label457: 
	//this.hook457(configManager);
	this.hook449(envImpl);
	if (!dbEnvHome.exists()) {
	    throw new LogException("Environment home " + dbEnvHome + " doesn't exist");
	}
	currentFileNum = 0L;
	nextAvailableLsn = DbLsn.makeLsn(currentFileNum, firstLogEntryOffset());
	lastUsedLsn = DbLsn.NULL_LSN;
	perFileLastUsedLsn = new HashMap();
	prevOffset = 0L;
	endOfLog = new LogEndFileDescriptor();
	forceNewFile = false;
	saveLastPosition();
	String stopOnWriteProp = System.getProperty("je.debug.stopOnWrite");
	if (stopOnWriteProp != null) {
	    stopOnWriteCount = Long.parseLong(stopOnWriteProp);
	}
	this.hook452(envImpl);
    }

    /** 
     * Set the file manager's "end of log".
     * @param nextAvailableLsnLSN to be used for the next log entry
     * @param lastUsedLsnlast LSN to have a valid entry, may be null
     * @param prevOffsetvalue to use for the prevOffset of the next entry. If thebeginning of the file, this is 0.
     */
    public void setLastPosition(long nextAvailableLsn, long lastUsedLsn, long prevOffset) {
	this.lastUsedLsn = lastUsedLsn;
	perFileLastUsedLsn.put(new Long(DbLsn.getFileNumber(lastUsedLsn)), new Long(lastUsedLsn));
	this.nextAvailableLsn = nextAvailableLsn;
	currentFileNum = DbLsn.getFileNumber(this.nextAvailableLsn);
	this.prevOffset = prevOffset;
	saveLastPosition();
    }

    void saveLastPosition() {
	savedNextAvailableLsn = nextAvailableLsn;
	savedLastUsedLsn = lastUsedLsn;
	savedPrevOffset = prevOffset;
	savedForceNewFile = forceNewFile;
	savedCurrentFileNum = currentFileNum;
    }

    void restoreLastPosition() {
	nextAvailableLsn = savedNextAvailableLsn;
	lastUsedLsn = savedLastUsedLsn;
	prevOffset = savedPrevOffset;
	forceNewFile = savedForceNewFile;
	currentFileNum = savedCurrentFileNum;
    }

    /** 
     * May be used to disable sync at file end to speed unit tests. Must only be used for unit testing, since log corruption may result.
     */
    public void setSyncAtFileEnd(boolean sync) {
	syncAtFileEnd = sync;
    }

    /** 
     * public for cleaner.
     * @return the number of the first file in this environment.
     */
    public Long getFirstFileNum() {
	return getFileNum(true);
    }

    public boolean getReadOnly() {
	return readOnly;
    }

    /** 
     * @return the number of the last file in this environment.
     */
    public Long getLastFileNum() {
	return getFileNum(false);
    }

    public long getCurrentFileNum() {
	return currentFileNum;
    }

    public void setIncludeDeletedFiles(boolean includeDeletedFiles) {
	this.includeDeletedFiles = includeDeletedFiles;
    }

    /** 
     * Get all JE file numbers.
     * @return an array of all JE file numbers.
     */
    public Long[] getAllFileNumbers() {
	String[] names = listFiles(JE_SUFFIXES);
	Long[] nums = new Long[names.length];
	for (int i = 0; i < nums.length; i += 1) {
	    nums[i] = getNumFromName(names[i]);
	}
	return nums;
    }

    /** 
     * Get the next file number before/after currentFileNum.
     * @param currentFileNumthe file we're at right now. Note that it may not exist, ifit's been cleaned and renamed.
     * @param forwardif true, we want the next larger file, if false we want theprevious file
     * @return null if there is no following file, or if filenum doesn't exist
     */
    public Long getFollowingFileNum(long currentFileNum, boolean forward) {
	String[] names = listFiles(JE_SUFFIXES);
	String searchName = getFileName(currentFileNum, JE_SUFFIX);
	int foundIdx = Arrays.binarySearch(names, searchName);
	boolean foundTarget = false;
	if (foundIdx >= 0) {
	    if (forward) {
		foundIdx++;
	    } else {
		foundIdx--;
	    }
	} else {
	    foundIdx = Math.abs(foundIdx + 1);
	    if (!forward) {
		foundIdx--;
	    }
	}
	if (forward && (foundIdx < names.length)) {
	    foundTarget = true;
	} else if (!forward && (foundIdx > -1)) {
	    foundTarget = true;
	}
	if (foundTarget) {
	    return getNumFromName(names[foundIdx]);
	} else {
	    return null;
	}
    }

    /** 
     * @return true if there are any files at all.
     */
    public boolean filesExist() {
	String[] names = listFiles(JE_SUFFIXES);
	return (names.length != 0);
    }

    /** 
     * Get the first or last file number in the set of je files.
     * @param firstif true, get the first file, else get the last file
     * @return the file number or null if no files exist
     */
    private Long getFileNum(boolean first) {
	String[] names = listFiles(JE_SUFFIXES);
	if (names.length == 0) {
	    return null;
	} else {
	    int index = 0;
	    if (!first) {
		index = names.length - 1;
	    }
	    return getNumFromName(names[index]);
	}
    }

    /** 
     * Get the file number from a file name.
     * @param thefile name
     * @return the file number
     */
    private Long getNumFromName(String fileName) {
	String fileNumber = fileName.substring(0, fileName.indexOf("."));
	return new Long(Long.parseLong(fileNumber, 16));
    }

    /** 
     * Find je files. Return names sorted in ascending fashion.
     * @param suffixwhich type of file we're looking for
     * @return array of file names
     */
    String[] listFiles(String[] suffixes) {
	String[] fileNames = dbEnvHome.list(new JEFileFilter(suffixes));
	Arrays.sort(fileNames);
	return fileNames;
    }

    /** 
     * Find je files, flavor for unit test support.
     * @param suffixwhich type of file we're looking for
     * @return array of file names
     */
    public static String[] listFiles(File envDirFile, String[] suffixes) {
	String[] fileNames = envDirFile.list(new JEFileFilter(suffixes));
	Arrays.sort(fileNames);
	return fileNames;
    }

    /** 
     * @return the full file name and path for the nth je file.
     */
    String[] getFullFileNames(long fileNum) {
	if (includeDeletedFiles) {
	    int nSuffixes = JE_AND_DEL_SUFFIXES.length;
	    String[] ret = new String[nSuffixes];
	    for (int i = 0; i < nSuffixes; i++) {
		ret[i] = getFullFileName(getFileName(fileNum, JE_AND_DEL_SUFFIXES[i]));
	    }
	    return ret;
	} else {
	    return new String[] { getFullFileName(getFileName(fileNum, JE_SUFFIX)) };
	}
    }

    /** 
     * @return the full file name and path for the given file number and suffix.
     */
    public String getFullFileName(long fileNum, String suffix) {
	return getFullFileName(getFileName(fileNum, suffix));
    }

    /** 
     * @return the full file name and path for this file name.
     */
    private String getFullFileName(String fileName) {
	return dbEnvHome + File.separator + fileName;
    }

    /** 
     * @return the file name for the nth file.
     */
    public static String getFileName(long fileNum, String suffix) {
	return (HexFormatter.formatLong(fileNum).substring(10) + suffix);
    }

    /** 
     * Rename this file to NNNNNNNN.suffix. If that file already exists, try NNNNNNNN.suffix.1, etc. Used for deleting files or moving corrupt files aside.
     * @param fileNumthe file we want to move
     * @param newSuffixthe new file suffix
     */
    public void renameFile(long fileNum, String newSuffix) throws DatabaseException, IOException {
	int repeatNum = 0;
	boolean renamed = false;
	while (!renamed) {
	    String generation = "";
	    if (repeatNum > 0) {
		generation = "." + repeatNum;
	    }
	    String newName = getFullFileName(getFileName(fileNum, newSuffix) + generation);
	    File targetFile = new File(newName);
	    if (targetFile.exists()) {
		repeatNum++;
	    } else {
		String oldFileName = getFullFileNames(fileNum)[0];
    Label458:		
//this.hook458(fileNum);
		File oldFile = new File(oldFileName);
		if (oldFile.renameTo(targetFile)) {
		    renamed = true;
		} else {
		    throw new LogException("Couldn't rename " + oldFileName + " to " + newName);
		}
	    }
	}
    }

    /** 
     * Delete log file NNNNNNNN.
     * @param fileNumthe file we want to move
     */
    public void deleteFile(long fileNum) throws DatabaseException, IOException {
	String fileName = getFullFileNames(fileNum)[0];
  Label459:
	//this.hook459(fileNum);
	File file = new File(fileName);
	boolean done = file.delete();
	if (!done) {
	    throw new LogException("Couldn't delete " + file);
	}
    }

    /** 
     * Return a read only file handle that corresponds the this file number. Retrieve it from the cache or open it anew and validate the file header. This method takes a latch on this file, so that the file descriptor will be held in the cache as long as it's in use. When the user is done with the file, the latch must be released.
     * @param fileNumwhich file
     * @return the file handle for the existing or newly created file
     */
 FileHandle getFileHandle(long fileNum) throws LogException,DatabaseException{
    try {
	    Long fileId = new Long(fileNum);
	    FileHandle fileHandle = null;
// Start of hook460
      Label460:
		  Label463:
      Label450:
      Label462:
      fileHandle = makeFileHandle(fileNum, FileMode.READ_MODE);
		  Label464:
      Label453:
			if (fileHandle.getFile() == null) {
					Label454:
			} 
			else {
					throw new ReturnObject(fileHandle);
			}
// End of hook460
					throw ReturnHack.returnObject;
			} catch (ReturnObject r) {
					return (FileHandle) r.value;
			}
  }

    private FileHandle makeFileHandle(long fileNum, FileMode mode) throws DatabaseException {
	String[] fileNames = getFullFileNames(fileNum);
	RandomAccessFile newFile = null;
	String fileName = null;
	try {
	    FileNotFoundException FNFE = null;
	    for (int i = 0; i < fileNames.length; i++) {
		fileName = fileNames[i];
		try {
		    newFile = new RandomAccessFile(fileName, mode.getModeValue());
		    break;
		} catch (FileNotFoundException e) {
		    if (FNFE == null) {
			FNFE = e;
		    }
		}
	    }
	    if (newFile == null) {
		throw FNFE;
	    }
	    boolean oldHeaderVersion = false;
	    if (newFile.length() == 0) {
		if (mode == FileMode.READWRITE_MODE) {
		    long lastLsn = DbLsn.longToLsn((Long) perFileLastUsedLsn.remove(new Long(fileNum - 1)));
		    long headerPrevOffset = 0;
		    if (lastLsn != DbLsn.NULL_LSN) {
			headerPrevOffset = DbLsn.getFileOffset(lastLsn);
		    }
		    FileHeader fileHeader = new FileHeader(fileNum, headerPrevOffset);
		    writeFileHeader(newFile, fileName, fileHeader);
		}
	    } else {
		oldHeaderVersion = readAndValidateFileHeader(newFile, fileName, fileNum);
	    }
	    return new FileHandle(newFile, fileName, envImpl, oldHeaderVersion);
	} catch (FileNotFoundException e) {
	    throw new LogFileNotFoundException("Couldn't open file " + fileName + ": " + e.getMessage());
	} catch (DbChecksumException e) {
	    closeFileInErrorCase(newFile);
	    throw new DbChecksumException(envImpl, "Couldn't open file " + fileName, e);
	} catch (Throwable t) {
	    closeFileInErrorCase(newFile);
	    throw new DatabaseException("Couldn't open file " + fileName + ": " + t, t);
	}
    }

    /** 
     * Close this file and eat any exceptions. Used in catch clauses.
     */
    private void closeFileInErrorCase(RandomAccessFile file) {
	try {
	    if (file != null) {
		file.close();
	    }
	} catch (IOException e) {
	}
    }

    /** 
     * Read the given je log file and validate the header.
     * @throws DatabaseExceptionif the file header isn't valid
     * @return whether the file header has an old version number.
     */
    private boolean readAndValidateFileHeader(RandomAccessFile file, String fileName, long fileNum)
	    throws DatabaseException, IOException {
	LogManager logManager = envImpl.getLogManager();
	LogEntry headerEntry = logManager.getLogEntry(DbLsn.makeLsn(fileNum, 0), file);
	FileHeader header = (FileHeader) headerEntry.getMainItem();
	return header.validate(fileName, fileNum);
    }

    /** 
     * Write a proper file header to the given file.
     */
    private void writeFileHeader(RandomAccessFile file, String fileName, FileHeader header)
	    throws DatabaseException, IOException {
	envImpl.checkIfInvalid();
	if (envImpl.mayNotWrite()) {
	    return;
	}
	int headerSize = header.getLogSize();
	int entrySize = headerSize + LogManager.HEADER_BYTES;
	ByteBuffer headerBuf = envImpl.getLogManager().putIntoBuffer(header, headerSize, 0, false, entrySize);
	if (++writeCount >= stopOnWriteCount) {
	    Runtime.getRuntime().halt(0xff);
	}
	int bytesWritten;
	try {
	    if (RUNRECOVERY_EXCEPTION_TESTING) {
		generateRunRecoveryException(file, headerBuf, 0);
	    }
	    bytesWritten = writeToFile(file, headerBuf, 0);
	} catch (ClosedChannelException e) {
	    throw new RunRecoveryException(envImpl, "Channel closed, may be due to thread interrupt", e);
	} catch (IOException e) {
	    throw new RunRecoveryException(envImpl, "IOException caught: " + e);
	}
	if (bytesWritten != entrySize) {
	    throw new LogException("File " + fileName + " was created with an incomplete header. Only " + bytesWritten
		    + " bytes were written.");
	}
    }

    /** 
     * @return the prevOffset field stored in the file header.
     */
    long getFileHeaderPrevOffset(long fileNum) throws IOException, DatabaseException {
	LogEntry headerEntry = envImpl.getLogManager().getLogEntry(DbLsn.makeLsn(fileNum, 0));
	FileHeader header = (FileHeader) headerEntry.getMainItem();
	return header.getLastEntryInPrevFileOffset();
    }

    /** 
     * @return the file offset of the last LSN that was used. For constructingthe headers of log entries. If the last LSN that was used was in a previous file, or this is the very first LSN of the whole system, return 0.
     */
    long getPrevEntryOffset() {
	return prevOffset;
    }

    /** 
     * Increase the current log position by "size" bytes. Move the prevOffset pointer along.
     * @param sizeis an unsigned int
     * @return true if we flipped to the next log file.
     */
    boolean bumpLsn(long size) {
	saveLastPosition();
	boolean flippedFiles = false;
	if (forceNewFile || (DbLsn.getFileOffset(nextAvailableLsn) + size) > maxFileSize) {
	    forceNewFile = false;
	    currentFileNum++;
	    if (lastUsedLsn != DbLsn.NULL_LSN) {
		perFileLastUsedLsn.put(new Long(DbLsn.getFileNumber(lastUsedLsn)), new Long(lastUsedLsn));
	    }
	    prevOffset = 0;
	    lastUsedLsn = DbLsn.makeLsn(currentFileNum, firstLogEntryOffset());
	    flippedFiles = true;
	} else {
	    if (lastUsedLsn == DbLsn.NULL_LSN) {
		prevOffset = 0;
	    } else {
		prevOffset = DbLsn.getFileOffset(lastUsedLsn);
	    }
	    lastUsedLsn = nextAvailableLsn;
	}
	nextAvailableLsn = DbLsn.makeLsn(DbLsn.getFileNumber(lastUsedLsn), (DbLsn.getFileOffset(lastUsedLsn) + size));
	return flippedFiles;
    }

    /** 
     * Write out a log buffer to the file.
     * @param fullBufferbuffer to write
     */
    void writeLogBuffer(LogBuffer fullBuffer) throws DatabaseException {
	envImpl.checkIfInvalid();
	if (envImpl.mayNotWrite()) {
	    return;
	}
	long firstLsn = fullBuffer.getFirstLsn();
	if (firstLsn != DbLsn.NULL_LSN) {
	    RandomAccessFile file = endOfLog.getWritableFile(DbLsn.getFileNumber(firstLsn));
	    ByteBuffer data = fullBuffer.getDataBuffer();
	    if (++writeCount >= stopOnWriteCount) {
		Runtime.getRuntime().halt(0xff);
	    }
	    try {
	//	this.hook465(fullBuffer, firstLsn, file);
Label465:
		if (IO_EXCEPTION_TESTING) {
		    throw new IOException("generated for testing");
		}
		if (RUNRECOVERY_EXCEPTION_TESTING) {
		    generateRunRecoveryException(file, data, DbLsn.getFileOffset(firstLsn));
		}
		writeToFile(file, data, DbLsn.getFileOffset(firstLsn));
	    } catch (ClosedChannelException e) {
		throw new RunRecoveryException(envImpl, "File closed, may be due to thread interrupt", e);
	    } catch (IOException IOE) {
		abortCommittedTxns(data);
    Label466:
	//	this.hook466(fullBuffer, firstLsn, file, data, IOE); 
	    }
	    assert EnvironmentImpl.maybeForceYield();
	}
    }

    /** 
     * Write a buffer to a file at a given offset, using NIO if so configured.
     */
    private int writeToFile(RandomAccessFile file, ByteBuffer data, long destOffset)
	    throws IOException, DatabaseException {
	return new FileManager_writeToFile(this, file, data, destOffset).execute();
    }

    /** 
     * Read a buffer from a file at a given offset, using NIO if so configured.
     */
    void readFromFile(RandomAccessFile file, ByteBuffer readBuffer, long offset) throws IOException {
	new FileManager_readFromFile(this, file, readBuffer, offset).execute();
    }

    private void abortCommittedTxns(ByteBuffer data) {
	final byte commitType = LogEntryType.LOG_TXN_COMMIT.getTypeNum();
	final byte abortType = LogEntryType.LOG_TXN_ABORT.getTypeNum();
//	this.hook461(data);
  Label461:
	while (data.remaining() > 0) {
	    int recStartPos = data.position();
	    data.position(recStartPos + LogManager.HEADER_ENTRY_TYPE_OFFSET);
	    int typePos = data.position();
	    byte entryType = data.get();
	    boolean recomputeChecksum = false;
	    if (entryType == commitType) {
		data.position(typePos);
		data.put(abortType);
		recomputeChecksum = true;
	    }
	    byte version = data.get();
	    data.position(data.position() + LogManager.PREV_BYTES);
	    int itemSize = LogUtils.readInt(data);
	    int itemDataStartPos = data.position();
	    if (recomputeChecksum) {
		Checksum checksum = Adler32.makeChecksum();
		data.position(recStartPos);
		int nChecksumBytes = itemSize + (LogManager.HEADER_BYTES - LogManager.CHECKSUM_BYTES);
		byte[] checksumBytes = new byte[nChecksumBytes];
		System.arraycopy(data.array(), recStartPos + LogManager.CHECKSUM_BYTES, checksumBytes, 0,
			nChecksumBytes);
		checksum.update(checksumBytes, 0, nChecksumBytes);
		LogUtils.writeUnsignedInt(data, checksum.getValue());
	    }
	    data.position(itemDataStartPos + itemSize);
	}
	data.position(0);
    }

    /** 
     * FSync the end of the log.
     */
    void syncLogEnd() throws DatabaseException {
	try {
	    endOfLog.force();
	} catch (IOException e) {
	    throw new DatabaseException(e);
	}
    }

    /** 
     * Sync the end of the log, close off this log file. Should only be called under the log write latch.
     */
    void syncLogEndAndFinishFile() throws DatabaseException, IOException {
	if (syncAtFileEnd) {
	    syncLogEnd();
	}
	endOfLog.close();
    }

    /** 
     * Close all file handles and empty the cache.
     */
    public void clear() throws IOException, DatabaseException {
	endOfLog.close();
    }

    /** 
     * Clear the file lock.
     */
    public void close() throws IOException, DatabaseException {
    }

    /** 
     * Ensure that if the environment home dir is on readonly media or in a readonly directory that the environment has been opened for readonly access.
     * @return true if the environment home dir is readonly.
     */
    private boolean checkEnvHomePermissions(boolean readOnly) throws DatabaseException {
	boolean envDirIsReadOnly = !dbEnvHome.canWrite();
	if (envDirIsReadOnly && !readOnly) {
	    throw new DatabaseException("The Environment directory " + dbEnvHome + " is not writable, but the "
		    + "Environment was opened for read-write access.");
	}
	return envDirIsReadOnly;
    }

    /** 
     * Truncate a log at this position. Used by recovery to a timestamp utilities and by recovery to set the end-of-log position. <p> This method forces a new log file to be written next, if the last file (the file truncated to) has an old version in its header. This ensures that when the log is opened by an old version of JE, a version incompatibility will be detected. [#11243] </p>
     */
    public void truncateLog(long fileNum, long offset) throws IOException, DatabaseException {
	FileHandle handle = makeFileHandle(fileNum, FileMode.READWRITE_MODE);
	RandomAccessFile file = handle.getFile();
	try {
	    file.getChannel().truncate(offset);
	} finally {
	    file.close();
	}
	if (handle.isOldHeaderVersion()) {
	    forceNewFile = true;
	}
    }

    /** 
     * Set the flag that causes a new file to be written before the next write.
     */
    void forceNewLogFile() {
	forceNewFile = true;
    }

    /** 
     * @return the size in bytes of the file header log entry.
     */
    public static int firstLogEntryOffset() {
	return FileHeader.entrySize() + LogManager.HEADER_BYTES;
    }

    /** 
     * Return the next available LSN in the log. Note that this is unsynchronized, so is only valid as an approximation of log size.
     */
    public long getNextLsn() {
	return nextAvailableLsn;
    }

    /** 
     * Return the last allocated LSN in the log. Note that this is unsynchronized, so if it is called outside the log write latch it is only valid as an approximation of log size.
     */
    public long getLastUsedLsn() {
	return lastUsedLsn;
    }

    private void generateRunRecoveryException(RandomAccessFile file, ByteBuffer data, long destOffset)
	    throws DatabaseException, IOException {
	if (runRecoveryExceptionThrown) {
	    try {
		throw new Exception("Write after RunRecoveryException");
	    } catch (Exception e) {
		e.printStackTrace();
	    }
	}
	runRecoveryExceptionCounter += 1;
	if (runRecoveryExceptionCounter >= RUNRECOVERY_EXCEPTION_MAX) {
	    runRecoveryExceptionCounter = 0;
	}
	if (runRecoveryExceptionRandom == null) {
	    runRecoveryExceptionRandom = new Random(System.currentTimeMillis());
	}
	if (runRecoveryExceptionCounter == runRecoveryExceptionRandom.nextInt(RUNRECOVERY_EXCEPTION_MAX)) {
	    int len = runRecoveryExceptionRandom.nextInt(data.remaining());
	    if (len > 0) {
		byte[] a = new byte[len];
		data.get(a, 0, len);
		ByteBuffer buf = ByteBuffer.wrap(a);
		writeToFile(file, buf, destOffset);
	    }
	    runRecoveryExceptionThrown = true;
	    throw new RunRecoveryException(envImpl, "Randomly generated for testing");
	}
    }

    protected void hook449(EnvironmentImpl envImpl) throws DatabaseException {
    }

    protected FileHandle hook450(long fileNum, Long fileId, FileHandle fileHandle)
	    throws LogException, DatabaseException {
	fileHandle = this.hook462(fileNum, fileId, fileHandle);
	return fileHandle;
    }

    protected void hook452(EnvironmentImpl envImpl) throws DatabaseException {
    }

    //protected void hook453(FileHandle fileHandle) throws LogException, DatabaseException {
    //}

    protected void hook454(FileHandle fileHandle) throws LogException, DatabaseException {
    }

    protected void hook456(DbConfigManager configManager) throws DatabaseException {
    }

    protected void hook457(DbConfigManager configManager) throws DatabaseException {
    }

    protected void hook458(long fileNum) throws DatabaseException, IOException {
    }

    protected void hook459(long fileNum) throws DatabaseException, IOException {
    }

    //protected void hook460(long fileNum, Long fileId, FileHandle fileHandle) throws LogException, DatabaseException {
	//fileHandle = this.hook463(fileNum, fileId, fileHandle);
	//this.hook453(fileHandle);
	//if (fileHandle.getFile() == null) {
	 //   this.hook454(fileHandle);
	//} else {
	 //   throw new ReturnObject(fileHandle);
//	}
  //  }

  //  protected void hook461(ByteBuffer data) {
 //   }

 //   protected FileHandle hook462(long fileNum, Long fileId, FileHandle fileHandle)
	//    throws LogException, DatabaseException {
	//fileHandle = makeFileHandle(fileNum, FileMode.READ_MODE);
	//this.hook464(fileId, fileHandle);
	//return fileHandle;
   // }

   // protected FileHandle hook463(long fileNum, Long fileId, FileHandle fileHandle)
	  //  throws LogException, DatabaseException {
	//fileHandle = this.hook450(fileNum, fileId, fileHandle);
	//return fileHandle;
   // }

    //protected void hook464(Long fileId, FileHandle fileHandle) throws LogException, DatabaseException {
    //}

    protected void hook465(LogBuffer fullBuffer, long firstLsn, RandomAccessFile file)
	    throws DatabaseException, ClosedChannelException, IOException {
    }

 //   protected void hook466(LogBuffer fullBuffer, long firstLsn, RandomAccessFile file, ByteBuffer data, IOException IOE)
	//    throws DatabaseException {
	//throw new DatabaseException(IOE);
   // }

    protected void hook467(boolean readOnly) throws DatabaseException {
    }

}
