/**
   * @param directory
   * @param journalSizeThresholdInBytes Size of the current journal file beyond which it is closed
   *     and a new one started. Zero indicates no size threshold. This is useful journal backup
   *     purposes.
   * @param journalAgeThresholdInMillis Age of the current journal file beyond which it is closed
   *     and a new one started. Zero indicates no age threshold. This is useful journal backup
   *     purposes.
   */
  public PersistentJournal(
      PrevaylerDirectory directory,
      long journalSizeThresholdInBytes,
      long journalAgeThresholdInMillis,
      String journalSuffix,
      Monitor monitor)
      throws IOException {
    PrevaylerDirectory.checkValidJournalSuffix(journalSuffix);

    _monitor = monitor;
    _directory = directory;
    _directory.produceDirectory();
    _journalSizeThresholdInBytes = journalSizeThresholdInBytes;
    _journalAgeThresholdInMillis = journalAgeThresholdInMillis;
    _journalSuffix = journalSuffix;
  }
  private long recoverPendingTransactions(
      TransactionSubscriber subscriber, long initialTransaction, File initialJournal)
      throws IOException {
    long recoveringTransaction = PrevaylerDirectory.journalVersion(initialJournal);
    File journal = initialJournal;
    DurableInputStream input = new DurableInputStream(journal, _monitor);

    while (true) {
      try {
        Chunk chunk = input.readChunk();

        if (recoveringTransaction >= initialTransaction) {
          if (!journal.getName().endsWith(_journalSuffix)) {
            throw new IOException(
                "There are transactions needing to be recovered from "
                    + journal
                    + ", but only "
                    + _journalSuffix
                    + " files are supported");
          }

          TransactionTimestamp entry = TransactionTimestamp.fromChunk(chunk);

          if (entry.systemVersion() != recoveringTransaction) {
            throw new IOException(
                "Expected " + recoveringTransaction + " but was " + entry.systemVersion());
          }

          subscriber.receive(entry);
        }

        recoveringTransaction++;

      } catch (EOFException eof) {
        File nextFile = _directory.journalFile(recoveringTransaction, _journalSuffix);
        if (journal.equals(nextFile))
          PrevaylerDirectory.renameUnusedFile(
              journal); // The first transaction in this log file is incomplete. We need to reuse
                        // this file name.
        journal = nextFile;
        if (!journal.exists()) break;
        input = new DurableInputStream(journal, _monitor);
      }
    }
    return recoveringTransaction;
  }
 private DurableOutputStream createOutputJournal(long transactionNumber) {
   File file = _directory.journalFile(transactionNumber, _journalSuffix);
   try {
     return new DurableOutputStream(file);
   } catch (IOException iox) {
     handle(iox, file, "creating");
     return null;
   }
 }
  /**
   * IMPORTANT: This method cannot be called while the log() method is being called in another
   * thread. If there are no journal files in the directory (when a snapshot is taken and all
   * journal files are manually deleted, for example), the initialTransaction parameter in the first
   * call to this method will define what the next transaction number will be. We have to find
   * clearer/simpler semantics.
   */
  public void update(TransactionSubscriber subscriber, long initialTransactionWanted)
      throws IOException, ClassNotFoundException {
    File initialJournal = _directory.findInitialJournalFile(initialTransactionWanted);

    if (initialJournal == null) {
      initializeNextTransaction(initialTransactionWanted, 1);
      return;
    }

    long nextTransaction =
        recoverPendingTransactions(subscriber, initialTransactionWanted, initialJournal);

    initializeNextTransaction(initialTransactionWanted, nextTransaction);
  }