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}