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()}.
109 * </p>
110 * <pre>
111 *      thread.interrupt();
112 * </pre>
113 * <p>
114 * If you interrupt a tailer, the tailer listener is called with the {@link InterruptedException}.
115 * </p>
116 * <p>
117 * The file is read using the default charset; this can be overridden if necessary.
118 * </p>
119 * @see TailerListener
120 * @see TailerListenerAdapter
121 * @since 2.0
122 * @since 2.5 Updated behavior and documentation for {@link Thread#interrupt()}
123 */
124public class Tailer implements Runnable {
125
126    private static final int DEFAULT_DELAY_MILLIS = 1000;
127
128    private static final String RAF_MODE = "r";
129
130    // The default charset used for reading files
131    private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
132
133    /**
134     * Buffer on top of RandomAccessFile.
135     */
136    private final byte[] inbuf;
137
138    /**
139     * The file which will be tailed.
140     */
141    private final File file;
142
143    /**
144     * The character set that will be used to read the file.
145     */
146    private final Charset charset;
147
148    /**
149     * The amount of time to wait for the file to be updated.
150     */
151    private final long delayMillis;
152
153    /**
154     * Whether to tail from the end or start of file
155     */
156    private final boolean end;
157
158    /**
159     * The listener to notify of events when tailing.
160     */
161    private final TailerListener listener;
162
163    /**
164     * Whether to close and reopen the file whilst waiting for more input.
165     */
166    private final boolean reOpen;
167
168    /**
169     * The tailer will run as long as this value is true.
170     */
171    private volatile boolean run = true;
172
173    /**
174     * Creates a Tailer for the given file, starting from the beginning, with the default delay of 1.0s.
175     * @param file The file to follow.
176     * @param listener the TailerListener to use.
177     */
178    public Tailer(final File file, final TailerListener listener) {
179        this(file, listener, DEFAULT_DELAY_MILLIS);
180    }
181
182    /**
183     * Creates a Tailer for the given file, starting from the beginning.
184     * @param file the file to follow.
185     * @param listener the TailerListener to use.
186     * @param delayMillis the delay between checks of the file for new content in milliseconds.
187     */
188    public Tailer(final File file, final TailerListener listener, final long delayMillis) {
189        this(file, listener, delayMillis, false);
190    }
191
192    /**
193     * Creates a Tailer for the given file, with a delay other than the default 1.0s.
194     * @param file the file to follow.
195     * @param listener the TailerListener to use.
196     * @param delayMillis the delay between checks of the file for new content in milliseconds.
197     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
198     */
199    public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end) {
200        this(file, listener, delayMillis, end, IOUtils.DEFAULT_BUFFER_SIZE);
201    }
202
203    /**
204     * Creates a Tailer for the given file, with a delay other than the default 1.0s.
205     * @param file the file to follow.
206     * @param listener the TailerListener to use.
207     * @param delayMillis the delay between checks of the file for new content in milliseconds.
208     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
209     * @param reOpen if true, close and reopen the file between reading chunks
210     */
211    public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end,
212                  final boolean reOpen) {
213        this(file, listener, delayMillis, end, reOpen, IOUtils.DEFAULT_BUFFER_SIZE);
214    }
215
216    /**
217     * Creates a Tailer for the given file, with a specified buffer size.
218     * @param file the file to follow.
219     * @param listener the TailerListener to use.
220     * @param delayMillis the delay between checks of the file for new content in milliseconds.
221     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
222     * @param bufSize Buffer size
223     */
224    public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end,
225                  final int bufSize) {
226        this(file, listener, delayMillis, end, false, bufSize);
227    }
228
229    /**
230     * Creates a Tailer for the given file, with a specified buffer size.
231     * @param file the file to follow.
232     * @param listener the TailerListener to use.
233     * @param delayMillis the delay between checks of the file for new content in milliseconds.
234     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
235     * @param reOpen if true, close and reopen the file between reading chunks
236     * @param bufSize Buffer size
237     */
238    public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end,
239                  final boolean reOpen, final int bufSize) {
240        this(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, bufSize);
241    }
242
243    /**
244     * Creates a Tailer for the given file, with a specified buffer size.
245     * @param file the file to follow.
246     * @param charset the Charset to be used for reading the file
247     * @param listener the TailerListener to use.
248     * @param delayMillis the delay between checks of the file for new content in milliseconds.
249     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
250     * @param reOpen if true, close and reopen the file between reading chunks
251     * @param bufSize Buffer size
252     */
253    public Tailer(final File file, final Charset charset, final TailerListener listener, final long delayMillis,
254                  final boolean end, final boolean reOpen
255            , final int bufSize) {
256        this.file = file;
257        this.delayMillis = delayMillis;
258        this.end = end;
259
260        this.inbuf = new byte[bufSize];
261
262        // Save and prepare the listener
263        this.listener = listener;
264        listener.init(this);
265        this.reOpen = reOpen;
266        this.charset = charset;
267    }
268
269    /**
270     * Creates and starts a Tailer for the given file.
271     *
272     * @param file the file to follow.
273     * @param listener the TailerListener to use.
274     * @param delayMillis the delay between checks of the file for new content in milliseconds.
275     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
276     * @param bufSize buffer size.
277     * @return The new tailer
278     */
279    public static Tailer create(final File file, final TailerListener listener, final long delayMillis,
280                                final boolean end, final int bufSize) {
281        return create(file, listener, delayMillis, end, false, bufSize);
282    }
283
284    /**
285     * Creates and starts a Tailer for the given file.
286     *
287     * @param file the file to follow.
288     * @param listener the TailerListener to use.
289     * @param delayMillis the delay between checks of the file for new content in milliseconds.
290     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
291     * @param reOpen whether to close/reopen the file between chunks
292     * @param bufSize 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 boolean reOpen,
297            final int bufSize) {
298        return create(file, DEFAULT_CHARSET, listener, delayMillis, end, reOpen, bufSize);
299    }
300
301    /**
302     * Creates and starts a Tailer for the given file.
303     *
304     * @param file the file to follow.
305     * @param charset the character set to use for reading the file
306     * @param listener the TailerListener to use.
307     * @param delayMillis the delay between checks of the file for new content in milliseconds.
308     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
309     * @param reOpen whether to close/reopen the file between chunks
310     * @param bufSize buffer size.
311     * @return The new tailer
312     */
313    public static Tailer create(final File file, final Charset charset, final TailerListener listener,
314                                final long delayMillis, final boolean end, final boolean reOpen
315            ,final int bufSize) {
316        final Tailer tailer = new Tailer(file, charset, listener, delayMillis, end, reOpen, bufSize);
317        final Thread thread = new Thread(tailer);
318        thread.setDaemon(true);
319        thread.start();
320        return tailer;
321    }
322
323    /**
324     * Creates and starts a Tailer for the given file with default buffer size.
325     *
326     * @param file the file to follow.
327     * @param listener the TailerListener to use.
328     * @param delayMillis the delay between checks of the file for new content in milliseconds.
329     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
330     * @return The new tailer
331     */
332    public static Tailer create(final File file, final TailerListener listener, final long delayMillis,
333                                final boolean end) {
334        return create(file, listener, delayMillis, end, IOUtils.DEFAULT_BUFFER_SIZE);
335    }
336
337    /**
338     * Creates and starts a Tailer for the given file with default buffer size.
339     *
340     * @param file the file to follow.
341     * @param listener the TailerListener to use.
342     * @param delayMillis the delay between checks of the file for new content in milliseconds.
343     * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
344     * @param reOpen whether to close/reopen the file between chunks
345     * @return The new tailer
346     */
347    public static Tailer create(final File file, final TailerListener listener, final long delayMillis,
348                                final boolean end, final boolean reOpen) {
349        return create(file, listener, delayMillis, end, reOpen, IOUtils.DEFAULT_BUFFER_SIZE);
350    }
351
352    /**
353     * Creates and starts a Tailer for the given file, starting at the beginning of the file
354     *
355     * @param file the file to follow.
356     * @param listener the TailerListener to use.
357     * @param delayMillis the delay between checks of the file for new content in milliseconds.
358     * @return The new tailer
359     */
360    public static Tailer create(final File file, final TailerListener listener, final long delayMillis) {
361        return create(file, listener, delayMillis, false);
362    }
363
364    /**
365     * Creates and starts a Tailer for the given file, starting at the beginning of the file
366     * with the default delay of 1.0s
367     *
368     * @param file the file to follow.
369     * @param listener the TailerListener to use.
370     * @return The new tailer
371     */
372    public static Tailer create(final File file, final TailerListener listener) {
373        return create(file, listener, DEFAULT_DELAY_MILLIS, false);
374    }
375
376    /**
377     * Return the file.
378     *
379     * @return the file
380     */
381    public File getFile() {
382        return file;
383    }
384
385    /**
386     * Gets whether to keep on running.
387     *
388     * @return whether to keep on running.
389     * @since 2.5
390     */
391    protected boolean getRun() {
392        return run;
393    }
394
395    /**
396     * Return the delay in milliseconds.
397     *
398     * @return the delay in milliseconds.
399     */
400    public long getDelay() {
401        return delayMillis;
402    }
403
404    /**
405     * Follows changes in the file, calling the TailerListener's handle method for each new line.
406     */
407    @Override
408    public void run() {
409        RandomAccessFile reader = null;
410        try {
411            long last = 0; // The last time the file was checked for changes
412            long position = 0; // position within the file
413            // Open the file
414            while (getRun() && reader == null) {
415                try {
416                    reader = new RandomAccessFile(file, RAF_MODE);
417                } catch (final FileNotFoundException e) {
418                    listener.fileNotFound();
419                }
420                if (reader == null) {
421                    Thread.sleep(delayMillis);
422                } else {
423                    // The current position in the file
424                    position = end ? file.length() : 0;
425                    last = file.lastModified();
426                    reader.seek(position);
427                }
428            }
429            while (getRun()) {
430                final boolean newer = FileUtils.isFileNewer(file, last); // IO-279, must be done first
431                // Check the file length to see if it was rotated
432                final long length = file.length();
433                if (length < position) {
434                    // File was rotated
435                    listener.fileRotated();
436                    // Reopen the reader after rotation ensuring that the old file is closed iff we re-open it
437                    // successfully
438                    try (RandomAccessFile save = reader) {
439                        reader = new RandomAccessFile(file, RAF_MODE);
440                        // At this point, we're sure that the old file is rotated
441                        // Finish scanning the old file and then we'll start with the new one
442                        try {
443                            readLines(save);
444                        }  catch (final IOException ioe) {
445                            listener.handle(ioe);
446                        }
447                        position = 0;
448                    } catch (final FileNotFoundException e) {
449                        // in this case we continue to use the previous reader and position values
450                        listener.fileNotFound();
451                        Thread.sleep(delayMillis);
452                    }
453                    continue;
454                }
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                if (reOpen && reader != null) {
474                    reader.close();
475                }
476                Thread.sleep(delayMillis);
477                if (getRun() && reOpen) {
478                    reader = new RandomAccessFile(file, RAF_MODE);
479                    reader.seek(position);
480                }
481            }
482        } catch (final InterruptedException e) {
483            Thread.currentThread().interrupt();
484            listener.handle(e);
485        } catch (final Exception e) {
486            listener.handle(e);
487        } finally {
488            try {
489                if (reader != null) {
490                    reader.close();
491                }
492            }
493            catch (final IOException e) {
494                listener.handle(e);
495            }
496            stop();
497        }
498    }
499
500    /**
501     * Allows the tailer to complete its current loop and return.
502     */
503    public void stop() {
504        this.run = false;
505    }
506
507    /**
508     * Read new lines.
509     *
510     * @param reader The file to read
511     * @return The new position after the lines have been read
512     * @throws java.io.IOException if an I/O error occurs.
513     */
514    private long readLines(final RandomAccessFile reader) throws IOException {
515        try (ByteArrayOutputStream lineBuf = new ByteArrayOutputStream(64)) {
516            long pos = reader.getFilePointer();
517            long rePos = pos; // position to re-read
518            int num;
519            boolean seenCR = false;
520            while (getRun() && ((num = reader.read(inbuf)) != EOF)) {
521                for (int i = 0; i < num; i++) {
522                    final byte ch = inbuf[i];
523                    switch ( ch ) {
524                        case '\n':
525                            seenCR = false; // swallow CR before LF
526                            listener.handle(new String(lineBuf.toByteArray(), charset));
527                            lineBuf.reset();
528                            rePos = pos + i + 1;
529                            break;
530                        case '\r':
531                            if (seenCR) {
532                                lineBuf.write('\r');
533                            }
534                            seenCR = true;
535                            break;
536                        default:
537                            if (seenCR) {
538                                seenCR = false; // swallow final CR
539                                listener.handle(new String(lineBuf.toByteArray(), charset));
540                                lineBuf.reset();
541                                rePos = pos + i + 1;
542                            }
543                            lineBuf.write(ch);
544                    }
545                }
546                pos = reader.getFilePointer();
547            }
548
549            reader.seek(rePos); // Ensure we can re-read if necessary
550
551            if (listener instanceof TailerListenerAdapter) {
552                ((TailerListenerAdapter) listener).endOfFileReached();
553            }
554
555            return rePos;
556        }
557    }
558}