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