001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.io.input; 018 019import static org.apache.commons.io.IOUtils.CR; 020import static org.apache.commons.io.IOUtils.EOF; 021import static org.apache.commons.io.IOUtils.LF; 022 023import java.io.ByteArrayOutputStream; 024import java.io.Closeable; 025import java.io.File; 026import java.io.FileNotFoundException; 027import java.io.IOException; 028import java.io.RandomAccessFile; 029import java.nio.charset.Charset; 030import java.nio.file.Files; 031import java.nio.file.LinkOption; 032import java.nio.file.Path; 033import java.nio.file.attribute.FileTime; 034import java.time.Duration; 035import java.util.Arrays; 036import java.util.Objects; 037import java.util.concurrent.ExecutorService; 038import java.util.concurrent.Executors; 039 040import org.apache.commons.io.IOUtils; 041import org.apache.commons.io.ThreadUtils; 042import org.apache.commons.io.build.AbstractOrigin; 043import org.apache.commons.io.build.AbstractStreamBuilder; 044import org.apache.commons.io.file.PathUtils; 045import org.apache.commons.io.file.attribute.FileTimes; 046 047/** 048 * Simple implementation of the Unix "tail -f" functionality. 049 * <p> 050 * To build an instance, use {@link Builder}. 051 * </p> 052 * <h2>1. Create a TailerListener implementation</h2> 053 * <p> 054 * First you need to create a {@link TailerListener} implementation; ({@link TailerListenerAdapter} is provided for 055 * convenience so that you don't have to implement every method). 056 * </p> 057 * <p> 058 * For example: 059 * </p> 060 * <pre> 061 * public class MyTailerListener extends TailerListenerAdapter { 062 * public void handle(String line) { 063 * System.out.println(line); 064 * } 065 * } 066 * </pre> 067 * <h2>2. Using a Tailer</h2> 068 * <p> 069 * You can create and use a Tailer in one of three ways: 070 * </p> 071 * <ul> 072 * <li>Using a {@link Builder}</li> 073 * <li>Using an {@link java.util.concurrent.Executor}</li> 074 * <li>Using a {@link Thread}</li> 075 * </ul> 076 * <p> 077 * An example of each is shown below. 078 * </p> 079 * <h3>2.1 Using a Builder</h3> 080 * <pre> 081 * TailerListener listener = new MyTailerListener(); 082 * Tailer tailer = Tailer.builder() 083 * .setFile(file) 084 * .setTailerListener(listener) 085 * .setDelayDuration(delay) 086 * .get(); 087 * </pre> 088 * <h3>2.2 Using an Executor</h3> 089 * <pre> 090 * TailerListener listener = new MyTailerListener(); 091 * Tailer tailer = new Tailer(file, listener, delay); 092 * 093 * // stupid executor impl. for demo purposes 094 * Executor executor = new Executor() { 095 * public void execute(Runnable command) { 096 * command.run(); 097 * } 098 * }; 099 * 100 * executor.execute(tailer); 101 * </pre> 102 * <h3>2.3 Using a Thread</h3> 103 * <pre> 104 * TailerListener listener = new MyTailerListener(); 105 * Tailer tailer = new Tailer(file, listener, delay); 106 * Thread thread = new Thread(tailer); 107 * thread.setDaemon(true); // optional 108 * thread.start(); 109 * </pre> 110 * <h2>3. Stopping a Tailer</h2> 111 * <p> 112 * Remember to stop the tailer when you have done with it: 113 * </p> 114 * <pre> 115 * tailer.stop(); 116 * </pre> 117 * <h2>4. Interrupting a Tailer</h2> 118 * <p> 119 * You can interrupt the thread a tailer is running on by calling {@link Thread#interrupt()}. 120 * </p> 121 * <pre> 122 * thread.interrupt(); 123 * </pre> 124 * <p> 125 * If you interrupt a tailer, the tailer listener is called with the {@link InterruptedException}. 126 * </p> 127 * <p> 128 * The file is read using the default Charset; this can be overridden if necessary. 129 * </p> 130 * 131 * @see Builder 132 * @see TailerListener 133 * @see TailerListenerAdapter 134 * @since 2.0 135 * @since 2.5 Updated behavior and documentation for {@link Thread#interrupt()}. 136 * @since 2.12.0 Add {@link Tailable} and {@link RandomAccessResourceBridge} interfaces to tail of files accessed using 137 * alternative libraries such as jCIFS or <a href="https://commons.apache.org/proper/commons-vfs/">Apache Commons 138 * VFS</a>. 139 */ 140public class Tailer implements Runnable, AutoCloseable { 141 // @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}