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