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 }