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.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 }