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