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 {@linkplain 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 }