1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 * TODO remove once https://issues.apache.org/jira/browse/IO-444 is fixed 18 */ 19 package org.apache.commons.io.input; 20 21 import java.io.File; 22 import java.nio.charset.Charset; 23 import java.util.concurrent.CountDownLatch; 24 import java.util.concurrent.Executors; 25 import java.util.concurrent.ScheduledExecutorService; 26 import java.util.concurrent.ScheduledFuture; 27 import java.util.concurrent.TimeUnit; 28 29 /** 30 * Simple implementation of the unix "tail -f" functionality. 31 * 32 * <h2>1. Create a TailerListener implementation</h2> 33 * <p> 34 * First you need to create a {@link TailerListener} implementation ( 35 * {@link TailerListenerAdapter} is provided for convenience so that you don't 36 * have to implement every method). 37 * </p> 38 * 39 * <p> 40 * For example: 41 * </p> 42 * 43 * <pre> 44 * public class MyTailerListener extends TailerListenerAdapter { 45 * public void handle(String line) { 46 * System.out.println(line); 47 * } 48 * } 49 * </pre> 50 * 51 * <h2>2. Using a Tailer</h2> 52 * 53 * <p> 54 * You can create and use a Tailer in one of three ways: 55 * </p> 56 * <ul> 57 * <li>Using one of the static helper methods: 58 * <ul> 59 * <li>{@link Tailer#create(File, TailerListener)}</li> 60 * <li>{@link Tailer#create(File, TailerListener, long)}</li> 61 * <li>{@link Tailer#create(File, TailerListener, long, boolean)}</li> 62 * </ul> 63 * </li> 64 * <li>Using an {@link java.util.concurrent.Executor}</li> 65 * <li>Using an {@link Thread}</li> 66 * </ul> 67 * 68 * <p> 69 * An example of each of these is shown below. 70 * </p> 71 * 72 * <h3>2.1 Using the static helper method</h3> 73 * 74 * <pre> 75 * TailerListener listener = new MyTailerListener(); 76 * Tailer tailer = Tailer.create(file, listener, delay); 77 * </pre> 78 * 79 * <h3>2.2 Using an Executor</h3> 80 * 81 * <pre> 82 * TailerListener listener = new MyTailerListener(); 83 * Tailer tailer = new Tailer(file, listener, delay); 84 * 85 * // stupid executor impl. for demo purposes 86 * Executor executor = new Executor() { 87 * public void execute(Runnable command) { 88 * command.run(); 89 * } 90 * }; 91 * 92 * executor.execute(tailer); 93 * </pre> 94 * 95 * 96 * <h3>2.3 Using a Thread</h3> 97 * 98 * <pre> 99 * TailerListener listener = new MyTailerListener(); 100 * Tailer tailer = new Tailer(file, listener, delay); 101 * Thread thread = new Thread(tailer); 102 * thread.setDaemon(true); // optional 103 * thread.start(); 104 * </pre> 105 * 106 * <h2>3. Stopping a Tailer</h2> 107 * <p> 108 * Remember to stop the tailer when you have done with it: 109 * </p> 110 * 111 * <pre> 112 * tailer.stop(); 113 * </pre> 114 * 115 * <h2>4. Interrupting a Tailer</h2> 116 * <p> 117 * You can interrupt the thread a tailer is running on by calling 118 * {@link Thread#interrupt()}. 119 * </p> 120 * 121 * <pre> 122 * thread.interrupt(); 123 * </pre> 124 * <p> 125 * If you interrupt a tailer, the tailer listener is called with the 126 * {@link InterruptedException}. 127 * </p> 128 * 129 * <p> 130 * The file is read using the default charset; this can be overriden if 131 * necessary 132 * </p> 133 * 134 * @see TailerListener 135 * @see TailerListenerAdapter 136 * @version $Id$ 137 * @since 2.0 138 * @since 2.5 Updated behavior and documentation for {@link Thread#interrupt()} 139 */ 140 public class Tailer implements Runnable { 141 142 private static final int DEFAULT_BUFSIZE = 4096; 143 144 // The default charset used for reading files 145 private static final Charset DEFAULT_CHARSET = Charset.defaultCharset(); 146 147 private static final int DEFAULT_DELAY_MILLIS = 1000; 148 149 /** 150 * Creates and starts a Tailer for the given file. 151 * 152 * @param file 153 * the file to follow. 154 * @param charset 155 * the character set to use for reading the file 156 * @param listener 157 * the TailerListener to use. 158 * @param delayMillis 159 * the delay between checks of the file for new content in 160 * milliseconds. 161 * @param end 162 * Set to true to tail from the end of the file, false to tail 163 * from the beginning of the file. 164 * @param reOpen 165 * whether to close/reopen the file between chunks 166 * @param bufSize 167 * buffer size. 168 * @return The new tailer 169 */ 170 public static Tailer create(final File file, final Charset charset, final TailerListener listener, 171 final long delayMillis, final boolean end, final boolean reOpen, final int bufSize) { 172 final Tailer tailer = new Tailer(file, charset, listener, delayMillis, end, reOpen, bufSize); 173 final Thread thread = new Thread(tailer); 174 thread.setDaemon(true); 175 thread.start(); 176 return tailer; 177 } 178 179 /** 180 * Creates and starts a Tailer for the given file, starting at the beginning 181 * of the file with the default delay of 1.0s 182 * 183 * @param file 184 * the file to follow. 185 * @param listener 186 * the TailerListener to use. 187 * @return The new tailer 188 */ 189 public static Tailer create(final File file, final TailerListener listener) { 190 return Tailer.create(file, listener, Tailer.DEFAULT_DELAY_MILLIS, false); 191 } 192 193 /** 194 * Creates and starts a Tailer for the given file, starting at the beginning 195 * of the file 196 * 197 * @param file 198 * the file to follow. 199 * @param listener 200 * the TailerListener to use. 201 * @param delayMillis 202 * the delay between checks of the file for new content in 203 * milliseconds. 204 * @return The new tailer 205 */ 206 public static Tailer create(final File file, final TailerListener listener, final long delayMillis) { 207 return Tailer.create(file, listener, delayMillis, false); 208 } 209 210 /** 211 * Creates and starts a Tailer for the given file with default buffer size. 212 * 213 * @param file 214 * the file to follow. 215 * @param listener 216 * the TailerListener to use. 217 * @param delayMillis 218 * the delay between checks of the file for new content in 219 * milliseconds. 220 * @param end 221 * Set to true to tail from the end of the file, false to tail 222 * from the beginning of the file. 223 * @return The new tailer 224 */ 225 public static Tailer create(final File file, final TailerListener listener, final long delayMillis, 226 final boolean end) { 227 return Tailer.create(file, listener, delayMillis, end, Tailer.DEFAULT_BUFSIZE); 228 } 229 230 /** 231 * Creates and starts a Tailer for the given file with default buffer size. 232 * 233 * @param file 234 * the file to follow. 235 * @param listener 236 * the TailerListener to use. 237 * @param delayMillis 238 * the delay between checks of the file for new content in 239 * milliseconds. 240 * @param end 241 * Set to true to tail from the end of the file, false to tail 242 * from the beginning of the file. 243 * @param reOpen 244 * whether to close/reopen the file between chunks 245 * @return The new tailer 246 */ 247 public static Tailer create(final File file, final TailerListener listener, final long delayMillis, 248 final boolean end, final boolean reOpen) { 249 return Tailer.create(file, listener, delayMillis, end, reOpen, Tailer.DEFAULT_BUFSIZE); 250 } 251 252 /** 253 * Creates and starts a Tailer for the given file. 254 * 255 * @param file 256 * the file to follow. 257 * @param listener 258 * the TailerListener to use. 259 * @param delayMillis 260 * the delay between checks of the file for new content in 261 * milliseconds. 262 * @param end 263 * Set to true to tail from the end of the file, false to tail 264 * from the beginning of the file. 265 * @param reOpen 266 * whether to close/reopen the file between chunks 267 * @param bufSize 268 * buffer size. 269 * @return The new tailer 270 */ 271 public static Tailer create(final File file, final TailerListener listener, final long delayMillis, 272 final boolean end, final boolean reOpen, final int bufSize) { 273 return Tailer.create(file, Tailer.DEFAULT_CHARSET, listener, delayMillis, end, reOpen, bufSize); 274 } 275 276 /** 277 * Creates and starts a Tailer for the given file. 278 * 279 * @param file 280 * the file to follow. 281 * @param listener 282 * the TailerListener to use. 283 * @param delayMillis 284 * the delay between checks of the file for new content in 285 * milliseconds. 286 * @param end 287 * Set to true to tail from the end of the file, false to tail 288 * from the beginning of the file. 289 * @param bufSize 290 * buffer size. 291 * @return The new tailer 292 */ 293 public static Tailer create(final File file, final TailerListener listener, final long delayMillis, 294 final boolean end, final int bufSize) { 295 return Tailer.create(file, listener, delayMillis, end, false, bufSize); 296 } 297 298 /** 299 * The amount of time to wait for the file to be updated. 300 */ 301 private final long delayMillis; 302 303 private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 304 305 /** 306 * The file which will be tailed. 307 */ 308 private final File file; 309 310 private final TailerListener listener; 311 312 private final CountDownLatch runTrigger = new CountDownLatch(1); 313 314 private final TailerRun scheduled; 315 316 /** 317 * Creates a Tailer for the given file, with a specified buffer size. 318 * 319 * @param file 320 * the file to follow. 321 * @param cset 322 * the Charset to be used for reading the file 323 * @param listener 324 * the TailerListener to use. 325 * @param delayMillis 326 * the delay between checks of the file for new content in 327 * milliseconds. 328 * @param end 329 * Set to true to tail from the end of the file, false to tail 330 * from the beginning of the file. 331 * @param reOpen 332 * if true, close and reopen the file between reading chunks 333 * @param bufSize 334 * Buffer size 335 */ 336 public Tailer(final File file, final Charset cset, final TailerListener listener, final long delayMillis, 337 final boolean end, final boolean reOpen, final int bufSize) { 338 this.file = file; 339 this.delayMillis = delayMillis; 340 this.listener = listener; 341 listener.init(this); 342 this.scheduled = new TailerRun(file, cset, listener, end, reOpen, bufSize); 343 } 344 345 /** 346 * Creates a Tailer for the given file, starting from the beginning, with 347 * the default delay of 1.0s. 348 * 349 * @param file 350 * The file to follow. 351 * @param listener 352 * the TailerListener to use. 353 */ 354 public Tailer(final File file, final TailerListener listener) { 355 this(file, listener, Tailer.DEFAULT_DELAY_MILLIS); 356 } 357 358 /** 359 * Creates a Tailer for the given file, starting from the beginning. 360 * 361 * @param file 362 * the file to follow. 363 * @param listener 364 * the TailerListener to use. 365 * @param delayMillis 366 * the delay between checks of the file for new content in 367 * milliseconds. 368 */ 369 public Tailer(final File file, final TailerListener listener, final long delayMillis) { 370 this(file, listener, delayMillis, false); 371 } 372 373 /** 374 * Creates a Tailer for the given file, with a delay other than the default 375 * 1.0s. 376 * 377 * @param file 378 * the file to follow. 379 * @param listener 380 * the TailerListener to use. 381 * @param delayMillis 382 * the delay between checks of the file for new content in 383 * milliseconds. 384 * @param end 385 * Set to true to tail from the end of the file, false to tail 386 * from the beginning of the file. 387 */ 388 public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end) { 389 this(file, listener, delayMillis, end, Tailer.DEFAULT_BUFSIZE); 390 } 391 392 /** 393 * Creates a Tailer for the given file, with a delay other than the default 394 * 1.0s. 395 * 396 * @param file 397 * the file to follow. 398 * @param listener 399 * the TailerListener to use. 400 * @param delayMillis 401 * the delay between checks of the file for new content in 402 * milliseconds. 403 * @param end 404 * Set to true to tail from the end of the file, false to tail 405 * from the beginning of the file. 406 * @param reOpen 407 * if true, close and reopen the file between reading chunks 408 */ 409 public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, 410 final boolean reOpen) { 411 this(file, listener, delayMillis, end, reOpen, Tailer.DEFAULT_BUFSIZE); 412 } 413 414 /** 415 * Creates a Tailer for the given file, with a specified buffer size. 416 * 417 * @param file 418 * the file to follow. 419 * @param listener 420 * the TailerListener to use. 421 * @param delayMillis 422 * the delay between checks of the file for new content in 423 * milliseconds. 424 * @param end 425 * Set to true to tail from the end of the file, false to tail 426 * from the beginning of the file. 427 * @param reOpen 428 * if true, close and reopen the file between reading chunks 429 * @param bufSize 430 * Buffer size 431 */ 432 public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, 433 final boolean reOpen, final int bufSize) { 434 this(file, Tailer.DEFAULT_CHARSET, listener, delayMillis, end, reOpen, bufSize); 435 } 436 437 /** 438 * Creates a Tailer for the given file, with a specified buffer size. 439 * 440 * @param file 441 * the file to follow. 442 * @param listener 443 * the TailerListener to use. 444 * @param delayMillis 445 * the delay between checks of the file for new content in 446 * milliseconds. 447 * @param end 448 * Set to true to tail from the end of the file, false to tail 449 * from the beginning of the file. 450 * @param bufSize 451 * Buffer size 452 */ 453 public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, 454 final int bufSize) { 455 this(file, listener, delayMillis, end, false, bufSize); 456 } 457 458 /** 459 * Return the delay in milliseconds. 460 * 461 * @return the delay in milliseconds. 462 */ 463 public long getDelay() { 464 return this.delayMillis; 465 } 466 467 /** 468 * Return the file. 469 * 470 * @return the file 471 */ 472 public File getFile() { 473 return this.file; 474 } 475 476 /** 477 * Gets whether to keep on running. 478 * 479 * @return whether to keep on running. 480 * @since 2.5 481 */ 482 protected boolean getRun() { 483 return this.runTrigger.getCount() > 0; 484 } 485 486 /** 487 * Follows changes in the file, calling the TailerListener's handle method 488 * for each new line. 489 */ 490 @Override 491 public void run() { 492 final ScheduledFuture<?> future = this.executor.scheduleWithFixedDelay(this.scheduled, 0, this.delayMillis, 493 TimeUnit.MILLISECONDS); 494 try { 495 this.runTrigger.await(); 496 } catch (final InterruptedException e) { 497 this.listener.handle(e); 498 } finally { 499 future.cancel(true); // stop the periodic reading 500 this.scheduled.cleanup(); 501 this.executor.shutdownNow(); 502 this.listener.destroy(); 503 } 504 } 505 506 /** 507 * Allows the tailer to complete its current loop and return. 508 */ 509 public void stop() { 510 this.runTrigger.countDown(); 511 } 512 513 public void waitUntilStarted() { 514 while (!this.scheduled.hasStartedOnce() || this.scheduled.hasFinished()) { 515 try { 516 // wait for a miniscule amount of time 517 Thread.sleep(Math.max(this.getDelay() / 100, 1)); 518 } catch (final InterruptedException e) { 519 // do nothing 520 } 521 } 522 } 523 524 }