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