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 */ 017 package org.apache.commons.io.input; 018 019 import java.io.File; 020 import java.io.FileNotFoundException; 021 import java.io.IOException; 022 import java.io.RandomAccessFile; 023 024 import org.apache.commons.io.FileUtils; 025 import org.apache.commons.io.IOUtils; 026 027 /** 028 * Simple implementation of the unix "tail -f" functionality. 029 * <p> 030 * <h2>1. Create a TailerListener implementation</h3> 031 * <p> 032 * First you need to create a {@link TailerListener} implementation 033 * ({@link TailerListenerAdapter} is provided for convenience so that you don't have to 034 * implement every method). 035 * </p> 036 * 037 * <p>For example:</p> 038 * <pre> 039 * public class MyTailerListener extends TailerListenerAdapter { 040 * public void handle(String line) { 041 * System.out.println(line); 042 * } 043 * } 044 * </pre> 045 * 046 * <h2>2. Using a Tailer</h2> 047 * 048 * You can create and use a Tailer in one of three ways: 049 * <ul> 050 * <li>Using one of the static helper methods: 051 * <ul> 052 * <li>{@link Tailer#create(File, TailerListener)}</li> 053 * <li>{@link Tailer#create(File, TailerListener, long)}</li> 054 * <li>{@link Tailer#create(File, TailerListener, long, boolean)}</li> 055 * </ul> 056 * </li> 057 * <li>Using an {@link java.util.concurrent.Executor}</li> 058 * <li>Using an {@link Thread}</li> 059 * </ul> 060 * 061 * An example of each of these is shown below. 062 * 063 * <h3>2.1 Using the static helper method</h3> 064 * 065 * <pre> 066 * TailerListener listener = new MyTailerListener(); 067 * Tailer tailer = Tailer.create(file, listener, delay); 068 * </pre> 069 * 070 * <h3>2.2 Use an Executor</h3> 071 * 072 * <pre> 073 * TailerListener listener = new MyTailerListener(); 074 * Tailer tailer = new Tailer(file, listener, delay); 075 * 076 * // stupid executor impl. for demo purposes 077 * Executor executor = new Executor() { 078 * public void execute(Runnable command) { 079 * command.run(); 080 * } 081 * }; 082 * 083 * executor.execute(tailer); 084 * </pre> 085 * 086 * 087 * <h3>2.3 Use a Thread</h3> 088 * <pre> 089 * TailerListener listener = new MyTailerListener(); 090 * Tailer tailer = new Tailer(file, listener, delay); 091 * Thread thread = new Thread(tailer); 092 * thread.setDaemon(true); // optional 093 * thread.start(); 094 * </pre> 095 * 096 * <h2>3. Stop Tailing</h3> 097 * <p>Remember to stop the tailer when you have done with it:</p> 098 * <pre> 099 * tailer.stop(); 100 * </pre> 101 * 102 * @see TailerListener 103 * @see TailerListenerAdapter 104 * @version $Id: Tailer.java 1348698 2012-06-11 01:09:58Z ggregory $ 105 * @since 2.0 106 */ 107 public class Tailer implements Runnable { 108 109 private static final int DEFAULT_DELAY_MILLIS = 1000; 110 111 private static final String RAF_MODE = "r"; 112 113 private static final int DEFAULT_BUFSIZE = 4096; 114 115 /** 116 * Buffer on top of RandomAccessFile. 117 */ 118 private final byte inbuf[]; 119 120 /** 121 * The file which will be tailed. 122 */ 123 private final File file; 124 125 /** 126 * The amount of time to wait for the file to be updated. 127 */ 128 private final long delayMillis; 129 130 /** 131 * Whether to tail from the end or start of file 132 */ 133 private final boolean end; 134 135 /** 136 * The listener to notify of events when tailing. 137 */ 138 private final TailerListener listener; 139 140 /** 141 * Whether to close and reopen the file whilst waiting for more input. 142 */ 143 private final boolean reOpen; 144 145 /** 146 * The tailer will run as long as this value is true. 147 */ 148 private volatile boolean run = true; 149 150 /** 151 * Creates a Tailer for the given file, starting from the beginning, with the default delay of 1.0s. 152 * @param file The file to follow. 153 * @param listener the TailerListener to use. 154 */ 155 public Tailer(File file, TailerListener listener) { 156 this(file, listener, DEFAULT_DELAY_MILLIS); 157 } 158 159 /** 160 * Creates a Tailer for the given file, starting from the beginning. 161 * @param file the file to follow. 162 * @param listener the TailerListener to use. 163 * @param delayMillis the delay between checks of the file for new content in milliseconds. 164 */ 165 public Tailer(File file, TailerListener listener, long delayMillis) { 166 this(file, listener, delayMillis, false); 167 } 168 169 /** 170 * Creates a Tailer for the given file, with a delay other than the default 1.0s. 171 * @param file the file to follow. 172 * @param listener the TailerListener to use. 173 * @param delayMillis the delay between checks of the file for new content in milliseconds. 174 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 175 */ 176 public Tailer(File file, TailerListener listener, long delayMillis, boolean end) { 177 this(file, listener, delayMillis, end, DEFAULT_BUFSIZE); 178 } 179 180 /** 181 * Creates a Tailer for the given file, with a delay other than the default 1.0s. 182 * @param file the file to follow. 183 * @param listener the TailerListener to use. 184 * @param delayMillis the delay between checks of the file for new content in milliseconds. 185 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 186 * @param reOpen if true, close and reopen the file between reading chunks 187 */ 188 public Tailer(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen) { 189 this(file, listener, delayMillis, end, reOpen, DEFAULT_BUFSIZE); 190 } 191 192 /** 193 * Creates a Tailer for the given file, with a specified buffer size. 194 * @param file the file to follow. 195 * @param listener the TailerListener to use. 196 * @param delayMillis the delay between checks of the file for new content in milliseconds. 197 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 198 * @param bufSize Buffer size 199 */ 200 public Tailer(File file, TailerListener listener, long delayMillis, boolean end, int bufSize) { 201 this(file, listener, delayMillis, end, false, bufSize); 202 } 203 204 /** 205 * Creates a Tailer for the given file, with a specified buffer size. 206 * @param file the file to follow. 207 * @param listener the TailerListener to use. 208 * @param delayMillis the delay between checks of the file for new content in milliseconds. 209 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 210 * @param reOpen if true, close and reopen the file between reading chunks 211 * @param bufSize Buffer size 212 */ 213 public Tailer(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen, int bufSize) { 214 this.file = file; 215 this.delayMillis = delayMillis; 216 this.end = end; 217 218 this.inbuf = new byte[bufSize]; 219 220 // Save and prepare the listener 221 this.listener = listener; 222 listener.init(this); 223 this.reOpen = reOpen; 224 } 225 226 /** 227 * Creates and starts a Tailer for the given file. 228 * 229 * @param file the file to follow. 230 * @param listener the TailerListener to use. 231 * @param delayMillis the delay between checks of the file for new content in milliseconds. 232 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 233 * @param bufSize buffer size. 234 * @return The new tailer 235 */ 236 public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end, int bufSize) { 237 Tailer tailer = new Tailer(file, listener, delayMillis, end, bufSize); 238 Thread thread = new Thread(tailer); 239 thread.setDaemon(true); 240 thread.start(); 241 return tailer; 242 } 243 244 /** 245 * Creates and starts a Tailer for the given file. 246 * 247 * @param file the file to follow. 248 * @param listener the TailerListener to use. 249 * @param delayMillis the delay between checks of the file for new content in milliseconds. 250 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 251 * @param reOpen whether to close/reopen the file between chunks 252 * @param bufSize buffer size. 253 * @return The new tailer 254 */ 255 public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen, 256 int bufSize) { 257 Tailer tailer = new Tailer(file, listener, delayMillis, end, reOpen, bufSize); 258 Thread thread = new Thread(tailer); 259 thread.setDaemon(true); 260 thread.start(); 261 return tailer; 262 } 263 264 /** 265 * Creates and starts a Tailer for the given file with default buffer size. 266 * 267 * @param file the file to follow. 268 * @param listener the TailerListener to use. 269 * @param delayMillis the delay between checks of the file for new content in milliseconds. 270 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 271 * @return The new tailer 272 */ 273 public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end) { 274 return create(file, listener, delayMillis, end, DEFAULT_BUFSIZE); 275 } 276 277 /** 278 * Creates and starts a Tailer for the given file with default buffer size. 279 * 280 * @param file the file to follow. 281 * @param listener the TailerListener to use. 282 * @param delayMillis the delay between checks of the file for new content in milliseconds. 283 * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file. 284 * @param reOpen whether to close/reopen the file between chunks 285 * @return The new tailer 286 */ 287 public static Tailer create(File file, TailerListener listener, long delayMillis, boolean end, boolean reOpen) { 288 return create(file, listener, delayMillis, end, reOpen, DEFAULT_BUFSIZE); 289 } 290 291 /** 292 * Creates and starts a Tailer for the given file, starting at the beginning of the file 293 * 294 * @param file the file to follow. 295 * @param listener the TailerListener to use. 296 * @param delayMillis the delay between checks of the file for new content in milliseconds. 297 * @return The new tailer 298 */ 299 public static Tailer create(File file, TailerListener listener, long delayMillis) { 300 return create(file, listener, delayMillis, false); 301 } 302 303 /** 304 * Creates and starts a Tailer for the given file, starting at the beginning of the file 305 * with the default delay of 1.0s 306 * 307 * @param file the file to follow. 308 * @param listener the TailerListener to use. 309 * @return The new tailer 310 */ 311 public static Tailer create(File file, TailerListener listener) { 312 return create(file, listener, DEFAULT_DELAY_MILLIS, false); 313 } 314 315 /** 316 * Return the file. 317 * 318 * @return the file 319 */ 320 public File getFile() { 321 return file; 322 } 323 324 /** 325 * Return the delay in milliseconds. 326 * 327 * @return the delay in milliseconds. 328 */ 329 public long getDelay() { 330 return delayMillis; 331 } 332 333 /** 334 * Follows changes in the file, calling the TailerListener's handle method for each new line. 335 */ 336 public void run() { 337 RandomAccessFile reader = null; 338 try { 339 long last = 0; // The last time the file was checked for changes 340 long position = 0; // position within the file 341 // Open the file 342 while (run && reader == null) { 343 try { 344 reader = new RandomAccessFile(file, RAF_MODE); 345 } catch (FileNotFoundException e) { 346 listener.fileNotFound(); 347 } 348 349 if (reader == null) { 350 try { 351 Thread.sleep(delayMillis); 352 } catch (InterruptedException e) { 353 } 354 } else { 355 // The current position in the file 356 position = end ? file.length() : 0; 357 last = System.currentTimeMillis(); 358 reader.seek(position); 359 } 360 } 361 362 while (run) { 363 364 boolean newer = FileUtils.isFileNewer(file, last); // IO-279, must be done first 365 366 // Check the file length to see if it was rotated 367 long length = file.length(); 368 369 if (length < position) { 370 371 // File was rotated 372 listener.fileRotated(); 373 374 // Reopen the reader after rotation 375 try { 376 // Ensure that the old file is closed iff we re-open it successfully 377 RandomAccessFile save = reader; 378 reader = new RandomAccessFile(file, RAF_MODE); 379 position = 0; 380 // close old file explicitly rather than relying on GC picking up previous RAF 381 IOUtils.closeQuietly(save); 382 } catch (FileNotFoundException e) { 383 // in this case we continue to use the previous reader and position values 384 listener.fileNotFound(); 385 } 386 continue; 387 } else { 388 389 // File was not rotated 390 391 // See if the file needs to be read again 392 if (length > position) { 393 394 // The file has more content than it did last time 395 position = readLines(reader); 396 last = System.currentTimeMillis(); 397 398 } else if (newer) { 399 400 /* 401 * This can happen if the file is truncated or overwritten with the exact same length of 402 * information. In cases like this, the file position needs to be reset 403 */ 404 position = 0; 405 reader.seek(position); // cannot be null here 406 407 // Now we can read new lines 408 position = readLines(reader); 409 last = System.currentTimeMillis(); 410 } 411 } 412 if (reOpen) { 413 IOUtils.closeQuietly(reader); 414 } 415 try { 416 Thread.sleep(delayMillis); 417 } catch (InterruptedException e) { 418 } 419 if (run && reOpen) { 420 reader = new RandomAccessFile(file, RAF_MODE); 421 reader.seek(position); 422 } 423 } 424 425 } catch (Exception e) { 426 427 listener.handle(e); 428 429 } finally { 430 IOUtils.closeQuietly(reader); 431 } 432 } 433 434 /** 435 * Allows the tailer to complete its current loop and return. 436 */ 437 public void stop() { 438 this.run = false; 439 } 440 441 /** 442 * Read new lines. 443 * 444 * @param reader The file to read 445 * @return The new position after the lines have been read 446 * @throws java.io.IOException if an I/O error occurs. 447 */ 448 private long readLines(RandomAccessFile reader) throws IOException { 449 StringBuilder sb = new StringBuilder(); 450 451 long pos = reader.getFilePointer(); 452 long rePos = pos; // position to re-read 453 454 int num; 455 boolean seenCR = false; 456 while (run && ((num = reader.read(inbuf)) != -1)) { 457 for (int i = 0; i < num; i++) { 458 byte ch = inbuf[i]; 459 switch (ch) { 460 case '\n': 461 seenCR = false; // swallow CR before LF 462 listener.handle(sb.toString()); 463 sb.setLength(0); 464 rePos = pos + i + 1; 465 break; 466 case '\r': 467 if (seenCR) { 468 sb.append('\r'); 469 } 470 seenCR = true; 471 break; 472 default: 473 if (seenCR) { 474 seenCR = false; // swallow final CR 475 listener.handle(sb.toString()); 476 sb.setLength(0); 477 rePos = pos + i + 1; 478 } 479 sb.append((char) ch); // add character, not its ascii value 480 } 481 } 482 483 pos = reader.getFilePointer(); 484 } 485 486 reader.seek(rePos); // Ensure we can re-read if necessary 487 return rePos; 488 } 489 490 }