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