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