Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   /*-
    * See the file LICENSE for redistribution information.
    *
    * Copyright (c) 2002, 2013 Oracle and/or its affiliates.  All rights reserved.
    *
    */
   
   package com.sleepycat.je.log;
   
  import java.nio.Buffer;
  
A FileReader is an abstract class that traverses the log files, reading in chunks of the file at a time. Concrete subclasses perform a particular action to each entry.
  
  public abstract class FileReader {
  
      protected final EnvironmentImpl envImpl;
      protected final FileManager fileManager;
  
      /*
       * The ReadWindow is a data buffer that acts as a sliding window view
       * of the log. It is positioned against the log and filled up with data.
       */
      protected final ReadWindow window;
  
      /* 
       * For piecing together a log entry that is read from multiple read buffer
       * calls.
       */
      private ByteBuffer saveBuffer;   
  
      private final boolean singleFile;// if true, do not read across files
  
      protected boolean eof;           // true if at end of the log.
                                       // TODO: assess whether this is redundant 
                                       // with the EOFException, and could be
                                       // streamlined.
  
      protected final boolean forward;   // if true, we're reading forward
  
      /* stats */
      private int nRead;           // num entries we've seen
  
      /* The log entry header for the entry that was just read. */
      protected LogEntryHeader currentEntryHeader;
  
      /*
       * The log entry before the current entry. In general,
       * currentEntryPrevOffset is the same as
       * currentEntryHeader.getPrevOffset(), but it's initialized and used before
       * a header is read. Only used for backward scanning.
       */
      protected long currentEntryPrevOffset;
  
      /*
       * nextEntryOffset is used to set the currentEntryOffset after we've read
       * an entry. Only used for forward scanning.
       */
      protected long currentEntryOffset;
      protected long nextEntryOffset;
      protected long startLsn;  // We start reading from this LSN.
      private final long finishLsn// If going backwards, read up to this LSN.
  
      /* For checking checksum on the read. */
      protected ChecksumValidator cksumValidator;
      private boolean doChecksumOnRead;       // Validate checksums
      private boolean alwaysValidateChecksum// Validate for all entry types
  
      protected final Logger logger;

    
A FileReader just needs to know what size chunks to read in.

Parameters:
endOfFileLsn indicates the end of the log file
  
      public FileReader(EnvironmentImpl envImpl,
                        int readBufferSize,
                        boolean forward,
                        long startLsn,
                        Long singleFileNumber,
                        long endOfFileLsn,
                        long finishLsn)
          throws DatabaseException {
  
          this. = envImpl;
          this. = envImpl.getFileManager();
         this. = (singleFileNumber != null);
         this. = forward;
 
         this. = envImpl.getLogManager().getChecksumOnRead();
         if (this.) {
              = new ChecksumValidator();
         }
 
          = makeWindow(readBufferSize);
          = ByteBuffer.allocate(readBufferSize);
 
         /* stats */
          = 0;
 
         /* Determine the starting position. */
         this. = startLsn;
         this. = finishLsn;
 
          = envImpl.getLogger();
 
         initStartingPosition(endOfFileLsnsingleFileNumber);
     }

    
May be overridden by other FileReaders.

 
     protected ReadWindow makeWindow(int readBufferSize
         throws DatabaseException {
         
         return new ReadWindow(readBufferSize);
     }

    
Helper for determining the starting position and opening up a file at the desired location.
 
     protected void initStartingPosition(long endOfFileLsn,
                                         Long ignoreSingleFileNumber) {
          = false;
         if () {
 
             /*
              * Start off at the startLsn. If that's null, start at the
              * beginning of the log. If there are no log files, set eof.
              */
             if ( != .) {
                 .initAtFileStart();
             } else {
                 Long firstNum = .getFirstFileNum();
                 if (firstNum == null) {
                      = true;
                 } else {
                     .initAtFileStart(DbLsn.makeLsn(firstNum, 0));
                 }
             }
 
             /*
              * After we read the first entry, the currentEntry will point here.
              */
              = .getEndOffset();
         } else {
 
             /*
              * Make the read buffer look like it's positioned off the end of
              * the file. Initialize the first LSN we want to read. When
              * traversing the log backwards, we always start at the very end.
              */
             assert  != .;
             .initAtFileStart(endOfFileLsn);
 
             /*
              * currentEntryPrevOffset points to the entry we want to start out
              * reading when going backwards. If it's 0, the entry we want to
              * read is in a different file.
              */
             if (DbLsn.getFileNumber() ==
                 DbLsn.getFileNumber(endOfFileLsn)) {
                  = DbLsn.getFileOffset();
             } else {
                  = 0;
             }
              = DbLsn.getFileOffset(endOfFileLsn);
         }
     }

    
Whether to always validate the checksum, even for non-target entries.
 
     public void setAlwaysValidateChecksum(boolean validate) {
          = validate;
     }

    

Returns:
the number of entries processed by this reader.
 
     public int getNumRead() {
         return ;
     }
 
     public long getNRepeatIteratorReads() {
         return .getNRepeatIteratorReads();
     }

    
Get LSN of the last entry read.
 
     public long getLastLsn() {
         return DbLsn.makeLsn(.currentFileNum(), );
     }

    
Returns the total size (including header) of the last entry read.
 
     public int getLastEntrySize() {
         return .getSize() + .getItemSize();
     }

    
Scans the log files until either it has reached the end of the log or has hit an invalid portion.

Returns:
true if an element has been read, false at end-of-log.
Throws:
com.sleepycat.je.EnvironmentFailureException if a ChecksumException, FileNotFoundException, or another internal problem occurs.
 
     public boolean readNextEntry() {
         try {
             return readNextEntryAllowExceptions();
         } catch (FileNotFoundException e) {
             throw new EnvironmentFailureException
                 (,
                  .e);
         } catch (ChecksumException e) {
             throw new EnvironmentFailureException
                 (.e);
         }
     }

    
Variant of readNextEntry that throws FileNotFoundException and ChecksumException, rather than wrapping them in an EnvironmentFailureException and invalidating the enviornment. This allows users of this class (see cleaner.FileProcessor), and subclasses that override readNextEntry (see ScavengerFileReader and LastFileReader), to handle these exceptions specially.
 
     public final boolean readNextEntryAllowExceptions()
         throws FileNotFoundExceptionChecksumException {
 
         boolean foundEntry = false;
         try {
             while ((!) && (!foundEntry)) {
 
                 /* Read the invariant portion of the next header. */
                 getLogEntryInReadBuffer();
                 ByteBuffer dataBuffer =
                     readData(.,
                              true); // collectData
 
                 readBasicHeader(dataBuffer);
 
                 boolean isTarget;
                 boolean isChecksumTarget;
 
                 if (.isVariableLength()) {
 
                     /*
                      * For all variable length entries, init the checksum w/the
                      * invariant portion of the header, before we know whether
                      * the entry is a target for this reader.  This has
                      * to be done before we read the variable portion of the
                      * header, because readData() only guarantees that it
                      * returns a dataBuffer that contains the next bytes that
                      * are needed, and has no guarantee that it holds any bytes
                      * that were previously read. The act of calling
                      * readData() to obtain the optional portion may reset the
                      * dataBuffer, and nudge the invariant part of the header
                      * out of the buffer returned by readData()
                      */
                     startChecksum(dataBuffer);
 
                     int optionalPortionLen =
                         .getVariablePortionSize();
                     /* Load the optional part of the header into a buffer. */
                     dataBuffer = readData(optionalPortionLentrue);
 
                     /*
                      * Add to checksum while the buffer is positioned at
                      * the start of the new bytes.
                      */
                     addToChecksum(dataBufferoptionalPortionLen);
 
                     /* Now read the optional bytes. */
                     .readVariablePortion(dataBuffer);
 
                     isTarget = isTargetEntry();
                     isChecksumTarget = (isTarget || );
                 } else {
                     isTarget = isTargetEntry();
                     isChecksumTarget = (isTarget || );
                     startChecksum(dataBufferisChecksumTarget);
                 }
 
                 boolean collectData = (isChecksumTarget && ) ||
                     isTarget;
 
                 /*
                  * Read in the body of the next entry. Note that even if this
                  * isn't a targeted entry, we have to move the buffer position
                  * along.
                  */
                 dataBuffer = readData(.getItemSize(),
                                       collectData);
 
                 /*
                  * We've read an entry. Move up our offsets if we're moving
                  * forward. If we're moving backwards, we set our offset before
                  * we read the header, because we knew where the entry started.
                  */
                 if () {
                      = ;
                      +=
                         .getSize() +       // header size
                         .getItemSize();    // item size
                 }
 
                 /* Validate the log entry checksum. */
                 validateChecksum(dataBufferisChecksumTarget);
                 
                 if (isTarget) {
 
                     /*
                      * For a target entry, call the subclass reader's
                      * processEntry method to do whatever we need with the
                      * entry.  It returns true if this entry is one that should
                      * be returned.  Note that some entries, although targeted
                      * and read, are not returned.
                      */
                     if (processEntry(dataBuffer)) {
                         foundEntry = true;
                         ++;
                     }
                 } else if (collectData) {
 
                     /*
                      * For a non-target entry that was validated, the buffer is
                      * positioned at the start of the entry; skip over it.
                      */
                     skipEntry(dataBuffer);
                 }
             }
         } catch (EOFException e) {
              = true;
         } catch (DatabaseException e) {
              = true;
             /* Report on error. */
             reportProblem(e);
             throw e;
         }
 
         return foundEntry;
     }

    
May be called by processEntry when it determines that the entry does not need to be read/de-serialized.
 
     protected void skipEntry(ByteBuffer entryBuffer) {
         threadSafeBufferPosition
             (entryBuffer,
              threadSafeBufferPosition(entryBuffer) +
              .getItemSize());
     }
 
     private void reportProblem(Exception e) {
 
         StringBuilder sb = new StringBuilder();
         sb.append("Halted log file reading at file 0x").
             append(Long.toHexString(.currentFileNum())).
             append(" offset 0x").
             append(Long.toHexString()).
             append(" offset(decimal)=").
             append().
             append(" prev=0x").
             append(Long.toHexString());
 
             if ( != null) {
                 LogEntryType problemType =
                     LogEntryType.findType(.getType());
             sb.append(":\nentry=").
                 append(problemType).
                 append("type=").
                 append(.getType()).
                 append(",version=").
                 append(.getVersion()).
                 append(")\nprev=0x").
                 append(Long.toHexString()).
                 append("\nsize=").
                 append(.getItemSize()).
                 append("\nNext entry should be at 0x").
                 append(Long.toHexString( +
                                                .getSize() +
                                         .getItemSize()));
         }
 
         LoggerUtils.traceAndLogException
             ("FileReader""readNextEntry"sb.toString(), e);
     }

    
Make sure that the start of the target log entry is in the header.
 
     private void getLogEntryInReadBuffer()
         throws ChecksumException,
                EOFException,
                FileNotFoundException,
                DatabaseException {
 
         /*
          * If we're going forward, because we read every byte sequentially,
          * we're always sure the read buffer is positioned at the right spot.
          * If we go backwards, we need to jump the buffer position. These
          * methods may be overridden by subclasses.
          */
         if () {
             setForwardPosition();
         } else {
             setBackwardPosition();
         }
     }

    
Ensure that the next target is in the window. The default behavior is that the next target is the next, following entry, so we can assume that it's in the window. All we have to do is to check if we've gone past the specified end point.

 
     protected void setForwardPosition() 
         throws EOFException,
                DatabaseException
                ChecksumException
                FileNotFoundException {
 
         if ( != .) {
             /* The next log entry has passed the end LSN. */
             long nextLsn = DbLsn.makeLsn(.currentFileNum(),
                                          );
             if (DbLsn.compareTo(nextLsn) >= 0) {
                 throw new EOFException();
             }
         }
     }

    
Ensure that the next target is in the window. The default behavior is that the next target is the next previous entry.

 
     protected void setBackwardPosition() 
         throws ChecksumException,
                FileNotFoundException,
                EOFException,
                DatabaseException {
 
         /*
          * currentEntryPrevOffset is the entry before the current entry.
          * currentEntryOffset is the entry we just read (or the end of the
          * file if we're starting out.
          */
         if (( != 0) &&
             .containsOffset()) {
 
             /* The next log entry has passed the start LSN. */
             long nextLsn = DbLsn.makeLsn(.currentFileNum(),
                                          );
             if ( != .) {
                 if (DbLsn.compareTo(nextLsn) == -1) {
                     throw new EOFException("finish=" + 
                                            DbLsn.getNoFormatString() +
                                            "next=" + 
                                            DbLsn.getNoFormatString(nextLsn));
                 }
             }
 
             /* This log entry starts in this buffer, just reposition. */
             .positionBuffer();
         } else {
 
             /*
              * The start of the log entry is not in this read buffer so
              * we must fill the buffer again. 
              *
              * 1) The target log entry is in a different file from the
              * current window's file. Move the window to the previous
              * file and start the read from the target LSN.
              *
              * 2) The target log entry is the same file but the log entry
              * is larger than the read chunk size. Start the next read
              * buffer from the target LSN. It's going to take multiple
              * reads to get the log entry, and we might as well get as
              * much as possible.
              *
              * 3) In the same file, and the log entry fits within one
              * read buffer. Try to position the next buffer chunk so the
              * target entry is held within the buffer, all the way at the
              * end. That way, since we're reading backwards, there will be
              * more buffered data available for following reads.
              */
             long nextFile;
             long nextWindowStart;
             long nextTarget;
 
             if ( == 0) {
                 /* Case 1: Go to another file. */
                  = .getFileHeaderPrevOffset
                     (.currentFileNum());
 
                 Long prevFileNum =
                     .getFollowingFileNum(.currentFileNum(),
                                                     false);
                 if (prevFileNum == null) {
                     throw new EOFException("No file following " +
                                            .currentFileNum());
                 }
 
                 if (.currentFileNum() - prevFileNum.longValue() != 1) {
                     handleGapInBackwardsScan(prevFileNum);
                 }
 
                 nextFile = prevFileNum;
                 nextWindowStart = ;
                 nextTarget = ;
             } else if (( - ) >
                        .capacity()) {
 
                 /*
                  * Case 2: The entry is in the same file, but is bigger
                  * than one buffer. Position it at the front of the buffer.
                  */
                 nextFile = .currentFileNum();
                 nextWindowStart = ;
                 nextTarget = ;
             } else {
 
                 /* 
                  * Case 3: In same file, but not in this buffer. The target
                  * entry will fit in one buffer.
                  */
                 nextFile = .currentFileNum();
                 long newPosition =  -
                     .capacity();
                 nextWindowStart = (newPosition < 0) ? 0 : newPosition;
                 nextTarget = ;
             }
 
             /* The next log entry has passed the start LSN. */
             long nextLsn = DbLsn.makeLsn(nextFile,
                                          );
             if ( != .) {
                 if (DbLsn.compareTo(nextLsn) == -1) {
                     throw new EOFException("finish=" + 
                                            DbLsn.getNoFormatString() +
                                            " next=" +
                                            DbLsn.getNoFormatString(nextLsn));
                 }
             }
 
             .slideAndFill
                 (nextFilenextWindowStartnextTarget);
         }
 
         /* The current entry will start at this offset. */
     }

    
Read the basic log entry header, leaving the buffer mark at the beginning of the checksummed header data.
 
     private void readBasicHeader(ByteBuffer dataBuffer)
         throws ChecksumExceptionDatabaseException  {
 
         /* Read the header for this entry. */
          = new LogEntryHeader(dataBuffer.);
 
         /*
          * currentEntryPrevOffset is a separate field, and is not obtained
          * directly from the currentEntryHeader, because it is initialized and
          * used before any log entry was read.
          */
     }

    
Reset the checksum validator and add the new header bytes. Assumes that the data buffer is positioned just past the end of the invariant portion of the log entry header.

 
     private void startChecksum(ByteBuffer dataBuffer
         throws ChecksumException {
 
         startChecksum(dataBuffertrue  /* isChecksumTarget */);
     }
 
     private void startChecksum(ByteBuffer dataBuffer
                                boolean isChecksumTarget)
         throws ChecksumException {
 
         if (!) {
             return;
         }
 
         if (!isChecksumTarget) {
             return;
         }
 
         /* Clear out any previous data. */
         .reset();
 
         int originalPosition = threadSafeBufferPosition(dataBuffer);
         if (.isInvisible()) {
 
             /* 
              * Turn off invisibility so that the checksum will succeed. When
              * entries are made invisible, the checksum is not adjusted. Note
              * that the dataBuffer can leave the invisible bit transformed,
              * because the header has already been initialized, and this data
              * will never be read again.
              */
             LogEntryHeader.turnOffInvisible(dataBufferoriginalPosition -
                                             .);
         }
 
         /* Position the buffer at the start of the data, after the checksum. */
         int headerSizeMinusChecksum =
         int entryTypeStart = originalPosition - headerSizeMinusChecksum;
         threadSafeBufferPosition(dataBufferentryTypeStart);
 
         /* Load the validate with the header bytes. */
         .update(dataBufferheaderSizeMinusChecksum);
 
         /* Move the data buffer back to the original position. */
         threadSafeBufferPosition(dataBufferoriginalPosition);
     }
 
     private void addToChecksum(ByteBuffer dataBufferint length
         throws ChecksumException {
 
         if (!) {
             return;
         }
 
         .update(dataBufferlength);
     }

    
Add the entry bytes to the checksum and check the value. This method must be called with the buffer positioned at the start of the entry.
 
     private void validateChecksum(ByteBuffer dataBuffer
                                   boolean isChecksumTarget)
         throws ChecksumException {
 
         if (!) {
             return;
         }
 
         if (!isChecksumTarget) {
             return;
         }
 
         .update(dataBuffer.getItemSize());
                                 .currentFileNum(),
                                 );
     }

    
Try to read a specified number of bytes.

Parameters:
amountToRead is the number of bytes we need
collectData is true if we need to actually look at the data. If false, we know we're skipping this entry, and all we need to do is to count until we get to the right spot.
Returns:
a byte buffer positioned at the head of the desired portion, or null if we reached eof.
 
     private ByteBuffer readData(int amountToReadboolean collectData)
         throws ChecksumException,
                EOFException,
                FileNotFoundException,
                DatabaseException {
 
         int alreadyRead = 0;
         ByteBuffer completeBuffer = null;
         .clear();
 
         while ((alreadyRead < amountToRead) && !) {
 
             int bytesNeeded = amountToRead - alreadyRead;
             if (.hasRemaining()) {
 
                 /* There's data in the window, process it. */
                 if (collectData) {
 
                     /*
                      * Save data in a buffer for processing.
                      */
                     if ((alreadyRead > 0) ||
                         (.remaining() < bytesNeeded)) {
 
                         /* We need to piece an entry together. */
                         copyToSaveBuffer(bytesNeeded);
                         alreadyRead = threadSafeBufferPosition();
                         completeBuffer = ;
                     } else {
 
                         /* A complete entry is available in this buffer. */
                         completeBuffer = .getBuffer();
                         alreadyRead = amountToRead;
                     }
                 } else {
 
                     /*
                      * We're not processing the data, so need to save it. just
                      * move buffer positions.
                      */
                     int positionIncrement =
                         (.remaining() > bytesNeeded) ?
                         bytesNeeded : .remaining();
 
                     alreadyRead += positionIncrement;
                     .incrementBufferPosition(positionIncrement);
                     completeBuffer = .getBuffer();
                 }
             } else {
 
                 /*
                  * Look for more data.
                  */
                 if (.fillNext(bytesNeeded)) {
                     /* This call to fillNext slid the window to a new file. */
                      = 0;
                 }
             }
         }
 
         /* Flip the save buffer just in case we've been accumulating in it. */
 
         return completeBuffer;
     }
     
     /* Try to skip over a specified number of bytes. */
     public void skipData(int amountToSkip
         throws ChecksumException,
                EOFException,
                FileNotFoundException,
                DatabaseException {
                
         try {
             readData(amountToSkipfalse);
         } catch (DatabaseException e) {
             reportProblem(e);
             throw e;
         }    
     }

    
Copy the required number of bytes into the save buffer.
 
     private void copyToSaveBuffer(int bytesNeeded) {
         /* How much can we get from this current read buffer? */
         int bytesFromThisBuffer;
 
         if (bytesNeeded <= .remaining()) {
             bytesFromThisBuffer = bytesNeeded;
         } else {
             bytesFromThisBuffer = .remaining();
         }
 
         /* Gather it all into this save buffer. */
         ByteBuffer temp;
 
         /* Make sure the save buffer is big enough. */
             bytesFromThisBuffer) {
             /* Grow the save buffer. */
             temp = ByteBuffer.allocate(.capacity() +
                                        bytesFromThisBuffer);
             threadSafeBufferFlip();
             temp.put();
              = temp;
         }
 
         /*
          * Bulk copy only the required section from the read buffer into the
          * save buffer. We need from readBuffer.position() to
          * readBuffer.position() + bytesFromThisBuffer
          */
         temp = .getBuffer().slice();
         temp.limit(bytesFromThisBuffer);
         .put(temp);
         .incrementBufferPosition(bytesFromThisBuffer);
     }

    
Returns the number of reads since the last time this method was called.
 
     public int getAndResetNReads() {
         return .getAndResetNReads();
     }

    

Returns:
true if this reader should process this entry, or just skip over it.
Throws:
com.sleepycat.je.DatabaseException from subclasses.
 
     protected boolean isTargetEntry()
         throws DatabaseException {
 
         return true;
     }

    
Each file reader implements this method to process the entry data.

Parameters:
entryBuffer contains the entry data and is positioned at the data
Returns:
true if this entry should be returned
 
     protected abstract boolean processEntry(ByteBuffer entryBuffer)
         throws DatabaseException;

    
Never seen by user, used to indicate that the file reader should stop.
 
     @SuppressWarnings("serial")
     public static class EOFException extends Exception {
         public EOFException() {
             super();
         }
 
         /* 
          * @param message The message is used to hold debugging 
          * information.
          */
         public EOFException(String message) {
             super(message);
         }
     }

    

Returns:
true if the current entry is part of replication stream.
 
     public boolean entryIsReplicated() {
 
         if ( == null) {
             throw EnvironmentFailureException.unexpectedState
                 ("entryIsReplicated should not be used before reader is " +
                  "initialized");
         } 
         return .getReplicated();
     }

    
Note that we catch Exception here because it is possible that another thread is modifying the state of buffer simultaneously. Specifically, this can happen if another thread is writing this log buffer out and it does (e.g.) a flip operation on it. The actual mark/pos of the buffer may be caught in an unpredictable state. We could add another latch to protect this buffer, but that's heavier weight than we need. So the easiest thing to do is to just retry the duplicate operation. See [#9822].
 
     static Buffer threadSafeBufferFlip(ByteBuffer buffer) {
         while (true) {
             try {
                 return buffer.flip();
             } catch (IllegalArgumentException IAE) {
                 continue;
             }
         }
     }
 
     static int threadSafeBufferPosition(ByteBuffer buffer) {
         while (true) {
             try {
                 return buffer.position();
             } catch (IllegalArgumentException IAE) {
                 continue;
             }
         }
     }
 
     static Buffer threadSafeBufferPosition(ByteBuffer buffer,
                                            int newPosition) {
         assert (newPosition >= 0) : "illegal new position=" + newPosition;
         while (true) {
             try {
                 return buffer.position(newPosition);
             } catch (IllegalArgumentException IAE) {
                 if (newPosition > buffer.capacity()) {
                     throw IAE;
                 }
                 continue;
             }
         }
     }

    
TBW
 
     protected void handleGapInBackwardsScan(long prevFileNum) {
         throw new EnvironmentFailureException
             (,
              .,
              "Cannot read backward over cleaned file" +
              " from " + .currentFileNum() +
              " to " + prevFileNum);
     }

    
A ReadWindow provides a swath of data read from the JE log.
 
     protected static class ReadWindow {
 
         /*
          * fileNum, startOffset and endOffset indicate how the read buffer maps
          * to the JE log. For example, if the read buffer size is 200 and the
          * read buffer was filled from file 9, starting at byte 100, then:
          *          fileNum = 9
          *          startOffset = 100
          *          endOffset = 300
          * Note that the end point is not inclusive; endOffset is > the
          * readBuffer's end.
          */
         private long fileNum;      // file number we're pointing to
         private int logVersion;    // log version for fileNum/readBuffer
         protected long startOffset;// file offset that maps to buf start
         protected long endOffset;  // file offset that maps to buf end
         protected ByteBuffer readBuffer;   // buffer for reading from the file
 
         /* read buffer can't grow larger than this */
         private final int maxReadBufferSize;   
 
         protected final EnvironmentImpl envImpl;
         protected final FileManager fileManager;
 
         /*
          * The number of times we've tried to read in a log entry that was too
          * large for the read buffer.
          */
         private long nRepeatIteratorReads;
 
         /* Number of reads since the last time getAndResetNReads was called. */
         private int nReadOperations;
 
         protected ReadWindow(int readBufferSizeEnvironmentImpl envImpl) {
             DbConfigManager configManager = envImpl.getConfigManager();
              =
                 configManager.getInt(.);
             this. = envImpl;
              = envImpl.getFileManager();
 
              = ByteBuffer.allocate(readBufferSize);
             threadSafeBufferFlip();
         }
 
         /* 
          * Position this window at this LSN, but leave it empty, it has no data
          * yet.
          */
         public void initAtFileStart(long startLsn) {
             setFileNum(DbLsn.getFileNumber(startLsn),
                        .);
              = DbLsn.getFileOffset(startLsn);
              = ;
         }
 
         public long getEndOffset() {
             return ;
         }        

        
Ensure that whenever we change the fileNum, the logVersion is also updated. The fileNum and logVersion fields should be kept private.
 
         protected void setFileNum(final long fileNumfinal int logVersion) {
             this. = fileNum;
             this. = logVersion;
         }
 
         public long currentFileNum() {
             return ;
        }
        /* Return true if this offset is contained with the readBuffer. */
        boolean containsOffset(long targetOffset) {
            return (targetOffset >= ) &&
                (targetOffset < );
        }
        /* Return true if this lsn  is contained with the readBuffer. */
        public boolean containsLsn(long targetFileNumberlong targetOffset) {
            return (( == targetFileNumber) &&
                    containsOffset(targetOffset));
        }
        /* Position the readBuffer to the targetOffset. */
        public void positionBuffer(long targetOffset) {
            assert containsOffset(targetOffset) : this + " doesn't contain " +
                DbLsn.getNoFormatString(targetOffset);
            threadSafeBufferPosition(,
                                     (int) (targetOffset - ));
        }
        /* Move the readBuffer position up by the given increment. */
        void incrementBufferPosition(int increment) {
            int currentPosition = threadSafeBufferPosition();
            threadSafeBufferPosition(,
                                     currentPosition + increment);
        }
        /* 
         * Reposition to the specified file, and fill starting at
         * startOffset. Position the window's buffer to point at the log entry
         * indicated by targetOffset
         */
        public void slideAndFill(long windowfileNum
                                 long windowStartOffset
                                 long targetOffset,
                                 boolean forward)
            throws ChecksumException,
                   FileNotFoundException,
                   DatabaseException {
            FileHandle fileHandle = .getFileHandle(windowfileNum);
            try {
                 = windowStartOffset;
                setFileNum(windowfileNumfileHandle.getLogVersion());
                boolean foundData = fillFromFile(fileHandletargetOffset);
                /*
                 * When reading backwards, we need to guarantee there is no log
                 * gap, throws out an EnvironmentFailreException if it exists.
                 */
                if (!foundData && !forward) {
                    throw EnvironmentFailureException.unexpectedState
                        ("Detected a log file gap when reading backwards. " + 
                         "Target position = " + DbLsn.getNoFormatString
                         (DbLsn.makeLsn(windowfileNumtargetOffset)) +
                         " starting position = " + DbLsn.getNoFormatString
                         (DbLsn.makeLsn(windowfileNumwindowStartOffset)) +
                         " end position = " + DbLsn.getNoFormatString
                         (DbLsn.makeLsn(windowfileNum)));
                }
            } finally {
                fileHandle.release();
            }
        }

        
Fill up the read buffer with more data, moving along to the following file (next largest number) if needed.

Returns:
true if the fill moved us to a new file.
        protected boolean fillNext(boolean singleFileint bytesNeeded)
            throws ChecksumException,
                   FileNotFoundException,
                   EOFException,
                   DatabaseException {
            adjustReadBufferSize(bytesNeeded);
            FileHandle fileHandle = null;
            try {
                /* Get a file handle to read in more log. */
                fileHandle = .getFileHandle();
                /*
                 * Check to see if we've come to the end of the file.  If so,
                 * get the next file.
                 */
                 = ;
                if (fillFromFile(fileHandle)) {
                    /* 
                     * Successfully filled the read buffer, but didn't move to 
                     * a new file. 
                     */
                    return false
                }
                /* This file is done -- can we read in the next file? */
                if (singleFile) {
                    throw new EOFException("Single file only");
                }
                Long nextFile = 
                    .getFollowingFileNum(
                                                    true /* forward */);
                if (nextFile == null) {
                    throw new EOFException();
                }
                    
                fileHandle.release();
                fileHandle = null;
                fileHandle = .getFileHandle(nextFile);
                setFileNum(nextFilefileHandle.getLogVersion());
                 = 0;
                fillFromFile(fileHandle, 0);
                return true;
            } finally {
                if (fileHandle != null) {
                    fileHandle.release();
                }
            }
        }
        /* 
         * Assume that the window is properly positioned. Try to fill the read
         * buffer with data from this file handle, starting at the location
         * indicated by the starting offset field. If this file contains more
         * data, return true. If this file doesn't contain more data, return
         * false.
         *
         * In all cases, leave the the read buffer pointing at the target
         * offset and in a state that's ready to support reads, even if there
         * is nothing in the buffer. Note that the target offset, which may not
         * be the same as starting offset.
         * @return true if more data was read, false if not.         
         */
        protected boolean fillFromFile(FileHandle fileHandle
                                       long targetOffset
            throws DatabaseException {
            boolean foundData = false;
            .clear();
            if (.readFromFile(fileHandle.getFile(), 
                                         ,
                                         ,
                                         fileHandle.getFileNum(),
                                         false /* dataKnownToBeInFile */)) {
                foundData = true;
                 += 1;
                /*
                 * Ensure that fileNum and logVersion are in sync.  setFileNum
                 * handles changes in the file number.  But we must also update
                 * the logVersion here to handle the first read after we
                 * initialize fileNum and logVersion is unknown.
                 */
                 = fileHandle.getLogVersion();
            }
            /* 
             * In all cases, setup read buffer for valid reading. If the buffer
             * has no data, it will be positioned at the beginning, and will be
             * able to correctly return the fact that there is no data present.
             */
            threadSafeBufferFlip();
            threadSafeBufferPosition(
                                     (int) (targetOffset - ));
            return foundData;
        }

        
Change the read buffer size if we start hitting large log entries so we don't get into an expensive cycle of multiple reads and piecing together of log entries.
        protected void adjustReadBufferSize(int amountToRead) {
            int readBufferSize = .capacity();
            /* 
             * We need to read something larger than the current buffer
             * size. 
             */
            if (amountToRead > readBufferSize) {
                /* We're not at the max yet. */
                if (readBufferSize < ) {
                    /*
                     * Make the buffer the minimum of amountToRead or a
                     * maxReadBufferSize.
                     */
                    if (amountToRead < ) {
                        readBufferSize = amountToRead;
                        /* Make it a multiple of 1K. */
                        int remainder = readBufferSize % 1024;
                        readBufferSize += 1024 - remainder;
                        readBufferSize = Math.min(readBufferSize,
                                                  );
                    } else {
                        readBufferSize = ;
                    }
                     = ByteBuffer.allocate(readBufferSize);
                }
                if (amountToRead > .capacity()) {
                    ++;
                }
            }
        }
        int capacity() {
            return .capacity();
        }
        int remaining() {
            return .remaining();
        }
        boolean hasRemaining() {
            return .hasRemaining();
        }
        ByteBuffer getBuffer() {
            return ;
        }

        
Returns the number of reads since the last time this method was called.
        int getAndResetNReads() {
            int tmp = ;
             = 0;
            return tmp;
        }
        long getNRepeatIteratorReads() {
            return ;
        }
        @Override
        public String toString() {