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