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