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