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