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