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