Author: niallp
Date: Wed Sep 29 20:56:23 2010
New Revision: 1002844

URL: http://svn.apache.org/viewvc?rev=1002844&view=rev
Log:
IO-177 New Tailer class - Simple implementation of the unix "tail -f" 
functionality - thanks to Jeff Rodriguez for the patch

Added:
    commons/proper/io/trunk/src/java/org/apache/commons/io/input/Tailer.java   
(with props)
    
commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListener.java
   (with props)
    
commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListenerAdapter.java
   (with props)
    
commons/proper/io/trunk/src/test/org/apache/commons/io/input/TailerTest.java   
(with props)

Added: commons/proper/io/trunk/src/java/org/apache/commons/io/input/Tailer.java
URL: 
http://svn.apache.org/viewvc/commons/proper/io/trunk/src/java/org/apache/commons/io/input/Tailer.java?rev=1002844&view=auto
==============================================================================
--- commons/proper/io/trunk/src/java/org/apache/commons/io/input/Tailer.java 
(added)
+++ commons/proper/io/trunk/src/java/org/apache/commons/io/input/Tailer.java 
Wed Sep 29 20:56:23 2010
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.input;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import org.apache.commons.io.FileUtils;
+
+/**
+ * Simple implementation of the unix "tail -f" functionality.
+ * <p>
+ * Example Usage:
+ * <pre>
+ *      TailerListener listener = ...
+ *      Tailer tailer = new Tailer(file, listener, delay);
+ *      Thread thread = new Thread(tailer);
+ *      thread.start();
+ * </pre>
+ *
+ * @version $Id$
+ * @since Commons IO 2.0
+ */
+public class Tailer implements Runnable {
+
+    /**
+     * The file which will be tailed.
+     */
+    private final File file;
+
+    /**
+     * The amount of time to wait for the file to be updated.
+     */
+    private final long delay;
+
+    /**
+     * Whether to tail from the end or start of file
+     */
+    private final boolean end;
+
+    /**
+     * The listener to notify of events when tailing.
+     */
+    private final TailerListener listener;
+
+    /**
+     * The tailer will run as long as this value is true.
+     */
+    private boolean run = true;
+
+    /**
+     * The object which will be responsible for actually reading the file.
+     */
+    private RandomAccessFile reader;
+
+    /**
+     * The last position in the file that was read.
+     */
+    private long position;
+
+    /**
+     * The last time the file was checked for changes.
+     */
+    private long last;
+
+    /**
+     * Creates SimpleTailer for the given file.
+     * @param file The file to follow.
+     * @param listener The TailerListener which to use.
+     */
+    public Tailer(File file, TailerListener listener) {
+        this(file, listener, 1000);
+    }
+
+    /**
+     * Creates a SimpleTailer for the given file, with a delay other than the 
default 1.0s.
+     * @param file The file to follow.
+     * @param listener The TailerListener which to use.
+     * @param delay The delay between checks of the file for new content in 
milliseconds.
+     */
+    public Tailer(File file, TailerListener listener, long delay) {
+        this(file, listener, 1000, false);
+    }
+
+    /**
+     * Creates a SimpleTailer for the given file, with a delay other than the 
default 1.0s.
+     * @param file The file to follow.
+     * @param listener The TailerListener which to use.
+     * @param delay The delay between checks of the file for new content in 
milliseconds.
+     * @param end Set to true to tail from the end of the file, false to tail 
from the beginning of the file.
+     */
+    public Tailer(File file, TailerListener listener, long delay, boolean end) 
{
+
+        this.file = file;
+        this.delay = delay;
+        this.end = end;
+
+        // Save and prepare the listener
+        this.listener = listener;
+        listener.init(this);
+    }
+
+    /**
+     * Return the file.
+     *
+     * @return the file
+     */
+    public File getFile() {
+        return file;
+    }
+
+    /**
+     * Return the delay.
+     *
+     * @return the delay
+     */
+    public long getDelay() {
+        return delay;
+    }
+
+    /**
+     * Follows changes in the file, calling the TailerListener's handle method 
for each new line.
+     */
+    public void run() {
+        try {
+            // Open the file
+            while (run && reader == null) {
+                try {
+                    reader = new RandomAccessFile(file, "r");
+                } catch (FileNotFoundException e) {
+                    listener.fileNotFound();
+                }
+
+                if (reader == null) {
+                    Thread.sleep(delay);
+                }
+            }
+
+            // The current position in the file
+            position = end ? file.length() : 0;
+            last = System.currentTimeMillis();
+            reader.seek(position);
+
+            while (run) {
+
+                // Check the file length to see if it was rotated
+                long length = file.length();
+
+                if (length < position) {
+
+                    // File was rotated
+                    listener.fileRotated();
+
+                    // Reopen the reader after rotation
+                    try {
+                        reader = new RandomAccessFile(file, "r");
+                        position = 0;
+                    } catch (FileNotFoundException e) {
+                        listener.fileNotFound();
+                    }
+                    continue;
+                } else {
+
+                    // File was not rotated
+
+                    // See if the file needs to be read again
+                    if (length > position) {
+
+                        // The file has more content than it did last time
+                        readLines();
+
+                    } else if (FileUtils.isFileNewer(file, last)) {
+
+                        /* This can happen if the file is truncated or 
overwritten
+                         * with the exact same length of information. In cases 
like
+                         * this, the file position needs to be reset
+                         */
+                        position = 0;
+                        reader.seek(position);
+
+                        // Now we can read new lines
+                        readLines();
+                    }
+                }
+
+                Thread.sleep(delay);
+            }
+
+        } catch (Exception e) {
+
+            listener.handle(e);
+
+        }
+    }
+
+    /**
+     * Allows the tailer to complete it's current loop and return.
+     */
+    public synchronized void stop() {
+        this.run = false;
+    }
+
+    /**
+     * Read new lines.
+     * @throws java.io.IOException if an I/O error occurs.
+     */
+    private void readLines() throws IOException {
+        last = System.currentTimeMillis();
+        String line = reader.readLine();
+        while (line != null) {
+            listener.handle(line);
+            line = reader.readLine();
+        }
+        position = reader.getFilePointer();
+    }
+
+}

