Tailer.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.io.input;

  18. import static org.apache.commons.io.IOUtils.CR;
  19. import static org.apache.commons.io.IOUtils.EOF;
  20. import static org.apache.commons.io.IOUtils.LF;

  21. import java.io.ByteArrayOutputStream;
  22. import java.io.Closeable;
  23. import java.io.File;
  24. import java.io.FileNotFoundException;
  25. import java.io.IOException;
  26. import java.io.RandomAccessFile;
  27. import java.nio.charset.Charset;
  28. import java.nio.file.Files;
  29. import java.nio.file.LinkOption;
  30. import java.nio.file.Path;
  31. import java.nio.file.attribute.FileTime;
  32. import java.time.Duration;
  33. import java.util.Arrays;
  34. import java.util.Objects;
  35. import java.util.concurrent.ExecutorService;
  36. import java.util.concurrent.Executors;

  37. import org.apache.commons.io.IOUtils;
  38. import org.apache.commons.io.ThreadUtils;
  39. import org.apache.commons.io.build.AbstractOrigin;
  40. import org.apache.commons.io.build.AbstractStreamBuilder;
  41. import org.apache.commons.io.file.PathUtils;
  42. import org.apache.commons.io.file.attribute.FileTimes;

  43. /**
  44.  * Simple implementation of the Unix "tail -f" functionality.
  45.  * <p>
  46.  * To build an instance, use {@link Builder}.
  47.  * </p>
  48.  * <h2>1. Create a TailerListener implementation</h2>
  49.  * <p>
  50.  * First you need to create a {@link TailerListener} implementation; ({@link TailerListenerAdapter} is provided for
  51.  * convenience so that you don't have to implement every method).
  52.  * </p>
  53.  * <p>
  54.  * For example:
  55.  * </p>
  56.  * <pre>
  57.  * public class MyTailerListener extends TailerListenerAdapter {
  58.  *     public void handle(String line) {
  59.  *         System.out.println(line);
  60.  *     }
  61.  * }
  62.  * </pre>
  63.  * <h2>2. Using a Tailer</h2>
  64.  * <p>
  65.  * You can create and use a Tailer in one of three ways:
  66.  * </p>
  67.  * <ul>
  68.  * <li>Using a {@link Builder}</li>
  69.  * <li>Using an {@link java.util.concurrent.Executor}</li>
  70.  * <li>Using a {@link Thread}</li>
  71.  * </ul>
  72.  * <p>
  73.  * An example of each is shown below.
  74.  * </p>
  75.  * <h3>2.1 Using a Builder</h3>
  76.  * <pre>
  77.  * TailerListener listener = new MyTailerListener();
  78.  * Tailer tailer = Tailer.builder()
  79.  *   .setFile(file)
  80.  *   .setTailerListener(listener)
  81.  *   .setDelayDuration(delay)
  82.  *   .get();
  83.  * </pre>
  84.  * <h3>2.2 Using an Executor</h3>
  85.  * <pre>
  86.  * TailerListener listener = new MyTailerListener();
  87.  * Tailer tailer = new Tailer(file, listener, delay);
  88.  *
  89.  * // stupid executor impl. for demo purposes
  90.  * Executor executor = new Executor() {
  91.  *     public void execute(Runnable command) {
  92.  *         command.run();
  93.  *     }
  94.  * };
  95.  *
  96.  * executor.execute(tailer);
  97.  * </pre>
  98.  * <h3>2.3 Using a Thread</h3>
  99.  * <pre>
  100.  * TailerListener listener = new MyTailerListener();
  101.  * Tailer tailer = new Tailer(file, listener, delay);
  102.  * Thread thread = new Thread(tailer);
  103.  * thread.setDaemon(true); // optional
  104.  * thread.start();
  105.  * </pre>
  106.  * <h2>3. Stopping a Tailer</h2>
  107.  * <p>
  108.  * Remember to stop the tailer when you have done with it:
  109.  * </p>
  110.  * <pre>
  111.  * tailer.stop();
  112.  * </pre>
  113.  * <h2>4. Interrupting a Tailer</h2>
  114.  * <p>
  115.  * You can interrupt the thread a tailer is running on by calling {@link Thread#interrupt()}.
  116.  * </p>
  117.  * <pre>
  118.  * thread.interrupt();
  119.  * </pre>
  120.  * <p>
  121.  * If you interrupt a tailer, the tailer listener is called with the {@link InterruptedException}.
  122.  * </p>
  123.  * <p>
  124.  * The file is read using the default Charset; this can be overridden if necessary.
  125.  * </p>
  126.  *
  127.  * @see Builder
  128.  * @see TailerListener
  129.  * @see TailerListenerAdapter
  130.  * @since 2.0
  131.  * @since 2.5 Updated behavior and documentation for {@link Thread#interrupt()}.
  132.  * @since 2.12.0 Add {@link Tailable} and {@link RandomAccessResourceBridge} interfaces to tail of files accessed using
  133.  *        alternative libraries such as jCIFS or <a href="https://commons.apache.org/proper/commons-vfs/">Apache Commons
  134.  *        VFS</a>.
  135.  */
  136. public class Tailer implements Runnable, AutoCloseable {

  137.     // @formatter:off
  138.     /**
  139.      * Builds a new {@link Tailer}.
  140.      *
  141.      * <p>
  142.      * For example:
  143.      * </p>
  144.      * <pre>{@code
  145.      * Tailer t = Tailer.builder()
  146.      *   .setPath(path)
  147.      *   .setCharset(StandardCharsets.UTF_8)
  148.      *   .setDelayDuration(Duration.ofSeconds(1))
  149.      *   .setExecutorService(Executors.newSingleThreadExecutor(Builder::newDaemonThread))
  150.      *   .setReOpen(false)
  151.      *   .setStartThread(true)
  152.      *   .setTailable(tailable)
  153.      *   .setTailerListener(tailerListener)
  154.      *   .setTailFromEnd(false)
  155.      *   .get();}
  156.      * </pre>
  157.      *
  158.      * @see #get()
  159.      * @since 2.12.0
  160.      */
  161.     // @formatter:on
  162.     public static class Builder extends AbstractStreamBuilder<Tailer, Builder> {

  163.         private static final Duration DEFAULT_DELAY_DURATION = Duration.ofMillis(DEFAULT_DELAY_MILLIS);

  164.         /**
  165.          * Creates a new daemon thread.
  166.          *
  167.          * @param runnable the thread's runnable.
  168.          * @return a new daemon thread.
  169.          */
  170.         private static Thread newDaemonThread(final Runnable runnable) {
  171.             final Thread thread = new Thread(runnable, "commons-io-tailer");
  172.             thread.setDaemon(true);
  173.             return thread;
  174.         }

  175.         private Tailable tailable;
  176.         private TailerListener tailerListener;
  177.         private Duration delayDuration = DEFAULT_DELAY_DURATION;
  178.         private boolean tailFromEnd;
  179.         private boolean reOpen;
  180.         private boolean startThread = true;
  181.         private ExecutorService executorService = Executors.newSingleThreadExecutor(Builder::newDaemonThread);

  182.         /**
  183.          * Constructs a new builder of {@link Tailer}.
  184.          */
  185.         public Builder() {
  186.             // empty
  187.         }

  188.         /**
  189.          * Builds a new {@link Tailer}.
  190.          *
  191.          * <p>
  192.          * This builder uses the following aspects:
  193.          * </p>
  194.          * <ul>
  195.          * <li>{@link #getBufferSize()}</li>
  196.          * <li>{@link #getCharset()}</li>
  197.          * <li>{@link Tailable}</li>
  198.          * <li>{@link TailerListener}</li>
  199.          * <li>delayDuration</li>
  200.          * <li>tailFromEnd</li>
  201.          * <li>reOpen</li>
  202.          * </ul>
  203.          *
  204.          * @return a new instance.
  205.          * @see #getUnchecked()
  206.          */
  207.         @Override
  208.         public Tailer get() {
  209.             final Tailer tailer = new Tailer(tailable, getCharset(), tailerListener, delayDuration, tailFromEnd, reOpen, getBufferSize());
  210.             if (startThread) {
  211.                 executorService.submit(tailer);
  212.             }
  213.             return tailer;
  214.         }

  215.         /**
  216.          * Sets the delay duration. null resets to the default delay of one second.
  217.          *
  218.          * @param delayDuration the delay between checks of the file for new content.
  219.          * @return {@code this} instance.
  220.          */
  221.         public Builder setDelayDuration(final Duration delayDuration) {
  222.             this.delayDuration = delayDuration != null ? delayDuration : DEFAULT_DELAY_DURATION;
  223.             return this;
  224.         }

  225.         /**
  226.          * Sets the executor service to use when startThread is true.
  227.          *
  228.          * @param executorService the executor service to use when startThread is true.
  229.          * @return {@code this} instance.
  230.          */
  231.         public Builder setExecutorService(final ExecutorService executorService) {
  232.             this.executorService = Objects.requireNonNull(executorService, "executorService");
  233.             return this;
  234.         }

  235.         /**
  236.          * Sets the origin.
  237.          *
  238.          * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
  239.          */
  240.         @Override
  241.         protected Builder setOrigin(final AbstractOrigin<?, ?> origin) {
  242.             setTailable(new TailablePath(origin.getPath()));
  243.             return super.setOrigin(origin);
  244.         }

  245.         /**
  246.          * Sets the re-open behavior.
  247.          *
  248.          * @param reOpen whether to close/reopen the file between chunks
  249.          * @return {@code this} instance.
  250.          */
  251.         public Builder setReOpen(final boolean reOpen) {
  252.             this.reOpen = reOpen;
  253.             return this;
  254.         }

  255.         /**
  256.          * Sets the daemon thread startup behavior.
  257.          *
  258.          * @param startThread whether to create a daemon thread automatically.
  259.          * @return {@code this} instance.
  260.          */
  261.         public Builder setStartThread(final boolean startThread) {
  262.             this.startThread = startThread;
  263.             return this;
  264.         }

  265.         /**
  266.          * Sets the tailable.
  267.          *
  268.          * @param tailable the tailable.
  269.          * @return {@code this} instance.
  270.          */
  271.         public Builder setTailable(final Tailable tailable) {
  272.             this.tailable = Objects.requireNonNull(tailable, "tailable");
  273.             return this;
  274.         }

  275.         /**
  276.          * Sets the listener.
  277.          *
  278.          * @param tailerListener the listener.
  279.          * @return {@code this} instance.
  280.          */
  281.         public Builder setTailerListener(final TailerListener tailerListener) {
  282.             this.tailerListener = Objects.requireNonNull(tailerListener, "tailerListener");
  283.             return this;
  284.         }

  285.         /**
  286.          * Sets the tail start behavior.
  287.          *
  288.          * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
  289.          * @return {@code this} instance.
  290.          */
  291.         public Builder setTailFromEnd(final boolean end) {
  292.             this.tailFromEnd = end;
  293.             return this;
  294.         }
  295.     }

  296.     /**
  297.      * Bridges random access to a {@link RandomAccessFile}.
  298.      */
  299.     private static final class RandomAccessFileBridge implements RandomAccessResourceBridge {

  300.         private final RandomAccessFile randomAccessFile;

  301.         private RandomAccessFileBridge(final File file, final String mode) throws FileNotFoundException {
  302.             randomAccessFile = new RandomAccessFile(file, mode);
  303.         }

  304.         @Override
  305.         public void close() throws IOException {
  306.             randomAccessFile.close();
  307.         }

  308.         @Override
  309.         public long getPointer() throws IOException {
  310.             return randomAccessFile.getFilePointer();
  311.         }

  312.         @Override
  313.         public int read(final byte[] b) throws IOException {
  314.             return randomAccessFile.read(b);
  315.         }

  316.         @Override
  317.         public void seek(final long position) throws IOException {
  318.             randomAccessFile.seek(position);
  319.         }

  320.     }

  321.     /**
  322.      * Bridges access to a resource for random access, normally a file. Allows substitution of remote files for example
  323.      * using jCIFS.
  324.      *
  325.      * @since 2.12.0
  326.      */
  327.     public interface RandomAccessResourceBridge extends Closeable {

  328.         /**
  329.          * Gets the current offset in this tailable.
  330.          *
  331.          * @return the offset from the beginning of the tailable, in bytes, at which the next read or write occurs.
  332.          * @throws IOException if an I/O error occurs.
  333.          */
  334.         long getPointer() throws IOException;

  335.         /**
  336.          * Reads up to {@code b.length} bytes of data from this tailable into an array of bytes. This method blocks until at
  337.          * least one byte of input is available.
  338.          *
  339.          * @param b the buffer into which the data is read.
  340.          * @return the total number of bytes read into the buffer, or {@code -1} if there is no more data because the end of
  341.          *         this tailable has been reached.
  342.          * @throws IOException If the first byte cannot be read for any reason other than end of tailable, or if the random
  343.          *         access tailable has been closed, or if some other I/O error occurs.
  344.          */
  345.         int read(byte[] b) throws IOException;

  346.         /**
  347.          * Sets the file-pointer offset, measured from the beginning of this tailable, at which the next read or write occurs.
  348.          * The offset may be set beyond the end of the tailable. Setting the offset beyond the end of the tailable does not
  349.          * change the tailable length. The tailable length will change only by writing after the offset has been set beyond the
  350.          * end of the tailable.
  351.          *
  352.          * @param pos the offset position, measured in bytes from the beginning of the tailable, at which to set the tailable
  353.          *        pointer.
  354.          * @throws IOException if {@code pos} is less than {@code 0} or if an I/O error occurs.
  355.          */
  356.         void seek(long pos) throws IOException;
  357.     }

  358.     /**
  359.      * A tailable resource like a file.
  360.      *
  361.      * @since 2.12.0
  362.      */
  363.     public interface Tailable {

  364.         /**
  365.          * Creates a random access file stream to read.
  366.          *
  367.          * @param mode the access mode, by default this is for {@link RandomAccessFile}.
  368.          * @return a random access file stream to read.
  369.          * @throws FileNotFoundException if the tailable object does not exist.
  370.          */
  371.         RandomAccessResourceBridge getRandomAccess(String mode) throws FileNotFoundException;

  372.         /**
  373.          * Tests if this tailable is newer than the specified {@link FileTime}.
  374.          *
  375.          * @param fileTime the file time reference.
  376.          * @return true if the {@link File} exists and has been modified after the given {@link FileTime}.
  377.          * @throws IOException if an I/O error occurs.
  378.          */
  379.         boolean isNewer(FileTime fileTime) throws IOException;

  380.         /**
  381.          * Gets the last modification {@link FileTime}.
  382.          *
  383.          * @return See {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}.
  384.          * @throws IOException if an I/O error occurs.
  385.          */
  386.         FileTime lastModifiedFileTime() throws IOException;

  387.         /**
  388.          * Gets the size of this tailable.
  389.          *
  390.          * @return The size, in bytes, of this tailable, or {@code 0} if the file does not exist. Some operating systems may
  391.          *         return {@code 0} for path names denoting system-dependent entities such as devices or pipes.
  392.          * @throws IOException if an I/O error occurs.
  393.          */
  394.         long size() throws IOException;
  395.     }

  396.     /**
  397.      * A tailable for a file {@link Path}.
  398.      */
  399.     private static final class TailablePath implements Tailable {

  400.         private final Path path;
  401.         private final LinkOption[] linkOptions;

  402.         private TailablePath(final Path path, final LinkOption... linkOptions) {
  403.             this.path = Objects.requireNonNull(path, "path");
  404.             this.linkOptions = linkOptions;
  405.         }

  406.         Path getPath() {
  407.             return path;
  408.         }

  409.         @Override
  410.         public RandomAccessResourceBridge getRandomAccess(final String mode) throws FileNotFoundException {
  411.             return new RandomAccessFileBridge(path.toFile(), mode);
  412.         }

  413.         @Override
  414.         public boolean isNewer(final FileTime fileTime) throws IOException {
  415.             return PathUtils.isNewer(path, fileTime, linkOptions);
  416.         }

  417.         @Override
  418.         public FileTime lastModifiedFileTime() throws IOException {
  419.             return Files.getLastModifiedTime(path, linkOptions);
  420.         }

  421.         @Override
  422.         public long size() throws IOException {
  423.             return Files.size(path);
  424.         }

  425.         @Override
  426.         public String toString() {
  427.             return "TailablePath [file=" + path + ", linkOptions=" + Arrays.toString(linkOptions) + "]";
  428.         }
  429.     }

  430.     private static final int DEFAULT_DELAY_MILLIS = 1000;

  431.     private static final String RAF_READ_ONLY_MODE = "r";

  432.     /**
  433.      * The the virtual machine's {@link Charset#defaultCharset() default charset} used for reading files.
  434.      */
  435.     private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();

  436.     /**
  437.      * Constructs a new {@link Builder}.
  438.      *
  439.      * @return Creates a new {@link Builder}.
  440.      * @since 2.12.0
  441.      */
  442.     public static Builder builder() {
  443.         return new Builder();
  444.     }

  445.     /**
  446.      * Creates and starts a Tailer for the given file.
  447.      *
  448.      * @param file the file to follow.
  449.      * @param charset the character set to use for reading the file.
  450.      * @param listener the TailerListener to use.
  451.      * @param delayMillis the delay between checks of the file for new content in milliseconds.
  452.      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
  453.      * @param reOpen whether to close/reopen the file between chunks.
  454.      * @param bufferSize buffer size.
  455.      * @return The new tailer.
  456.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  457.      */
  458.     @Deprecated
  459.     public static Tailer create(final File file, final Charset charset, final TailerListener listener, final long delayMillis, final boolean end,
  460.         final boolean reOpen, final int bufferSize) {
  461.         //@formatter:off
  462.         return builder()
  463.                 .setFile(file)
  464.                 .setTailerListener(listener)
  465.                 .setCharset(charset)
  466.                 .setDelayDuration(Duration.ofMillis(delayMillis))
  467.                 .setTailFromEnd(end)
  468.                 .setReOpen(reOpen)
  469.                 .setBufferSize(bufferSize)
  470.                 .get();
  471.         //@formatter:on
  472.     }

  473.     /**
  474.      * Creates and starts a Tailer for the given file, starting at the beginning of the file with the default delay of 1.0s
  475.      *
  476.      * @param file the file to follow.
  477.      * @param listener the TailerListener to use.
  478.      * @return The new tailer.
  479.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  480.      */
  481.     @Deprecated
  482.     public static Tailer create(final File file, final TailerListener listener) {
  483.         //@formatter:off
  484.         return builder()
  485.                 .setFile(file)
  486.                 .setTailerListener(listener)
  487.                 .get();
  488.         //@formatter:on
  489.     }

  490.     /**
  491.      * Creates and starts a Tailer for the given file, starting at the beginning of the file
  492.      *
  493.      * @param file the file to follow.
  494.      * @param listener the TailerListener to use.
  495.      * @param delayMillis the delay between checks of the file for new content in milliseconds.
  496.      * @return The new tailer.
  497.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  498.      */
  499.     @Deprecated
  500.     public static Tailer create(final File file, final TailerListener listener, final long delayMillis) {
  501.         //@formatter:off
  502.         return builder()
  503.                 .setFile(file)
  504.                 .setTailerListener(listener)
  505.                 .setDelayDuration(Duration.ofMillis(delayMillis))
  506.                 .get();
  507.         //@formatter:on
  508.     }

  509.     /**
  510.      * Creates and starts a Tailer for the given file with default buffer size.
  511.      *
  512.      * @param file the file to follow.
  513.      * @param listener the TailerListener to use.
  514.      * @param delayMillis the delay between checks of the file for new content in milliseconds.
  515.      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
  516.      * @return The new tailer.
  517.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  518.      */
  519.     @Deprecated
  520.     public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end) {
  521.         //@formatter:off
  522.         return builder()
  523.                 .setFile(file)
  524.                 .setTailerListener(listener)
  525.                 .setDelayDuration(Duration.ofMillis(delayMillis))
  526.                 .setTailFromEnd(end)
  527.                 .get();
  528.         //@formatter:on
  529.     }

  530.     /**
  531.      * Creates and starts a Tailer for the given file with default buffer size.
  532.      *
  533.      * @param file the file to follow.
  534.      * @param listener the TailerListener to use.
  535.      * @param delayMillis the delay between checks of the file for new content in milliseconds.
  536.      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
  537.      * @param reOpen whether to close/reopen the file between chunks.
  538.      * @return The new tailer.
  539.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  540.      */
  541.     @Deprecated
  542.     public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen) {
  543.         //@formatter:off
  544.         return builder()
  545.                 .setFile(file)
  546.                 .setTailerListener(listener)
  547.                 .setDelayDuration(Duration.ofMillis(delayMillis))
  548.                 .setTailFromEnd(end)
  549.                 .setReOpen(reOpen)
  550.                 .get();
  551.         //@formatter:on
  552.     }

  553.     /**
  554.      * Creates and starts a Tailer for the given file.
  555.      *
  556.      * @param file the file to follow.
  557.      * @param listener the TailerListener to use.
  558.      * @param delayMillis the delay between checks of the file for new content in milliseconds.
  559.      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
  560.      * @param reOpen whether to close/reopen the file between chunks.
  561.      * @param bufferSize buffer size.
  562.      * @return The new tailer.
  563.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  564.      */
  565.     @Deprecated
  566.     public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen,
  567.         final int bufferSize) {
  568.         //@formatter:off
  569.         return builder()
  570.                 .setFile(file)
  571.                 .setTailerListener(listener)
  572.                 .setDelayDuration(Duration.ofMillis(delayMillis))
  573.                 .setTailFromEnd(end)
  574.                 .setReOpen(reOpen)
  575.                 .setBufferSize(bufferSize)
  576.                 .get();
  577.         //@formatter:on
  578.     }

  579.     /**
  580.      * Creates and starts a Tailer for the given file.
  581.      *
  582.      * @param file the file to follow.
  583.      * @param listener the TailerListener to use.
  584.      * @param delayMillis the delay between checks of the file for new content in milliseconds.
  585.      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
  586.      * @param bufferSize buffer size.
  587.      * @return The new tailer.
  588.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  589.      */
  590.     @Deprecated
  591.     public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end, final int bufferSize) {
  592.         //@formatter:off
  593.         return builder()
  594.                 .setFile(file)
  595.                 .setTailerListener(listener)
  596.                 .setDelayDuration(Duration.ofMillis(delayMillis))
  597.                 .setTailFromEnd(end)
  598.                 .setBufferSize(bufferSize)
  599.                 .get();
  600.         //@formatter:on
  601.     }

  602.     /**
  603.      * Buffer on top of RandomAccessResourceBridge.
  604.      */
  605.     private final byte[] inbuf;

  606.     /**
  607.      * The file which will be tailed.
  608.      */
  609.     private final Tailable tailable;

  610.     /**
  611.      * The character set that will be used to read the file.
  612.      */
  613.     private final Charset charset;

  614.     /**
  615.      * The amount of time to wait for the file to be updated.
  616.      */
  617.     private final Duration delayDuration;

  618.     /**
  619.      * Whether to tail from the end or start of file
  620.      */
  621.     private final boolean tailAtEnd;

  622.     /**
  623.      * The listener to notify of events when tailing.
  624.      */
  625.     private final TailerListener listener;

  626.     /**
  627.      * Whether to close and reopen the file whilst waiting for more input.
  628.      */
  629.     private final boolean reOpen;

  630.     /**
  631.      * The tailer will run as long as this value is true.
  632.      */
  633.     private volatile boolean run = true;

  634.     /**
  635.      * Creates a Tailer for the given file, with a specified buffer size.
  636.      *
  637.      * @param file the file to follow.
  638.      * @param charset the Charset to be used for reading the file
  639.      * @param listener the TailerListener to use.
  640.      * @param delayMillis the delay between checks of the file for new content in milliseconds.
  641.      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
  642.      * @param reOpen if true, close and reopen the file between reading chunks
  643.      * @param bufSize Buffer size
  644.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  645.      */
  646.     @Deprecated
  647.     public Tailer(final File file, final Charset charset, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen,
  648.         final int bufSize) {
  649.         this(new TailablePath(file.toPath()), charset, listener, Duration.ofMillis(delayMillis), end, reOpen, bufSize);
  650.     }

  651.     /**
  652.      * Creates a Tailer for the given file, starting from the beginning, with the default delay of 1.0s.
  653.      *
  654.      * @param file The file to follow.
  655.      * @param listener the TailerListener to use.
  656.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  657.      */
  658.     @Deprecated
  659.     public Tailer(final File file, final TailerListener listener) {
  660.         this(file, listener, DEFAULT_DELAY_MILLIS);
  661.     }

  662.     /**
  663.      * Creates a Tailer for the given file, starting from the beginning.
  664.      *
  665.      * @param file the file to follow.
  666.      * @param listener the TailerListener to use.
  667.      * @param delayMillis the delay between checks of the file for new content in milliseconds.
  668.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  669.      */
  670.     @Deprecated
  671.     public Tailer(final File file, final TailerListener listener, final long delayMillis) {
  672.         this(file, listener, delayMillis, false);
  673.     }

  674.     /**
  675.      * Creates a Tailer for the given file, with a delay other than the default 1.0s.
  676.      *
  677.      * @param file the file to follow.
  678.      * @param listener the TailerListener to use.
  679.      * @param delayMillis the delay between checks of the file for new content in milliseconds.
  680.      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
  681.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  682.      */
  683.     @Deprecated
  684.     public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end) {
  685.         this(file, listener, delayMillis, end, IOUtils.DEFAULT_BUFFER_SIZE);
  686.     }

  687.     /**
  688.      * Creates a Tailer for the given file, with a delay other than the default 1.0s.
  689.      *
  690.      * @param file the file to follow.
  691.      * @param listener the TailerListener to use.
  692.      * @param delayMillis the delay between checks of the file for new content in milliseconds.
  693.      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
  694.      * @param reOpen if true, close and reopen the file between reading chunks
  695.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  696.      */
  697.     @Deprecated
  698.     public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen) {
  699.         this(file, listener, delayMillis, end, reOpen, IOUtils.DEFAULT_BUFFER_SIZE);
  700.     }

  701.     /**
  702.      * Creates a Tailer for the given file, with a specified buffer size.
  703.      *
  704.      * @param file the file to follow.
  705.      * @param listener the TailerListener to use.
  706.      * @param delayMillis the delay between checks of the file for new content in milliseconds.
  707.      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
  708.      * @param reOpen if true, close and reopen the file between reading chunks
  709.      * @param bufferSize Buffer size
  710.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  711.      */
  712.     @Deprecated
  713.     public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen, final int bufferSize) {
  714.         this(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, bufferSize);
  715.     }

  716.     /**
  717.      * Creates a Tailer for the given file, with a specified buffer size.
  718.      *
  719.      * @param file the file to follow.
  720.      * @param listener the TailerListener to use.
  721.      * @param delayMillis the delay between checks of the file for new content in milliseconds.
  722.      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
  723.      * @param bufferSize Buffer size
  724.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  725.      */
  726.     @Deprecated
  727.     public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, final int bufferSize) {
  728.         this(file, listener, delayMillis, end, false, bufferSize);
  729.     }

  730.     /**
  731.      * Creates a Tailer for the given file, with a specified buffer size.
  732.      *
  733.      * @param tailable the file to follow.
  734.      * @param charset the Charset to be used for reading the file
  735.      * @param listener the TailerListener to use.
  736.      * @param delayDuration the delay between checks of the file for new content in milliseconds.
  737.      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
  738.      * @param reOpen if true, close and reopen the file between reading chunks
  739.      * @param bufferSize Buffer size
  740.      */
  741.     private Tailer(final Tailable tailable, final Charset charset, final TailerListener listener, final Duration delayDuration, final boolean end,
  742.         final boolean reOpen, final int bufferSize) {
  743.         this.tailable = Objects.requireNonNull(tailable, "tailable");
  744.         this.listener = Objects.requireNonNull(listener, "listener");
  745.         this.delayDuration = delayDuration;
  746.         this.tailAtEnd = end;
  747.         this.inbuf = IOUtils.byteArray(bufferSize);

  748.         // Save and prepare the listener
  749.         listener.init(this);
  750.         this.reOpen = reOpen;
  751.         this.charset = charset;
  752.     }

  753.     /**
  754.      * Requests the tailer to complete its current loop and return.
  755.      */
  756.     @Override
  757.     public void close() {
  758.         this.run = false;
  759.     }

  760.     /**
  761.      * Gets the delay in milliseconds.
  762.      *
  763.      * @return the delay in milliseconds.
  764.      * @deprecated Use {@link #getDelayDuration()}.
  765.      */
  766.     @Deprecated
  767.     public long getDelay() {
  768.         return delayDuration.toMillis();
  769.     }

  770.     /**
  771.      * Gets the delay Duration.
  772.      *
  773.      * @return the delay Duration.
  774.      * @since 2.12.0
  775.      */
  776.     public Duration getDelayDuration() {
  777.         return delayDuration;
  778.     }

  779.     /**
  780.      * Gets the file.
  781.      *
  782.      * @return the file
  783.      * @throws IllegalStateException if constructed using a user provided {@link Tailable} implementation
  784.      */
  785.     public File getFile() {
  786.         if (tailable instanceof TailablePath) {
  787.             return ((TailablePath) tailable).getPath().toFile();
  788.         }
  789.         throw new IllegalStateException("Cannot extract java.io.File from " + tailable.getClass().getName());
  790.     }

  791.     /**
  792.      * Gets whether to keep on running.
  793.      *
  794.      * @return whether to keep on running.
  795.      * @since 2.5
  796.      */
  797.     protected boolean getRun() {
  798.         return run;
  799.     }

  800.     /**
  801.      * Gets the Tailable.
  802.      *
  803.      * @return the Tailable
  804.      * @since 2.12.0
  805.      */
  806.     public Tailable getTailable() {
  807.         return tailable;
  808.     }

  809.     /**
  810.      * Reads new lines.
  811.      *
  812.      * @param reader The file to read
  813.      * @return The new position after the lines have been read
  814.      * @throws IOException if an I/O error occurs.
  815.      */
  816.     private long readLines(final RandomAccessResourceBridge reader) throws IOException {
  817.         try (ByteArrayOutputStream lineBuf = new ByteArrayOutputStream(64)) {
  818.             long pos = reader.getPointer();
  819.             long rePos = pos; // position to re-read
  820.             int num;
  821.             boolean seenCR = false;
  822.             while (getRun() && (num = reader.read(inbuf)) != EOF) {
  823.                 for (int i = 0; i < num; i++) {
  824.                     final byte ch = inbuf[i];
  825.                     switch (ch) {
  826.                     case LF:
  827.                         seenCR = false; // swallow CR before LF
  828.                         listener.handle(new String(lineBuf.toByteArray(), charset));
  829.                         lineBuf.reset();
  830.                         rePos = pos + i + 1;
  831.                         break;
  832.                     case CR:
  833.                         if (seenCR) {
  834.                             lineBuf.write(CR);
  835.                         }
  836.                         seenCR = true;
  837.                         break;
  838.                     default:
  839.                         if (seenCR) {
  840.                             seenCR = false; // swallow final CR
  841.                             listener.handle(new String(lineBuf.toByteArray(), charset));
  842.                             lineBuf.reset();
  843.                             rePos = pos + i + 1;
  844.                         }
  845.                         lineBuf.write(ch);
  846.                     }
  847.                 }
  848.                 pos = reader.getPointer();
  849.             }

  850.             reader.seek(rePos); // Ensure we can re-read if necessary

  851.             if (listener instanceof TailerListenerAdapter) {
  852.                 ((TailerListenerAdapter) listener).endOfFileReached();
  853.             }

  854.             return rePos;
  855.         }
  856.     }

  857.     /**
  858.      * Follows changes in the file, calling {@link TailerListener#handle(String)} with each new line.
  859.      */
  860.     @Override
  861.     public void run() {
  862.         RandomAccessResourceBridge reader = null;
  863.         try {
  864.             FileTime last = FileTimes.EPOCH; // The last time the file was checked for changes
  865.             long position = 0; // position within the file
  866.             // Open the file
  867.             while (getRun() && reader == null) {
  868.                 try {
  869.                     reader = tailable.getRandomAccess(RAF_READ_ONLY_MODE);
  870.                 } catch (final FileNotFoundException e) {
  871.                     listener.fileNotFound();
  872.                 }
  873.                 if (reader == null) {
  874.                     ThreadUtils.sleep(delayDuration);
  875.                 } else {
  876.                     // The current position in the file
  877.                     position = tailAtEnd ? tailable.size() : 0;
  878.                     last = tailable.lastModifiedFileTime();
  879.                     reader.seek(position);
  880.                 }
  881.             }
  882.             while (getRun()) {
  883.                 final boolean newer = tailable.isNewer(last); // IO-279, must be done first
  884.                 // Check the file length to see if it was rotated
  885.                 final long length = tailable.size();
  886.                 if (length < position) {
  887.                     // File was rotated
  888.                     listener.fileRotated();
  889.                     // Reopen the reader after rotation ensuring that the old file is closed iff we re-open it
  890.                     // successfully
  891.                     try (RandomAccessResourceBridge save = reader) {
  892.                         reader = tailable.getRandomAccess(RAF_READ_ONLY_MODE);
  893.                         // At this point, we're sure that the old file is rotated
  894.                         // Finish scanning the old file and then we'll start with the new one
  895.                         try {
  896.                             readLines(save);
  897.                         } catch (final IOException ioe) {
  898.                             listener.handle(ioe);
  899.                         }
  900.                         position = 0;
  901.                     } catch (final FileNotFoundException e) {
  902.                         // in this case we continue to use the previous reader and position values
  903.                         listener.fileNotFound();
  904.                         ThreadUtils.sleep(delayDuration);
  905.                     }
  906.                     continue;
  907.                 }
  908.                 // File was not rotated
  909.                 // See if the file needs to be read again
  910.                 if (length > position) {
  911.                     // The file has more content than it did last time
  912.                     position = readLines(reader);
  913.                     last = tailable.lastModifiedFileTime();
  914.                 } else if (newer) {
  915.                     /*
  916.                      * This can happen if the file is truncated or overwritten with the exact same length of information. In cases like
  917.                      * this, the file position needs to be reset
  918.                      */
  919.                     position = 0;
  920.                     reader.seek(position); // cannot be null here

  921.                     // Now we can read new lines
  922.                     position = readLines(reader);
  923.                     last = tailable.lastModifiedFileTime();
  924.                 }
  925.                 if (reOpen && reader != null) {
  926.                     reader.close();
  927.                 }
  928.                 ThreadUtils.sleep(delayDuration);
  929.                 if (getRun() && reOpen) {
  930.                     reader = tailable.getRandomAccess(RAF_READ_ONLY_MODE);
  931.                     reader.seek(position);
  932.                 }
  933.             }
  934.         } catch (final InterruptedException e) {
  935.             Thread.currentThread().interrupt();
  936.             listener.handle(e);
  937.         } catch (final Exception e) {
  938.             listener.handle(e);
  939.         } finally {
  940.             try {
  941.                 IOUtils.close(reader);
  942.             } catch (final IOException e) {
  943.                 listener.handle(e);
  944.             }
  945.             close();
  946.         }
  947.     }

  948.     /**
  949.      * Requests the tailer to complete its current loop and return.
  950.      *
  951.      * @deprecated Use {@link #close()}.
  952.      */
  953.     @Deprecated
  954.     public void stop() {
  955.         close();
  956.     }
  957. }