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 }