    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.io.input;
    018
    019import static org.apache.commons.io.IOUtils.CR;
    020import static org.apache.commons.io.IOUtils.EOF;
    021import static org.apache.commons.io.IOUtils.LF;
    022
    023import java.io.ByteArrayOutputStream;
    024import java.io.Closeable;
    025import java.io.File;
    026import java.io.FileNotFoundException;
    027import java.io.IOException;
    028import java.io.RandomAccessFile;
    029import java.nio.charset.Charset;
    030import java.nio.file.Files;
    031import java.nio.file.LinkOption;
    032import java.nio.file.Path;
    033import java.nio.file.attribute.FileTime;
    034import java.time.Duration;
    035import java.util.Arrays;
    036import java.util.Objects;
    037import java.util.concurrent.ExecutorService;
    038import java.util.concurrent.Executors;
    039
    040import org.apache.commons.io.IOUtils;
    041import org.apache.commons.io.ThreadUtils;
    042import org.apache.commons.io.build.AbstractOrigin;
    043import org.apache.commons.io.build.AbstractStreamBuilder;
    044import org.apache.commons.io.file.PathUtils;
    045import org.apache.commons.io.file.attribute.FileTimes;
    046
    047/**
    048 * Simple implementation of the UNIX "tail -f" functionality.
    049 * <p>
    050 * To build an instance, use {@link Builder}.
    051 * </p>
    052 * <h2>1. Create a TailerListener implementation</h2>
    053 * <p>
    054 * First you need to create a {@link TailerListener} implementation; ({@link TailerListenerAdapter} is provided for
    055 * convenience so that you don't have to implement every method).
    056 * </p>
    057 * <p>
    058 * For example:
    059 * </p>
    060 * <pre>
    061 * public class MyTailerListener extends TailerListenerAdapter {
    062 *     public void handle(String line) {
    063 *         System.out.println(line);
    064 *     }
    065 * }
    066 * </pre>
    067 * <h2>2. Using a Tailer</h2>
    068 * <p>
    069 * You can create and use a Tailer in one of three ways:
    070 * </p>
    071 * <ul>
    072 * <li>Using a {@link Builder}</li>
    073 * <li>Using an {@link java.util.concurrent.Executor}</li>
    074 * <li>Using a {@link Thread}</li>
    075 * </ul>
    076 * <p>
    077 * An example of each is shown below.
    078 * </p>
    079 * <h3>2.1 Using a Builder</h3>
    080 * <pre>
    081 * TailerListener listener = new MyTailerListener();
    082 * Tailer tailer = Tailer.builder()
    083 *   .setFile(file)
    084 *   .setTailerListener(listener)
    085 *   .setDelayDuration(delay)
    086 *   .get();
    087 * </pre>
    088 * <h3>2.2 Using an Executor</h3>
    089 * <pre>
    090 * TailerListener listener = new MyTailerListener();
    091 * Tailer tailer = new Tailer(file, listener, delay);
    092 *
    093 * // stupid executor impl. for demo purposes
    094 * Executor executor = new Executor() {
    095 *     public void execute(Runnable command) {
    096 *         command.run();
    097 *     }
    098 * };
    099 *
    100 * executor.execute(tailer);
    101 * </pre>
    102 * <h3>2.3 Using a Thread</h3>
    103 * <pre>
    104 * TailerListener listener = new MyTailerListener();
    105 * Tailer tailer = new Tailer(file, listener, delay);
    106 * Thread thread = new Thread(tailer);
    107 * thread.setDaemon(true); // optional
    108 * thread.start();
    109 * </pre>
    110 * <h2>3. Stopping a Tailer</h2>
    111 * <p>
    112 * Remember to stop the tailer when you have done with it:
    113 * </p>
    114 * <pre>
    115 * tailer.stop();
    116 * </pre>
    117 * <h2>4. Interrupting a Tailer</h2>
    118 * <p>
    119 * You can interrupt the thread a tailer is running on by calling {@link Thread#interrupt()}.
    120 * </p>
    121 * <pre>
    122 * thread.interrupt();
    123 * </pre>
    124 * <p>
    125 * If you interrupt a tailer, the tailer listener is called with the {@link InterruptedException}.
    126 * </p>
    127 * <p>
    128 * The file is read using the default Charset; this can be overridden if necessary.
    129 * </p>
    130 *
    131 * @see Builder
    132 * @see TailerListener
    133 * @see TailerListenerAdapter
    134 * @since 2.0
    135 * @since 2.5 Updated behavior and documentation for {@link Thread#interrupt()}.
    136 * @since 2.12.0 Add {@link Tailable} and {@link RandomAccessResourceBridge} interfaces to tail of files accessed using
    137 *        alternative libraries such as jCIFS or <a href="https://commons.apache.org/proper/commons-vfs/">Apache Commons
    138 *        VFS</a>.
    139 */
    140public class Tailer implements Runnable, AutoCloseable {
    141
    142    // @formatter:off
    143    /**
    144     * Builds a new {@link Tailer}.
    145     *
    146     * <p>
    147     * For example:
    148     * </p>
    149     * <pre>{@code
    150     * Tailer t = Tailer.builder()
    151     *   .setPath(path)
    152     *   .setCharset(StandardCharsets.UTF_8)
    153     *   .setDelayDuration(Duration.ofSeconds(1))
    154     *   .setExecutorService(Executors.newSingleThreadExecutor(Builder::newDaemonThread))
    155     *   .setReOpen(false)
    156     *   .setStartThread(true)
    157     *   .setTailable(tailable)
    158     *   .setTailerListener(tailerListener)
    159     *   .setTailFromEnd(false)
    160     *   .get();}
    161     * </pre>
    162     *
    163     * @see #get()
    164     * @since 2.12.0
    165     */
    166    // @formatter:on
    167    public static class Builder extends AbstractStreamBuilder<Tailer, Builder> {
    168
    169        private static final Duration DEFAULT_DELAY_DURATION = Duration.ofMillis(DEFAULT_DELAY_MILLIS);
    170
    171        /**
    172         * Creates a new daemon thread.
    173         *
    174         * @param runnable the thread's runnable.
    175         * @return a new daemon thread.
    176         */
    177        private static Thread newDaemonThread(final Runnable runnable) {
    178            final Thread thread = new Thread(runnable, "commons-io-tailer");
    179            thread.setDaemon(true);
    180            return thread;
    181        }
    182
    183        private Tailable tailable;
    184        private TailerListener tailerListener;
    185        private Duration delayDuration = DEFAULT_DELAY_DURATION;
    186        private boolean tailFromEnd;
    187        private boolean reOpen;
    188        private boolean startThread = true;
    189        private ExecutorService executorService = Executors.newSingleThreadExecutor(Builder::newDaemonThread);
    190
    191        /**
    192         * Builds a new {@link Tailer}.
    193         *
    194         * <p>
    195         * This builder use the following aspects:
    196         * </p>
    197         * <ul>
    198         * <li>{@link #getBufferSize()}</li>
    199         * <li>{@link #getCharset()}</li>
    200         * <li>{@link Tailable}</li>
    201         * <li>{@link TailerListener}</li>
    202         * <li>delayDuration</li>
    203         * <li>tailFromEnd</li>
    204         * <li>reOpen</li>
    205         * </ul>
    206         *
    207         * @return a new instance.
    208         */
    209        @Override
    210        public Tailer get() {
    211            final Tailer tailer = new Tailer(tailable, getCharset(), tailerListener, delayDuration, tailFromEnd, reOpen, getBufferSize());
    212            if (startThread) {
    213                executorService.submit(tailer);
    214            }
    215            return tailer;
    216        }
    217
    218        /**
    219         * Sets the delay duration. null resets to the default delay of one second.
    220         *
    221         * @param delayDuration the delay between checks of the file for new content.
    222         * @return this
    223         */
    224        public Builder setDelayDuration(final Duration delayDuration) {
    225            this.delayDuration = delayDuration != null ? delayDuration : DEFAULT_DELAY_DURATION;
    226            return this;
    227        }
    228
    229        /**
    230         * Sets the executor service to use when startThread is true.
    231         *
    232         * @param executorService the executor service to use when startThread is true.
    233         * @return this
    234         */
    235        public Builder setExecutorService(final ExecutorService executorService) {
    236            this.executorService = Objects.requireNonNull(executorService, "executorService");
    237            return this;
    238        }
    239
    240        /**
    241         * Sets the origin.
    242         *
    243         * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
    244         */
    245        @Override
    246        protected Builder setOrigin(final AbstractOrigin<?, ?> origin) {
    247            setTailable(new TailablePath(origin.getPath()));
    248            return super.setOrigin(origin);
    249        }
    250
    251        /**
    252         * Sets the re-open behavior.
    253         *
    254         * @param reOpen whether to close/reopen the file between chunks
    255         * @return this
    256         */
    257        public Builder setReOpen(final boolean reOpen) {
    258            this.reOpen = reOpen;
    259            return this;
    260        }
    261
    262        /**
    263         * Sets the daemon thread startup behavior.
    264         *
    265         * @param startThread whether to create a daemon thread automatically.
    266         * @return this
    267         */
    268        public Builder setStartThread(final boolean startThread) {
    269            this.startThread = startThread;
    270            return this;
    271        }
    272
    273        /**
    274         * Sets the tailable.
    275         *
    276         * @param tailable the tailable.
    277         * @return this.
    278         */
    279        public Builder setTailable(final Tailable tailable) {
    280            this.tailable = Objects.requireNonNull(tailable, "tailable");
    281            return this;
    282        }
    283
    284        /**
    285         * Sets the listener.
    286         *
    287         * @param tailerListener the listener.
    288         * @return this
    289         */
    290        public Builder setTailerListener(final TailerListener tailerListener) {
    291            this.tailerListener = Objects.requireNonNull(tailerListener, "tailerListener");
    292            return this;
    293        }
    294
    295        /**
    296         * Sets the tail start behavior.
    297         *
    298         * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    299         * @return this
    300         */
    301        public Builder setTailFromEnd(final boolean end) {
    302            this.tailFromEnd = end;
    303            return this;
    304        }
    305    }
    306
    307    /**
    308     * Bridges random access to a {@link RandomAccessFile}.
    309     */
    310    private static final class RandomAccessFileBridge implements RandomAccessResourceBridge {
    311
    312        private final RandomAccessFile randomAccessFile;
    313
    314        private RandomAccessFileBridge(final File file, final String mode) throws FileNotFoundException {
    315            randomAccessFile = new RandomAccessFile(file, mode);
    316        }
    317
    318        @Override
    319        public void close() throws IOException {
    320            randomAccessFile.close();
    321        }
    322
    323        @Override
    324        public long getPointer() throws IOException {
    325            return randomAccessFile.getFilePointer();
    326        }
    327
    328        @Override
    329        public int read(final byte[] b) throws IOException {
    330            return randomAccessFile.read(b);
    331        }
    332
    333        @Override
    334        public void seek(final long position) throws IOException {
    335            randomAccessFile.seek(position);
    336        }
    337
    338    }
    339
    340    /**
    341     * Bridges access to a resource for random access, normally a file. Allows substitution of remote files for example
    342     * using jCIFS.
    343     *
    344     * @since 2.12.0
    345     */
    346    public interface RandomAccessResourceBridge extends Closeable {
    347
    348        /**
    349         * Gets the current offset in this tailable.
    350         *
    351         * @return the offset from the beginning of the tailable, in bytes, at which the next read or write occurs.
    352         * @throws IOException if an I/O error occurs.
    353         */
    354        long getPointer() throws IOException;
    355
    356        /**
    357         * Reads up to {@code b.length} bytes of data from this tailable into an array of bytes. This method blocks until at
    358         * least one byte of input is available.
    359         *
    360         * @param b the buffer into which the data is read.
    361         * @return the total number of bytes read into the buffer, or {@code -1} if there is no more data because the end of
    362         *         this tailable has been reached.
    363         * @throws IOException If the first byte cannot be read for any reason other than end of tailable, or if the random
    364         *         access tailable has been closed, or if some other I/O error occurs.
    365         */
    366        int read(final byte[] b) throws IOException;
    367
    368        /**
    369         * Sets the file-pointer offset, measured from the beginning of this tailable, at which the next read or write occurs.
    370         * The offset may be set beyond the end of the tailable. Setting the offset beyond the end of the tailable does not
    371         * change the tailable length. The tailable length will change only by writing after the offset has been set beyond the
    372         * end of the tailable.
    373         *
    374         * @param pos the offset position, measured in bytes from the beginning of the tailable, at which to set the tailable
    375         *        pointer.
    376         * @throws IOException if {@code pos} is less than {@code 0} or if an I/O error occurs.
    377         */
    378        void seek(final long pos) throws IOException;
    379    }
    380
    381    /**
    382     * A tailable resource like a file.
    383     *
    384     * @since 2.12.0
    385     */
    386    public interface Tailable {
    387
    388        /**
    389         * Creates a random access file stream to read.
    390         *
    391         * @param mode the access mode, by default this is for {@link RandomAccessFile}.
    392         * @return a random access file stream to read.
    393         * @throws FileNotFoundException if the tailable object does not exist.
    394         */
    395        RandomAccessResourceBridge getRandomAccess(final String mode) throws FileNotFoundException;
    396
    397        /**
    398         * Tests if this tailable is newer than the specified {@link FileTime}.
    399         *
    400         * @param fileTime the file time reference.
    401         * @return true if the {@link File} exists and has been modified after the given {@link FileTime}.
    402         * @throws IOException if an I/O error occurs.
    403         */
    404        boolean isNewer(final FileTime fileTime) throws IOException;
    405
    406        /**
    407         * Gets the last modification {@link FileTime}.
    408         *
    409         * @return See {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}.
    410         * @throws IOException if an I/O error occurs.
    411         */
    412        FileTime lastModifiedFileTime() throws IOException;
    413
    414        /**
    415         * Gets the size of this tailable.
    416         *
    417         * @return The size, in bytes, of this tailable, or {@code 0} if the file does not exist. Some operating systems may
    418         *         return {@code 0} for path names denoting system-dependent entities such as devices or pipes.
    419         * @throws IOException if an I/O error occurs.
    420         */
    421        long size() throws IOException;
    422    }
    423
    424    /**
    425     * A tailable for a file {@link Path}.
    426     */
    427    private static final class TailablePath implements Tailable {
    428
    429        private final Path path;
    430        private final LinkOption[] linkOptions;
    431
    432        private TailablePath(final Path path, final LinkOption... linkOptions) {
    433            this.path = Objects.requireNonNull(path, "path");
    434            this.linkOptions = linkOptions;
    435        }
    436
    437        Path getPath() {
    438            return path;
    439        }
    440
    441        @Override
    442        public RandomAccessResourceBridge getRandomAccess(final String mode) throws FileNotFoundException {
    443            return new RandomAccessFileBridge(path.toFile(), mode);
    444        }
    445
    446        @Override
    447        public boolean isNewer(final FileTime fileTime) throws IOException {
    448            return PathUtils.isNewer(path, fileTime, linkOptions);
    449        }
    450
    451        @Override
    452        public FileTime lastModifiedFileTime() throws IOException {
    453            return Files.getLastModifiedTime(path, linkOptions);
    454        }
    455
    456        @Override
    457        public long size() throws IOException {
    458            return Files.size(path);
    459        }
    460
    461        @Override
    462        public String toString() {
    463            return "TailablePath [file=" + path + ", linkOptions=" + Arrays.toString(linkOptions) + "]";
    464        }
    465    }
    466
    467    private static final int DEFAULT_DELAY_MILLIS = 1000;
    468
    469    private static final String RAF_READ_ONLY_MODE = "r";
    470
    471    // The default charset used for reading files
    472    private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
    473
    474    /**
    475     * Constructs a new {@link Builder}.
    476     *
    477     * @return Creates a new {@link Builder}.
    478     * @since 2.12.0
    479     */
    480    public static Builder builder() {
    481        return new Builder();
    482    }
    483
    484    /**
    485     * Creates and starts a Tailer for the given file.
    486     *
    487     * @param file the file to follow.
    488     * @param charset the character set to use for reading the file.
    489     * @param listener the TailerListener to use.
    490     * @param delayMillis the delay between checks of the file for new content in milliseconds.
    491     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    492     * @param reOpen whether to close/reopen the file between chunks.
    493     * @param bufferSize buffer size.
    494     * @return The new tailer.
    495     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    496     */
    497    @Deprecated
    498    public static Tailer create(final File file, final Charset charset, final TailerListener listener, final long delayMillis, final boolean end,
    499        final boolean reOpen, final int bufferSize) {
    500        //@formatter:off
    501        return builder()
    502                .setFile(file)
    503                .setTailerListener(listener)
    504                .setCharset(charset)
    505                .setDelayDuration(Duration.ofMillis(delayMillis))
    506                .setTailFromEnd(end)
    507                .setReOpen(reOpen)
    508                .setBufferSize(bufferSize)
    509                .get();
    510        //@formatter:on
    511    }
    512
    513    /**
    514     * Creates and starts a Tailer for the given file, starting at the beginning of the file with the default delay of 1.0s
    515     *
    516     * @param file the file to follow.
    517     * @param listener the TailerListener to use.
    518     * @return The new tailer.
    519     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    520     */
    521    @Deprecated
    522    public static Tailer create(final File file, final TailerListener listener) {
    523        //@formatter:off
    524        return builder()
    525                .setFile(file)
    526                .setTailerListener(listener)
    527                .get();
    528        //@formatter:on
    529    }
    530
    531    /**
    532     * Creates and starts a Tailer for the given file, starting at the beginning of the file
    533     *
    534     * @param file the file to follow.
    535     * @param listener the TailerListener to use.
    536     * @param delayMillis the delay between checks of the file for new content in milliseconds.
    537     * @return The new tailer.
    538     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    539     */
    540    @Deprecated
    541    public static Tailer create(final File file, final TailerListener listener, final long delayMillis) {
    542        //@formatter:off
    543        return builder()
    544                .setFile(file)
    545                .setTailerListener(listener)
    546                .setDelayDuration(Duration.ofMillis(delayMillis))
    547                .get();
    548        //@formatter:on
    549    }
    550
    551    /**
    552     * Creates and starts a Tailer for the given file with default buffer size.
    553     *
    554     * @param file the file to follow.
    555     * @param listener the TailerListener to use.
    556     * @param delayMillis the delay between checks of the file for new content in milliseconds.
    557     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    558     * @return The new tailer.
    559     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    560     */
    561    @Deprecated
    562    public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end) {
    563        //@formatter:off
    564        return builder()
    565                .setFile(file)
    566                .setTailerListener(listener)
    567                .setDelayDuration(Duration.ofMillis(delayMillis))
    568                .setTailFromEnd(end)
    569                .get();
    570        //@formatter:on
    571    }
    572
    573    /**
    574     * Creates and starts a Tailer for the given file with default buffer size.
    575     *
    576     * @param file the file to follow.
    577     * @param listener the TailerListener to use.
    578     * @param delayMillis the delay between checks of the file for new content in milliseconds.
    579     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    580     * @param reOpen whether to close/reopen the file between chunks.
    581     * @return The new tailer.
    582     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    583     */
    584    @Deprecated
    585    public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen) {
    586        //@formatter:off
    587        return builder()
    588                .setFile(file)
    589                .setTailerListener(listener)
    590                .setDelayDuration(Duration.ofMillis(delayMillis))
    591                .setTailFromEnd(end)
    592                .setReOpen(reOpen)
    593                .get();
    594        //@formatter:on
    595    }
    596
    597    /**
    598     * Creates and starts a Tailer for the given file.
    599     *
    600     * @param file the file to follow.
    601     * @param listener the TailerListener to use.
    602     * @param delayMillis the delay between checks of the file for new content in milliseconds.
    603     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    604     * @param reOpen whether to close/reopen the file between chunks.
    605     * @param bufferSize buffer size.
    606     * @return The new tailer.
    607     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    608     */
    609    @Deprecated
    610    public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen,
    611        final int bufferSize) {
    612        //@formatter:off
    613        return builder()
    614                .setFile(file)
    615                .setTailerListener(listener)
    616                .setDelayDuration(Duration.ofMillis(delayMillis))
    617                .setTailFromEnd(end)
    618                .setReOpen(reOpen)
    619                .setBufferSize(bufferSize)
    620                .get();
    621        //@formatter:on
    622    }
    623
    624    /**
    625     * Creates and starts a Tailer for the given file.
    626     *
    627     * @param file the file to follow.
    628     * @param listener the TailerListener to use.
    629     * @param delayMillis the delay between checks of the file for new content in milliseconds.
    630     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    631     * @param bufferSize buffer size.
    632     * @return The new tailer.
    633     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    634     */
    635    @Deprecated
    636    public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end, final int bufferSize) {
    637        //@formatter:off
    638        return builder()
    639                .setFile(file)
    640                .setTailerListener(listener)
    641                .setDelayDuration(Duration.ofMillis(delayMillis))
    642                .setTailFromEnd(end)
    643                .setBufferSize(bufferSize)
    644                .get();
    645        //@formatter:on
    646    }
    647
    648    /**
    649     * Buffer on top of RandomAccessResourceBridge.
    650     */
    651    private final byte[] inbuf;
    652
    653    /**
    654     * The file which will be tailed.
    655     */
    656    private final Tailable tailable;
    657
    658    /**
    659     * The character set that will be used to read the file.
    660     */
    661    private final Charset charset;
    662
    663    /**
    664     * The amount of time to wait for the file to be updated.
    665     */
    666    private final Duration delayDuration;
    667
    668    /**
    669     * Whether to tail from the end or start of file
    670     */
    671    private final boolean tailAtEnd;
    672
    673    /**
    674     * The listener to notify of events when tailing.
    675     */
    676    private final TailerListener listener;
    677
    678    /**
    679     * Whether to close and reopen the file whilst waiting for more input.
    680     */
    681    private final boolean reOpen;
    682
    683    /**
    684     * The tailer will run as long as this value is true.
    685     */
    686    private volatile boolean run = true;
    687
    688    /**
    689     * Creates a Tailer for the given file, with a specified buffer size.
    690     *
    691     * @param file the file to follow.
    692     * @param charset the Charset to be used for reading the file
    693     * @param listener the TailerListener to use.
    694     * @param delayMillis the delay between checks of the file for new content in milliseconds.
    695     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    696     * @param reOpen if true, close and reopen the file between reading chunks
    697     * @param bufSize Buffer size
    698     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    699     */
    700    @Deprecated
    701    public Tailer(final File file, final Charset charset, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen,
    702        final int bufSize) {
    703        this(new TailablePath(file.toPath()), charset, listener, Duration.ofMillis(delayMillis), end, reOpen, bufSize);
    704    }
    705
    706    /**
    707     * Creates a Tailer for the given file, starting from the beginning, with the default delay of 1.0s.
    708     *
    709     * @param file The file to follow.
    710     * @param listener the TailerListener to use.
    711     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    712     */
    713    @Deprecated
    714    public Tailer(final File file, final TailerListener listener) {
    715        this(file, listener, DEFAULT_DELAY_MILLIS);
    716    }
    717
    718    /**
    719     * Creates a Tailer for the given file, starting from the beginning.
    720     *
    721     * @param file the file to follow.
    722     * @param listener the TailerListener to use.
    723     * @param delayMillis the delay between checks of the file for new content in milliseconds.
    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) {
    728        this(file, listener, delayMillis, false);
    729    }
    730
    731    /**
    732     * Creates a Tailer for the given file, with a delay other than the default 1.0s.
    733     *
    734     * @param file the file to follow.
    735     * @param listener the TailerListener to use.
    736     * @param delayMillis 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     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    739     */
    740    @Deprecated
    741    public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end) {
    742        this(file, listener, delayMillis, end, IOUtils.DEFAULT_BUFFER_SIZE);
    743    }
    744
    745    /**
    746     * Creates a Tailer for the given file, with a delay other than the default 1.0s.
    747     *
    748     * @param file the file to follow.
    749     * @param listener the TailerListener to use.
    750     * @param delayMillis the delay between checks of the file for new content in milliseconds.
    751     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    752     * @param reOpen if true, close and reopen the file between reading chunks
    753     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    754     */
    755    @Deprecated
    756    public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen) {
    757        this(file, listener, delayMillis, end, reOpen, IOUtils.DEFAULT_BUFFER_SIZE);
    758    }
    759
    760    /**
    761     * Creates a Tailer for the given file, with a specified buffer size.
    762     *
    763     * @param file the file to follow.
    764     * @param listener the TailerListener to use.
    765     * @param delayMillis the delay between checks of the file for new content in milliseconds.
    766     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    767     * @param reOpen if true, close and reopen the file between reading chunks
    768     * @param bufferSize Buffer size
    769     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    770     */
    771    @Deprecated
    772    public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen, final int bufferSize) {
    773        this(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, bufferSize);
    774    }
    775
    776    /**
    777     * Creates a Tailer for the given file, with a specified buffer size.
    778     *
    779     * @param file the file to follow.
    780     * @param listener the TailerListener to use.
    781     * @param delayMillis the delay between checks of the file for new content in milliseconds.
    782     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    783     * @param bufferSize Buffer size
    784     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    785     */
    786    @Deprecated
    787    public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, final int bufferSize) {
    788        this(file, listener, delayMillis, end, false, bufferSize);
    789    }
    790
    791    /**
    792     * Creates a Tailer for the given file, with a specified buffer size.
    793     *
    794     * @param tailable the file to follow.
    795     * @param charset the Charset to be used for reading the file
    796     * @param listener the TailerListener to use.
    797     * @param delayDuration the delay between checks of the file for new content in milliseconds.
    798     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
    799     * @param reOpen if true, close and reopen the file between reading chunks
    800     * @param bufferSize Buffer size
    801     */
    802    private Tailer(final Tailable tailable, final Charset charset, final TailerListener listener, final Duration delayDuration, final boolean end,
    803        final boolean reOpen, final int bufferSize) {
    804        this.tailable = Objects.requireNonNull(tailable, "tailable");
    805        this.listener = Objects.requireNonNull(listener, "listener");
    806        this.delayDuration = delayDuration;
    807        this.tailAtEnd = end;
    808        this.inbuf = IOUtils.byteArray(bufferSize);
    809
    810        // Save and prepare the listener
    811        listener.init(this);
    812        this.reOpen = reOpen;
    813        this.charset = charset;
    814    }
    815
    816    /**
    817     * Requests the tailer to complete its current loop and return.
    818     */
    819    @Override
    820    public void close() {
    821        this.run = false;
    822    }
    823
    824    /**
    825     * Gets the delay in milliseconds.
    826     *
    827     * @return the delay in milliseconds.
    828     * @deprecated Use {@link #getDelayDuration()}.
    829     */
    830    @Deprecated
    831    public long getDelay() {
    832        return delayDuration.toMillis();
    833    }
    834
    835    /**
    836     * Gets the delay Duration.
    837     *
    838     * @return the delay Duration.
    839     * @since 2.12.0
    840     */
    841    public Duration getDelayDuration() {
    842        return delayDuration;
    843    }
    844
    845    /**
    846     * Gets the file.
    847     *
    848     * @return the file
    849     * @throws IllegalStateException if constructed using a user provided {@link Tailable} implementation
    850     */
    851    public File getFile() {
    852        if (tailable instanceof TailablePath) {
    853            return ((TailablePath) tailable).getPath().toFile();
    854        }
    855        throw new IllegalStateException("Cannot extract java.io.File from " + tailable.getClass().getName());
    856    }
    857
    858    /**
    859     * Gets whether to keep on running.
    860     *
    861     * @return whether to keep on running.
    862     * @since 2.5
    863     */
    864    protected boolean getRun() {
    865        return run;
    866    }
    867
    868    /**
    869     * Gets the Tailable.
    870     *
    871     * @return the Tailable
    872     * @since 2.12.0
    873     */
    874    public Tailable getTailable() {
    875        return tailable;
    876    }
    877
    878    /**
    879     * Reads new lines.
    880     *
    881     * @param reader The file to read
    882     * @return The new position after the lines have been read
    883     * @throws IOException if an I/O error occurs.
    884     */
    885    private long readLines(final RandomAccessResourceBridge reader) throws IOException {
    886        try (ByteArrayOutputStream lineBuf = new ByteArrayOutputStream(64)) {
    887            long pos = reader.getPointer();
    888            long rePos = pos; // position to re-read
    889            int num;
    890            boolean seenCR = false;
    891            while (getRun() && (num = reader.read(inbuf)) != EOF) {
    892                for (int i = 0; i < num; i++) {
    893                    final byte ch = inbuf[i];
    894                    switch (ch) {
    895                    case LF:
    896                        seenCR = false; // swallow CR before LF
    897                        listener.handle(new String(lineBuf.toByteArray(), charset));
    898                        lineBuf.reset();
    899                        rePos = pos + i + 1;
    900                        break;
    901                    case CR:
    902                        if (seenCR) {
    903                            lineBuf.write(CR);
    904                        }
    905                        seenCR = true;
    906                        break;
    907                    default:
    908                        if (seenCR) {
    909                            seenCR = false; // swallow final CR
    910                            listener.handle(new String(lineBuf.toByteArray(), charset));
    911                            lineBuf.reset();
    912                            rePos = pos + i + 1;
    913                        }
    914                        lineBuf.write(ch);
    915                    }
    916                }
    917                pos = reader.getPointer();
    918            }
    919
    920            reader.seek(rePos); // Ensure we can re-read if necessary
    921
    922            if (listener instanceof TailerListenerAdapter) {
    923                ((TailerListenerAdapter) listener).endOfFileReached();
    924            }
    925
    926            return rePos;
    927        }
    928    }
    929
    930    /**
    931     * Follows changes in the file, calling {@link TailerListener#handle(String)} with each new line.
    932     */
    933    @Override
    934    public void run() {
    935        RandomAccessResourceBridge reader = null;
    936        try {
    937            FileTime last = FileTimes.EPOCH; // The last time the file was checked for changes
    938            long position = 0; // position within the file
    939            // Open the file
    940            while (getRun() && reader == null) {
    941                try {
    942                    reader = tailable.getRandomAccess(RAF_READ_ONLY_MODE);
    943                } catch (final FileNotFoundException e) {
    944                    listener.fileNotFound();
    945                }
    946                if (reader == null) {
    947                    ThreadUtils.sleep(delayDuration);
    948                } else {
    949                    // The current position in the file
    950                    position = tailAtEnd ? tailable.size() : 0;
    951                    last = tailable.lastModifiedFileTime();
    952                    reader.seek(position);
    953                }
    954            }
    955            while (getRun()) {
    956                final boolean newer = tailable.isNewer(last); // IO-279, must be done first
    957                // Check the file length to see if it was rotated
    958                final long length = tailable.size();
    959                if (length < position) {
    960                    // File was rotated
    961                    listener.fileRotated();
    962                    // Reopen the reader after rotation ensuring that the old file is closed iff we re-open it
    963                    // successfully
    964                    try (RandomAccessResourceBridge save = reader) {
    965                        reader = tailable.getRandomAccess(RAF_READ_ONLY_MODE);
    966                        // At this point, we're sure that the old file is rotated
    967                        // Finish scanning the old file and then we'll start with the new one
    968                        try {
    969                            readLines(save);
    970                        } catch (final IOException ioe) {
    971                            listener.handle(ioe);
    972                        }
    973                        position = 0;
    974                    } catch (final FileNotFoundException e) {
    975                        // in this case we continue to use the previous reader and position values
    976                        listener.fileNotFound();
    977                        ThreadUtils.sleep(delayDuration);
    978                    }
    979                    continue;
    980                }
    981                // File was not rotated
    982                // See if the file needs to be read again
    983                if (length > position) {
    984                    // The file has more content than it did last time
    985                    position = readLines(reader);
    986                    last = tailable.lastModifiedFileTime();
    987                } else if (newer) {
    988                    /*
    989                     * This can happen if the file is truncated or overwritten with the exact same length of information. In cases like
    990                     * this, the file position needs to be reset
    991                     */
    992                    position = 0;
    993                    reader.seek(position); // cannot be null here
    994
    995                    // Now we can read new lines
    996                    position = readLines(reader);
    997                    last = tailable.lastModifiedFileTime();
    998                }
    999                if (reOpen && reader != null) {
    1000                    reader.close();
    1001                }
    1002                ThreadUtils.sleep(delayDuration);
    1003                if (getRun() && reOpen) {
    1004                    reader = tailable.getRandomAccess(RAF_READ_ONLY_MODE);
    1005                    reader.seek(position);
    1006                }
    1007            }
    1008        } catch (final InterruptedException e) {
    1009            Thread.currentThread().interrupt();
    1010            listener.handle(e);
    1011        } catch (final Exception e) {
    1012            listener.handle(e);
    1013        } finally {
    1014            try {
    1015                IOUtils.close(reader);
    1016            } catch (final IOException e) {
    1017                listener.handle(e);
    1018            }
    1019            close();
    1020        }
    1021    }
    1022
    1023    /**
    1024     * Requests the tailer to complete its current loop and return.
    1025     *
    1026     * @deprecated Use {@link #close()}.
    1027     */
    1028    @Deprecated
    1029    public void stop() {
    1030        close();
    1031    }
    1032}