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