View Javadoc
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 }