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          * 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 }