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