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