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