001package net.kreatious.pianoleopard.history; 002 003import java.io.File; 004import java.io.IOException; 005import java.io.RandomAccessFile; 006import java.nio.channels.FileChannel; 007import java.nio.channels.FileLock; 008import java.util.Optional; 009 010import net.kreatious.pianoleopard.midi.InputModel; 011import net.kreatious.pianoleopard.midi.OutputModel; 012 013/** 014 * Provides a history of user events. 015 * 016 * @author Jay-R Studer 017 */ 018public class History { 019 private final Optional<LogWriter> writer; 020 021 private History(Optional<LogWriter> writer) { 022 this.writer = writer; 023 } 024 025 /** 026 * Creates a new {@link History} that manages historical events generated by 027 * the user. 028 * 029 * @param log 030 * the log file to read and write from. 031 * @param outputModel 032 * the {@link OutputModel} to listen to 033 * @param inputModel 034 * the {@link InputModel} to listen to 035 * @return a new functional {@link History} object if the log file can be 036 * written to, otherwise a new {@link History} object that does 037 * nothing. 038 */ 039 public static History create(File log, OutputModel outputModel, InputModel inputModel) { 040 try { 041 // channel is closed when output model is closed 042 @SuppressWarnings("resource") 043 final FileChannel channel = new RandomAccessFile(log, "rw").getChannel(); 044 outputModel.addCloseable(channel); 045 046 final Optional<FileLock> lock = Optional.ofNullable(channel.tryLock()); 047 if (!lock.isPresent()) { 048 return new History(Optional.empty()); 049 } 050 051 return new History(LogWriter.create(channel, outputModel, inputModel)); 052 } catch (final IOException e) { 053 e.printStackTrace(); 054 return new History(Optional.empty()); 055 } 056 } 057 058 /** 059 * Starts parsing the log file with the specified visitor, if the log file 060 * exists. 061 * <p> 062 * This method blocks until the log file is completely parsed, during which 063 * time visitor methods are called from the context of the current thread. 064 * After this method has returned, the visitor's methods will be called from 065 * various threads. It is guaranteed that two visitor's methods will not be 066 * concurrently executed. 067 * <p> 068 * If the log file is unavailable, this method returns immediately and no 069 * methods on the visitor will be called. 070 * 071 * @param visitor 072 * the visitor to register 073 * @return nothing. Used to cause Java to select the callable overload when 074 * used in a lambda expression. 075 * @throws IOException 076 * if an I/O error occurs 077 */ 078 public Void startReading(HistoryVisitor visitor) throws IOException { 079 if (writer.isPresent()) { 080 writer.get().startReading(visitor); 081 } 082 return null; 083 } 084 085 /** 086 * Stops reading the log file with the specified visitor. 087 * 088 * @param visitor 089 * the visitor to unregister 090 */ 091 public void stopReading(HistoryVisitor visitor) { 092 writer.ifPresent(w -> w.stopReading(visitor)); 093 } 094}