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