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