Propchange: 
commons/proper/io/trunk/src/java/org/apache/commons/io/input/Tailer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
commons/proper/io/trunk/src/java/org/apache/commons/io/input/Tailer.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Added: 
commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListener.java
URL: 
http://svn.apache.org/viewvc/commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListener.java?rev=1002844&view=auto
==============================================================================
--- 
commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListener.java
 (added)
+++ 
commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListener.java
 Wed Sep 29 20:56:23 2010
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.input;
+
+/**
+ * Listener for events from a {...@link Tailer}.
+ *
+ * @version $Id$
+ * @since Commons IO 2.0
+ */
+public interface TailerListener {
+
+    /**
+     * The tailer will call this method during construction,
+     * giving the listener a method of stopping the tailer.
+     * @param tailer the tailer.
+     */
+    public void init(Tailer tailer);
+
+    /**
+     * This method is called if the tailed file is not found.
+     */
+    public void fileNotFound();
+
+    /**
+     * Called if a file rotation is detected.
+     *
+     * This method is called before the file is reopened, and fileNotFound may
+     * be called if the new file has not yet been created.
+     */
+    public void fileRotated();
+
+    /**
+     * Handles a line from a Tailer.
+     * @param line the line.
+     */
+    public void handle(String line);
+
+    /**
+     * Handles an Exception .
+     * @param ex the exception.
+     */
+    public void handle(Exception ex);
+
+}

Propchange: 
commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListener.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListener.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Added: 
commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListenerAdapter.java
URL: 
http://svn.apache.org/viewvc/commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListenerAdapter.java?rev=1002844&view=auto
==============================================================================
--- 
commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListenerAdapter.java
 (added)
+++ 
commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListenerAdapter.java
 Wed Sep 29 20:56:23 2010
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.input;
+
+/**
+ * {...@link TailerListener} Adapter.
+ *
+ * @version $Id$
+ * @since Commons IO 2.0
+ */
+public class TailerListenerAdapter implements TailerListener {
+
+    /**
+     * The tailer will call this method during construction,
+     * giving the listener a method of stopping the tailer.
+     * @param tailer the tailer.
+     */
+    public void init(Tailer tailer) {
+    }
+
+    /**
+     * This method is called if the tailed file is not found.
+     */
+    public void fileNotFound() {
+    }
+
+    /**
+     * Called if a file rotation is detected.
+     *
+     * This method is called before the file is reopened, and fileNotFound may
+     * be called if the new file has not yet been created.
+     */
+    public void fileRotated() {
+    }
+
+    /**
+     * Handles a line from a Tailer.
+     * @param line the line.
+     */
+    public void handle(String line) {
+    }
+
+    /**
+     * Handles an Exception .
+     * @param ex the exception.
+     */
+    public void handle(Exception ex) {
+    }
+
+}

