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}