Propchange: 
commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListenerAdapter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
commons/proper/io/trunk/src/java/org/apache/commons/io/input/TailerListenerAdapter.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Added: 
commons/proper/io/trunk/src/test/org/apache/commons/io/input/TailerTest.java
URL: 
http://svn.apache.org/viewvc/commons/proper/io/trunk/src/test/org/apache/commons/io/input/TailerTest.java?rev=1002844&view=auto
==============================================================================
--- 
commons/proper/io/trunk/src/test/org/apache/commons/io/input/TailerTest.java 
(added)
+++ 
commons/proper/io/trunk/src/test/org/apache/commons/io/input/TailerTest.java 
Wed Sep 29 20:56:23 2010
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.input;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.testtools.FileBasedTestCase;
+
+/**
+ * Tests for {...@link Tailer}.
+ *
+ * @version $Id$
+ */
+public class TailerTest extends FileBasedTestCase {
+
+    public TailerTest(String name) {
+        super(name);
+    }
+
+    public void testTailer() throws Exception {
+
+        // Create & start the Tailer
+        long delay = 50;
+        File file = new File(getTestDirectory(), "tailer1-test.txt");
+        createFile(file, 0);
+        TestTailerListener listener = new TestTailerListener();
+        Tailer tailer = start(file, listener, delay, false);
+
+        // Write some lines to the file
+        write(file, "Line one", "Line two");
+        Thread.sleep(delay * 2);
+        List<String> lines = listener.getLines();
+        assertEquals("1 line count", 2, lines.size());
+        assertEquals("1 line 1", "Line one", lines.get(0));
+        assertEquals("1 line 2", "Line two", lines.get(1));
+        listener.clear();
+
+        // Write another line to the file
+        write(file, "Line three");
+        Thread.sleep(delay * 2);
+        lines = listener.getLines();
+        assertEquals("2 line count", 1, lines.size());
+        assertEquals("2 line 3", "Line three", lines.get(0));
+        listener.clear();
+
+        // Check file does actually have all the lines
+        lines = FileUtils.readLines(file);
+        assertEquals("3 line count", 3, lines.size());
+        assertEquals("3 line 1", "Line one", lines.get(0));
+        assertEquals("3 line 2", "Line two", lines.get(1));
+        assertEquals("3 line 3", "Line three", lines.get(2));
+
+        // Delete & re-create
+        file.delete();
+        createFile(file, 0);
+        Thread.sleep(delay * 2);
+
+        // Write another line
+        write(file, "Line four");
+        Thread.sleep(delay * 2);
+        lines = listener.getLines();
+        assertEquals("4 line count", 1, lines.size());
+        assertEquals("4 line 3", "Line four", lines.get(0));
+        listener.clear();
+
+        // Stop
+        tailer.stop();
+        Thread.sleep(delay * 2);
+        write(file, "Line five");
+        assertEquals("4 line count", 0, listener.getLines().size());
+    }
+
+    /** Start a tailer */
+    private Tailer start(File file, TailerListener listener, long delay, 
boolean end) {
+        Tailer tailer = new Tailer(file, listener, delay, end);
+        Thread thread = new Thread(tailer);
+        thread.start();
+        return tailer;
+    }
+
+    /** Append some lines to a file */
+    private void write(File file, String... lines) throws Exception {
+        FileWriter writer = null;
+        try {
+            writer = new FileWriter(file, true);
+            for (String line : lines) {
+                writer.write(line + "\n");
+            }
+        } finally {
+            IOUtils.closeQuietly(writer);
+        }
+    }
+
+    /**
+     * Test {...@link TailerListener} implementation.
+     */
+    private static class TestTailerListener extends TailerListenerAdapter {
+
+        private final List<String> lines = new ArrayList<String>();
+
+        public void handle(String line) {
+            lines.add(line);
+        }
+        public List<String> getLines() {
+            return lines;
+        }
+        public void clear() {
+            lines.clear();
+        }
+    }
+}

Propchange: 
commons/proper/io/trunk/src/test/org/apache/commons/io/input/TailerTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
commons/proper/io/trunk/src/test/org/apache/commons/io/input/TailerTest.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL


Reply via email to