You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ni...@apache.org on 2008/04/06 11:56:33 UTC

svn commit: r645221 - in /commons/proper/io/trunk/src: java/org/apache/commons/io/monitor/ test/org/apache/commons/io/monitor/

Author: niallp
Date: Sun Apr  6 02:56:32 2008
New Revision: 645221

URL: http://svn.apache.org/viewvc?rev=645221&view=rev
Log:
IO-132 - add File Listener/Monitor

Added:
    commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/
    commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemEntry.java   (with props)
    commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemListener.java   (with props)
    commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemListenerAdaptor.java   (with props)
    commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemMonitor.java   (with props)
    commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemObserver.java   (with props)
    commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/package.html   (with props)
    commons/proper/io/trunk/src/test/org/apache/commons/io/monitor/
    commons/proper/io/trunk/src/test/org/apache/commons/io/monitor/CollectionFilesystemListener.java   (with props)
    commons/proper/io/trunk/src/test/org/apache/commons/io/monitor/FilesystemObserverTestCase.java   (with props)

Added: commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemEntry.java
URL: http://svn.apache.org/viewvc/commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemEntry.java?rev=645221&view=auto
==============================================================================
--- commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemEntry.java (added)
+++ commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemEntry.java Sun Apr  6 02:56:32 2008
@@ -0,0 +1,246 @@
+/*
+ * 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.monitor;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * {@link FilesystemEntry} represents the state of a file or directory, capturing
+ * the following {@link File} attributes at a point in time:
+ * <ul>
+ *   <li>File Name (see {@link File#getName()})</li>
+ *   <li>Exists - whether the file exists or not (see {@link File#exists()})</li>
+ *   <li>Directory - whether the file is a directory or not (see {@link File#isDirectory()})</li>
+ *   <li>Last Modified Date/Time (see {@link File#lastModified()})</li>
+ *   <li>Children - contents of a directory (see {@link File#listFiles(java.io.FileFilter)})</li>
+ * </ul>
+ * <p>
+ * <h3>Custom Implementations</h3>
+ * If the state of additional {@link File} attributes is required then create a custom
+ * {@link FilesystemEntry} with properties for those attributes. Override the
+ * {@link #newChildInstance(File)} to return a new instance of the appropriate type.
+ * You may also want to override the {@link #refresh()} and/or {@link #hasChanged()}
+ * methods.
+ * 
+ */
+public class FilesystemEntry implements Serializable {
+
+    private FilesystemEntry parent;
+    private FilesystemEntry[] children;
+    private File file;
+    private String name;
+    private boolean exists;
+    private boolean directory;
+    private long lastModified;
+
+    /**
+     * Construct a new monitor for a specified {@link File}.
+     *
+     * @param file The file being monitored
+     */
+    public FilesystemEntry(File file) {
+        this((FilesystemEntry)null, file);
+    }
+
+    /**
+     * Construct a new monitor for a specified {@link File}.
+     *
+     * @param parent The parent
+     * @param file The file being monitored
+     */
+    public FilesystemEntry(FilesystemEntry parent, File file) {
+        if (file == null) {
+            throw new IllegalArgumentException("File is missing");
+        }
+        this.file = file;
+        this.parent = parent;
+        this.name = file.getName();
+    }
+
+    /**
+     * Refresh the attributes from the underlying {@link File}.
+     * <p>
+     * This implementation refreshes the <code>name</code>, <code>exists</code>
+     * <code>directory</code> and <code>lastModified</code> properties.
+     */
+    public void refresh() {
+        name = file.getName();
+        exists = file.exists();
+        if (exists) {
+            directory = file.isDirectory();
+            lastModified = file.lastModified();
+        }
+    }
+
+    /**
+     * Create a new child instance.
+     * <p>
+     * Custom implementations should override this method to return
+     * a new instance of the appropriate type.
+     *
+     * @param file The child file
+     * @return a new child instance
+     */
+    public FilesystemEntry newChildInstance(File file) {
+        return new FilesystemEntry(this, file);
+    }
+
+    /**
+     * Indicate whether the file has changed or not.
+     * <p>
+     * This implementation compares the <code>lastModified<code>
+     * value of the {@link File} with the stored value.
+     *
+     * @return whether the file has changed or not
+     */
+    public boolean hasChanged() {
+        return (lastModified != file.lastModified());
+    }
+
+    /**
+     * Return the parent entry.
+     *
+     * @return the parent entry
+     */
+    public FilesystemEntry getParent() {
+        return parent;
+    }
+
+    /**
+     * Return the level
+     *
+     * @return the level
+     */
+    public int getLevel() {
+        return parent == null ? 0 : parent.getLevel() + 1;
+    }
+
+    /**
+     * Return the directory's files.
+     *
+     * @return This directory's files or an empty
+     * array if the file is not a directory or the
+     * directory is empty
+     */
+    public FilesystemEntry[] getChildren() {
+        return children != null ? children : FilesystemObserver.EMPTY_ENTRIES;
+    }
+
+    /**
+     * Set the directory's files.
+     *
+     * @param children This directory's files, may be null
+     */
+    public void setChildren(FilesystemEntry[] children) {
+        this.children = children;
+    }
+
+    /**
+     * Return the file being monitored.
+     *
+     * @return the file being monitored
+     */
+    public File getFile() {
+        return file;
+    }
+
+    /**
+     * Set the file being monitored.
+     *
+     * @param file the file being monitored
+     */
+    public void setFile(File file) {
+        this.file = file;
+    }
+
+    /**
+     * Return the file name.
+     *
+     * @return the file name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Set the file name.
+     *
+     * @param name the file name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Return the last modified time from the last time it
+     * was checked.
+     *
+     * @return the last modified time
+     */
+    public long getLastModified() {
+        return lastModified;
+    }
+
+    /**
+     * Return the last modified time from the last time it
+     * was checked.
+     *
+     * @param lastModified The last modified time
+     */
+    public void setLastModified(long lastModified) {
+        this.lastModified = lastModified;
+    }
+
+    /**
+     * Indicate whether the file existed the last time it
+     * was checked.
+     *
+     * @return whether the file existed
+     */
+    public boolean isExists() {
+        return exists;
+    }
+
+    /**
+     * Set whether the file existed the last time it
+     * was checked.
+     *
+     * @param exists whether the file exists or not
+     */
+    public void setExists(boolean exists) {
+        this.exists = exists;
+    }
+
+    /**
+     * Indicate whether the file is a directory or not.
+     *
+     * @return whether the file is a directory or not
+     */
+    public boolean isDirectory() {
+        return directory;
+    }
+
+    /**
+     * Set whether the file is a directory or not.
+     *
+     * @param directory whether the file is a directory or not
+     */
+    public void setDirectory(boolean directory) {
+        this.directory = directory;
+    }
+}

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

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

Added: commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemListener.java
URL: http://svn.apache.org/viewvc/commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemListener.java?rev=645221&view=auto
==============================================================================
--- commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemListener.java (added)
+++ commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemListener.java Sun Apr  6 02:56:32 2008
@@ -0,0 +1,86 @@
+/*
+ * 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.monitor;
+import java.io.File;
+
+/**
+ * A listener that receives events of file system modifications.
+ * <p>
+ * Register {@link FilesystemListener}s with a {@link FilesystemObserver}.
+ * 
+ * @see FilesystemObserver
+ * @version $Id$
+ * @since Commons IO 2.0
+ */
+public interface FilesystemListener {
+
+    /**
+     * File system observer started checking event.
+     *
+     * @param observer The file system observer
+     */
+    void onStart(final FilesystemObserver observer);
+
+    /**
+     * Directory created Event.
+     * 
+     * @param directory The directory created
+     */
+    void onDirectoryCreate(final File directory);
+
+    /**
+     * Directory changed Event.
+     * 
+     * @param directory The directory changed
+     */
+    void onDirectoryChange(final File directory);
+
+    /**
+     * Directory deleted Event.
+     * 
+     * @param directory The directory deleted
+     */
+    void onDirectoryDelete(final File directory);
+
+    /**
+     * File created Event.
+     * 
+     * @param file The file created
+     */
+    void onFileCreate(final File file);
+
+    /**
+     * File changed Event.
+     * 
+     * @param file The file changed
+     */
+    void onFileChange(final File file);
+
+    /**
+     * File deleted Event.
+     * 
+     * @param file The file deleted
+     */
+    void onFileDelete(final File file);
+
+    /**
+     * File system observer finished checking event.
+     *
+     * @param observer The file system observer
+     */
+    void onStop(final FilesystemObserver observer);
+}

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

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

Added: commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemListenerAdaptor.java
URL: http://svn.apache.org/viewvc/commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemListenerAdaptor.java?rev=645221&view=auto
==============================================================================
--- commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemListenerAdaptor.java (added)
+++ commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemListenerAdaptor.java Sun Apr  6 02:56:32 2008
@@ -0,0 +1,94 @@
+/*
+ * 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.monitor;
+
+import java.io.File;
+
+/**
+ * Convenience {@link FilesystemListener} implementation that does nothing.
+ * 
+ * @see FilesystemObserver
+ * @version $Id$
+ * @since Commons IO 2.0
+ */
+public class FilesystemListenerAdaptor implements FilesystemListener {
+
+    /**
+     * File system observer started checking event.
+     *
+     * @param observer The file system observer
+     */
+    public void onStart(final FilesystemObserver observer) {
+    }
+
+    /**
+     * Directory created Event.
+     * 
+     * @param directory The directory created
+     */
+    public void onDirectoryCreate(final File directory) {
+    }
+
+    /**
+     * Directory changed Event.
+     * 
+     * @param directory The directory changed
+     */
+    public void onDirectoryChange(final File directory) {
+    }
+
+    /**
+     * Directory deleted Event.
+     * 
+     * @param directory The directory deleted
+     */
+    public void onDirectoryDelete(final File directory) {
+    }
+
+    /**
+     * File created Event.
+     * 
+     * @param file The file created
+     */
+    public void onFileCreate(final File file) {
+    }
+
+    /**
+     * File changed Event.
+     * 
+     * @param file The file changed
+     */
+    public void onFileChange(final File file) {
+    }
+
+    /**
+     * File deleted Event.
+     * 
+     * @param file The file deleted
+     */
+    public void onFileDelete(final File file) {
+    }
+
+    /**
+     * File system observer finished checking event.
+     *
+     * @param observer The file system observer
+     */
+    public void onStop(final FilesystemObserver observer) {
+    }
+
+}

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

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

Added: commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemMonitor.java
URL: http://svn.apache.org/viewvc/commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemMonitor.java?rev=645221&view=auto
==============================================================================
--- commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemMonitor.java (added)
+++ commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemMonitor.java Sun Apr  6 02:56:32 2008
@@ -0,0 +1,154 @@
+/*
+ * 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.monitor;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A runnable that spawns a monitoring thread triggering any
+ * registered {@link FilesystemObserver} at a specified interval.
+ * 
+ * @see FilesystemObserver
+ * @see FilesystemTimerTask
+ * @version $Id$
+ * @since Commons IO 2.0
+ */
+public final class FilesystemMonitor implements Runnable {
+
+    private final long interval;
+    private final List<FilesystemObserver> observers = new CopyOnWriteArrayList<FilesystemObserver>();
+    private Thread thread = null;
+    private volatile boolean running = false;
+
+    /**
+     * Construct a monitor with a default interval of 10 seconds.
+     */
+    public FilesystemMonitor() {
+        this(10000);
+    }
+
+    /**
+     * Construct a monitor with the specified interval.
+     *
+     * @param interval The amount of time in miliseconds to wait between
+     * checks of the file system
+     */
+    public FilesystemMonitor(long interval) {
+        this.interval = interval;
+    }
+
+    /**
+     * Construct a monitor with the specified interval and set of observers.
+     *
+     * @param interval The amount of time in miliseconds to wait between
+     * checks of the file system
+     * @param observers The set of observers to add to the monitor.
+     */
+    public FilesystemMonitor(long interval, FilesystemObserver... observers) {
+        this(interval);
+        if (observers != null) {
+            for (int i = 0; i < observers.length; i++) {
+                addObserver(observers[i]);
+            }
+        }
+    }
+
+    /**
+     * Add a file system observer to this monitor.
+     *
+     * @param observer The file system observer to add
+     */
+    public void addObserver(final FilesystemObserver observer) {
+        if (observer != null) {
+            observers.add(observer);
+        }
+    }
+
+    /**
+     * Remove a file system observer from this monitor.
+     *
+     * @param observer The file system observer to remove
+     */
+    public void removeObserver(final FilesystemObserver observer) {
+        if (observer != null) {
+            while (observers.remove(observer)) {
+            }
+        }
+    }
+
+    /**
+     * Returns the set of {@link FilesystemObserver} registered with
+     * this monitor. 
+     *
+     * @return The set of {@link FilesystemObserver}
+     */
+    public Iterable<FilesystemObserver> getObservers() {
+        return observers;
+    }
+
+    /**
+     * Start monitoring.
+     *
+     * @throws Exception if an error occurs initializing the observer
+     */
+    public void start() throws Exception {
+        for (FilesystemObserver observer : observers) {
+            observer.initialize();
+        }
+        running = true;
+        thread = new Thread(this);
+        thread.start();
+    }
+
+    /**
+     * Stop monitoring.
+     *
+     * @throws Exception if an error occurs initializing the observer
+     */
+    public void stop() throws Exception {
+        running = false;
+        try {
+            thread.join(interval);
+        } catch (InterruptedException e) {
+        }
+        for (FilesystemObserver observer : observers) {
+            observer.destroy();
+        }
+    }
+
+    /**
+     * Run.
+     */
+    public void run() {
+        while (true) {
+            if (!running) {
+                break;
+            }
+            for (FilesystemObserver observer : observers) {
+                observer.checkAndNotify();
+            }
+            if (!running) {
+                break;
+            }
+            try {
+                Thread.sleep(interval);
+            } catch (final InterruptedException e) {
+            }
+        }
+    }
+}

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

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

Added: commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemObserver.java
URL: http://svn.apache.org/viewvc/commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemObserver.java?rev=645221&view=auto
==============================================================================
--- commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemObserver.java (added)
+++ commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/FilesystemObserver.java Sun Apr  6 02:56:32 2008
@@ -0,0 +1,492 @@
+/*
+ * 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.monitor;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.commons.io.comparator.NameFileComparator;
+
+/**
+ * FilesystemObserver represents the state of files below a root directory,
+ * checking the filesystem and notifying listeners of create, change or
+ * delete events.
+ * <p>
+ * To use this implementation:
+ * <ul>
+ *   <li>Create {@link FilesystemListener} implementation(s) that process
+ *      the file/directory create, change and delete events</li>
+ *   <li>Register the listener(s) with a {@link FilesystemObserver} for
+ *       the appropriate directory.</li>
+ *   <li>Either register the observer(s) with a {@link FilesystemMonitor} or
+ *       run manually.</li>
+ * </ul>
+ *
+ * <h2>Basic Usage</h2>
+ * Create a {@link FilesystemObserver} for the directory and register the listeners:
+ * <pre>
+ *      File directory = new File(new File("."), "src");
+ *      FilesystemObserver observer = new FilesystemObserver(directory);
+ *      observer.addListener(...);
+ *      observer.addListener(...);
+ * </pre>
+ * To manually observe a directory, initialize the observer and invoked the
+ * {@link #checkAndNotify()} method as required:
+ * <pre>
+ *      // intialize
+ *      observer.init();
+ *      ...
+ *      // invoke as required
+ *      observer.checkAndNotify();
+ *      ...
+ *      observer.checkAndNotify();
+ *      ...
+ *      // finished
+ *      observer.finish();
+ * </pre>
+ * Alternatively, register the oberver(s) with a {@link FilesystemMonitor},
+ * which creates a new thread, invoking the observer at the specified interval:
+ * <pre>
+ *      long interval = ...
+ *      FilesystemMonitor monitor = new FilesystemMonitor(interval);
+ *      monitor.addObserver(observer);
+ *      monitor.start();
+ *      ...
+ *      monitor.stop();
+ * </pre>
+ *
+ * <h2>File Filters</h2>
+ * This implementation can monitor portions of the file system
+ * by using {@link FileFilter}s to observe only the files and/or directories
+ * that are of interest. This makes it more efficient and reduces the
+ * noise from <i>unwanted</i> file system events.
+ * <p>
+ * <a href="http://commons.apache.org/io/">Commons IO</a> has a good range of
+ * useful, ready made 
+ * <a href="apidocs/org/apache/commons/io/filefilter/package-summary.html">File Filter</a>
+ * implementations for this purpose.
+ * <p>
+ * For example, to only observe 1) visible directories and 2) files with a ".java" suffix
+ * in a root directory called "src" you could set up a {@link FilesystemObserver} in the following
+ * way:
+ * <pre>
+ *      // Create a FileFilter
+ *      IOFileFilter directories = FileFilterUtils.directoryFileFilter();
+ *      IOFileFilter visible     = HiddenFileFilter.VISIBLE;
+ *      IOFileFilter dirFilter   = FileFilterUtils.andFileFilter(directories, visible);
+ *      IOFileFilter files       = FileFilterUtils.fileFileFilter();
+ *      IOFileFilter javaSuffix  = FileFilterUtils.suffixFileFilter(".java");
+ *      IOFileFilter fileFilter  = FileFilterUtils.andFileFilter(files, javaSuffix);
+ *      IOFileFilter filter = FileFilterUtils.orFileFilter(dirFilter, fileFilter);
+ *
+ *      // Create the File system observer and register File Listeners
+ *      FilesystemObserver observer = new FilesystemObserver(new File("src"), filter);
+ *      observer.addListener(...);
+ *      observer.addListener(...);
+ *
+ *      //
+ * </pre>
+ *
+ * <h2>File Comparator</h2>
+ * This implementation works by comparing the file names of the current contents of
+ * a directory with the previous contents using the <i>case-sensitive</i> 
+ * {@link NameFileComparator#NAME_COMPARATOR} to determine which files have been created,
+ * deleted or still exist. However a custom {@link Comparator} can be specified and
+ * one example usage would be to compare file names in a <i>case-insensitive</i>
+ * manner (@link {@link NameFileComparator#NAME_INSENSITIVE_COMPARATOR} could be used
+ * to do that).
+ *
+ * <h2>FilesystemEntry</h2>
+ * {@link FilesystemEntry} represents the state of a file or directory, capturing
+ * {@link File} attributes at a point in time. Custom implementations of
+ * {@link FilesystemEntry} can be used to capture additional properties that the
+ * basic implementation does not support. The {@link FilesystemEntry#hasChanged()}
+ * method is used to determine if a file or directory has changed since the last
+ * check. {@link FilesystemEntry#refresh()} stores the current state of the
+ * {@link File}'s properties.
+ *
+ * @see FilesystemListener
+ * @see FilesystemMonitor
+ * @version $Id$
+ * @since Commons IO 2.0
+ */
+public class FilesystemObserver implements Serializable {
+
+    private static final File[] EMPTY_FILES = new File[0];
+    static final FilesystemEntry[] EMPTY_ENTRIES = new FilesystemEntry[0];
+
+    private final List<FilesystemListener> listeners = new CopyOnWriteArrayList<FilesystemListener>();
+    private final FilesystemEntry rootEntry;
+    private final FileFilter fileFilter;
+    private final Comparator<File> comparator;
+
+    /**
+     * Construct an observer for the specified directory.
+     *
+     * @param directoryName the name of the directory to observe
+     */
+    public FilesystemObserver(String directoryName) {
+        this(new File(directoryName));
+    }
+
+    /**
+     * Construct an observer for the specified directory and file filter.
+     *
+     * @param directoryName the name of the directory to observe
+     * @param fileFilter The file filter or null if none
+     */
+    public FilesystemObserver(String directoryName, FileFilter fileFilter) {
+        this(new File(directoryName), fileFilter);
+    }
+
+    /**
+     * Construct an observer for the specified directory, file filter and
+     * file comparator.
+     *
+     * @param directoryName the name of the directory to observe
+     * @param fileFilter The file filter or null if none
+     * @param comparator The comparator to use when comparing file names, may be null
+     */
+    public FilesystemObserver(String directoryName, FileFilter fileFilter, Comparator<File> comparator) {
+        this(new File(directoryName), fileFilter, comparator);
+    }
+
+    /**
+     * Construct an observer for the specified directory.
+     *
+     * @param directory the directory to observe
+     */
+    public FilesystemObserver(File directory) {
+        this(directory, (FileFilter)null);
+    }
+
+    /**
+     * Construct an observer for the specified directory and file filter.
+     *
+     * @param directory the directory to observe
+     * @param fileFilter The file filter or null if none
+     */
+    public FilesystemObserver(File directory, FileFilter fileFilter) {
+        this(directory, fileFilter, (Comparator<File>)null);
+    }
+
+    /**
+     * Construct an observer for the specified directory, file filter and
+     * file comparator.
+     *
+     * @param directory the directory to observe
+     * @param fileFilter The file filter or null if none
+     * @param comparator The comparator to use when comparing file names, may be null
+     */
+    public FilesystemObserver(File directory, FileFilter fileFilter, Comparator<File> comparator) {
+        this(new FilesystemEntry(directory), fileFilter, comparator);
+    }
+
+    /**
+     * Construct an observer for the specified directory, file filter and
+     * file comparator.
+     *
+     * @param rootEntry the root directory to observe
+     * @param fileFilter The file filter or null if none
+     * @param comparator The comparator to use when comparing file names, may be null
+     */
+    protected FilesystemObserver(FilesystemEntry rootEntry, FileFilter fileFilter, Comparator<File> comparator) {
+        if (rootEntry == null) {
+            throw new IllegalArgumentException("Root entry is missing");
+        }
+        if (rootEntry.getFile() == null) {
+            throw new IllegalArgumentException("Root directory is missing");
+        }
+        this.rootEntry = rootEntry;
+        this.fileFilter = fileFilter;
+        if (comparator == null) {
+            this.comparator = NameFileComparator.NAME_COMPARATOR;
+        } else {
+            this.comparator = comparator;
+        }
+    }
+
+    /**
+     * Return the entry for the root directory.
+     *
+     * @return the entry for the root directory
+     */
+    public FilesystemEntry getRootEntry() {
+        return rootEntry;
+    }
+
+    /**
+     * Return the directory being observed.
+     *
+     * @return the directory being observed
+     */
+    public File getDirectory() {
+        return rootEntry.getFile();
+    }
+
+    /**
+     * Return the file filter, if any.
+     *
+     * @return the file filteror <code>null</code> if none
+     */
+    public FileFilter getFileFilter() {
+        return fileFilter;
+    }
+
+    /**
+     * Return the comparator.
+     *
+     * @return the comparator
+     */
+    public Comparator<File> getComparator() {
+        return comparator;
+    }
+
+    /**
+     * Add a file system listener.
+     *
+     * @param listener The file system listener
+     */
+    public void addListener(final FilesystemListener listener) {
+        if (listener != null) {
+            listeners.add(listener);
+        }
+    }
+
+    /**
+     * Remove a file system listener.
+     *
+     * @param listener The file system listener
+     */
+    public void removeListener(final FilesystemListener listener) {
+        if (listener != null) {
+            while (listeners.remove(listener)) {
+            }
+        }
+    }
+
+    /**
+     * Returns the set of registered file system listeners.
+     *
+     * @return The file system listeners
+     */
+    public Iterable<FilesystemListener> getListeners() {
+        return listeners;
+    }
+
+    /**
+     * Initialize the observer.
+     *
+     * @throws Exception if an error occurs
+     */
+    public void initialize() throws Exception {
+        rootEntry.refresh();
+        File[] files = listFiles(rootEntry.getFile());
+        FilesystemEntry[] children = files.length > 0 ? new FilesystemEntry[files.length] : EMPTY_ENTRIES;
+        for (int i = 0; i < files.length; i++) {
+            children[i] = createFileEntry(rootEntry, files[i]);
+        }
+        rootEntry.setChildren(children);
+    }
+
+    /**
+     * Final processing.
+     *
+     * @throws Exception if an error occurs
+     */
+    public void destroy() throws Exception {
+    }
+
+    /**
+     * Check whether the file and its chlidren have been created, modified or deleted.
+     */
+    public void checkAndNotify() {
+
+        /* fire onStart() */
+        for (FilesystemListener listener : listeners) {
+            listener.onStart(this);
+        }
+
+        /* fire directory/file events */
+        File rootFile = rootEntry.getFile();
+        if (rootFile.exists()) {
+            checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootFile));
+        } else if (rootEntry.isExists()) {
+            checkAndNotify(rootEntry, rootEntry.getChildren(), EMPTY_FILES);
+        } else {
+            // Didn't exist and still doesn't
+        }
+
+        /* fire onStop() */
+        for (FilesystemListener listener : listeners) {
+            listener.onStop(this);
+        }
+    }
+
+    /**
+     * Compare two file lists for files which have been created, modified or deleted.
+     *
+     * @param parent The parent entry
+     * @param previous The original list of files
+     * @param files  The current list of files
+     */
+    private void checkAndNotify(FilesystemEntry parent, FilesystemEntry[] previous, File[] files) {
+        int c = 0;
+        FilesystemEntry[] current = files.length > 0 ? new FilesystemEntry[files.length] : EMPTY_ENTRIES;
+        for (int p = 0; p < previous.length; p++) {
+            while (c < files.length &&  comparator.compare(previous[p].getFile(), files[c]) > 0) {
+                current[c] = createFileEntry(parent, files[c]);
+                doCreate(current[c]);
+                c++;
+            }
+            if (c < files.length && comparator.compare(previous[p].getFile(), files[c]) == 0) {
+                doMatch(previous[p], files[c]);
+                checkAndNotify(previous[p], previous[p].getChildren(), listFiles(files[c]));
+                current[c] = previous[p];
+                c++;
+            } else {
+                checkAndNotify(previous[p], previous[p].getChildren(), EMPTY_FILES);
+                doDelete(previous[p]);
+            }
+        }
+        for (; c < files.length; c++) {
+            current[c] = createFileEntry(parent, files[c]);
+            doCreate(current[c]);
+        }
+        parent.setChildren(current);
+    }
+
+    /**
+     * Create a new file entry for the specified file.
+     *
+     * @param parent The parent file entry
+     * @param file The file to create an entry for
+     * @return A new file entry
+     */
+    private FilesystemEntry createFileEntry(FilesystemEntry parent, File file) {
+        FilesystemEntry entry = parent.newChildInstance(file);
+        entry.refresh();
+        File[] files = listFiles(file);
+        FilesystemEntry[] children = files.length > 0 ? new FilesystemEntry[files.length] : EMPTY_ENTRIES;
+        for (int i = 0; i < files.length; i++) {
+            children[i] = createFileEntry(entry, files[i]);
+        }
+        entry.setChildren(children);
+        return entry;
+    }
+
+    /**
+     * Fire directory/file created events to the registered listeners.
+     *
+     * @param entry The file entry
+     */
+    private void doCreate(FilesystemEntry entry) {
+        for (FilesystemListener listener : listeners) {
+            if (entry.isDirectory()) {
+                listener.onDirectoryCreate(entry.getFile());
+            } else {
+                listener.onFileCreate(entry.getFile());
+            }
+        }
+        FilesystemEntry[] children = entry.getChildren();
+        for (int i = 0; i < children.length; i++) {
+            doCreate(children[i]);
+        }
+    }
+
+    /**
+     * Fire directory/file change events to the registered listeners.
+     *
+     * @param entry The previous file system entry
+     * @param file The current file
+     */
+    private void doMatch(FilesystemEntry entry, File file) {
+        if (entry.hasChanged()) {
+            for (FilesystemListener listener : listeners) {
+                if (entry.isDirectory()) {
+                    listener.onDirectoryChange(entry.getFile());
+                } else {
+                    listener.onFileChange(entry.getFile());
+                }
+            }
+            entry.refresh();
+        }
+        entry.setFile(file);
+    }
+
+    /**
+     * Fire directory/file delete events to the registered listeners.
+     *
+     * @param entry The file entry
+     */
+    private void doDelete(FilesystemEntry entry) {
+        for (FilesystemListener listener : listeners) {
+            if (entry.isDirectory()) {
+                listener.onDirectoryDelete(entry.getFile());
+            } else {
+                listener.onFileDelete(entry.getFile());
+            }
+        }
+    }
+
+    /**
+     * List the contents of a directory
+     *
+     * @param file The file to list the contents of
+     * @return the directory contents or a zero length array if
+     * the empty or the file is not a directory
+     */
+    private File[] listFiles(File file) {
+        File[] children = null;
+        if (file.isDirectory()) {
+            children = (fileFilter == null) ? file.listFiles() : file.listFiles(fileFilter);
+        }
+        if (children == null) {
+            children = EMPTY_FILES;
+        }
+        if (comparator != null && children.length > 1) {
+            Arrays.sort(children, comparator);
+        }
+        return children;
+    }
+
+    /**
+     * Provide a String representation of this observer.
+     *
+     * @return a String representation of this observer
+     */
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(getClass().getSimpleName());
+        builder.append("[file='");
+        builder.append(getDirectory().getPath());
+        if (fileFilter != null) {
+            builder.append(", ");
+            builder.append(fileFilter.toString());
+        }
+        builder.append(", listeners=");
+        builder.append(listeners.size());
+        builder.append("]");
+        return builder.toString();
+    }
+
+}

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

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

Added: commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/package.html
URL: http://svn.apache.org/viewvc/commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/package.html?rev=645221&view=auto
==============================================================================
--- commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/package.html (added)
+++ commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/package.html Sun Apr  6 02:56:32 2008
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+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.
+-->
+<html>
+<body>
+<p>
+This package provides a component for monitoring file system events
+(i.e. directory and file create, update and delete events).
+</p>
+</body>
+</html>

Propchange: commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/package.html
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/io/trunk/src/java/org/apache/commons/io/monitor/package.html
------------------------------------------------------------------------------
    svn:mime-type = text/html

Added: commons/proper/io/trunk/src/test/org/apache/commons/io/monitor/CollectionFilesystemListener.java
URL: http://svn.apache.org/viewvc/commons/proper/io/trunk/src/test/org/apache/commons/io/monitor/CollectionFilesystemListener.java?rev=645221&view=auto
==============================================================================
--- commons/proper/io/trunk/src/test/org/apache/commons/io/monitor/CollectionFilesystemListener.java (added)
+++ commons/proper/io/trunk/src/test/org/apache/commons/io/monitor/CollectionFilesystemListener.java Sun Apr  6 02:56:32 2008
@@ -0,0 +1,167 @@
+/*
+ * 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.monitor;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * {@link FilesystemListener} implementation that adds created, changed and deleted
+ * files/directories to a set of {@link Collection}s.
+ */
+public class CollectionFilesystemListener implements FilesystemListener, Serializable {
+
+    private final Collection<File> createdFiles = new ArrayList<File>();
+    private final Collection<File> changedFiles = new ArrayList<File>();
+    private final Collection<File> deletedFiles = new ArrayList<File>();
+    private final Collection<File> createdDirectories = new ArrayList<File>();
+    private final Collection<File> changedDirectories = new ArrayList<File>();
+    private final Collection<File> deletedDirectories = new ArrayList<File>();
+
+    /**
+     * File system observer started checking event.
+     *
+     * @param observer The file system observer
+     */
+    public void onStart(final FilesystemObserver observer) {
+        createdFiles.clear();
+        changedFiles.clear();
+        deletedFiles.clear();
+        createdDirectories.clear();
+        changedDirectories.clear();
+        deletedDirectories.clear();
+    }
+
+    /**
+     * Return the set of changed directories.
+     *
+     * @return Directories which have changed
+     */
+    public Collection getChangedDirectories() {
+        return changedDirectories;
+    }
+
+    /**
+     * Return the set of changed files.
+     *
+     * @return Files which have changed
+     */
+    public Collection getChangedFiles() {
+        return changedFiles;
+    }
+
+    /**
+     * Return the set of created directories.
+     *
+     * @return Directories which have been created
+     */
+    public Collection getCreatedDirectories() {
+        return createdDirectories;
+    }
+
+    /**
+     * Return the set of created files.
+     *
+     * @return Files which have been created
+     */
+    public Collection getCreatedFiles() {
+        return createdFiles;
+    }
+
+    /**
+     * Return the set of deleted directories.
+     *
+     * @return Directories which been deleted
+     */
+    public Collection getDeletedDirectories() {
+        return deletedDirectories;
+    }
+
+    /**
+     * Return the set of deleted files.
+     *
+     * @return Files which been deleted
+     */
+    public Collection getDeletedFiles() {
+        return deletedFiles;
+    }
+
+    /**
+     * Directory created Event.
+     * 
+     * @param directory The directory created
+     */
+    public void onDirectoryCreate(final File directory) {
+        createdDirectories.add(directory);
+    }
+
+    /**
+     * Directory changed Event.
+     * 
+     * @param directory The directory changed
+     */
+    public void onDirectoryChange(final File directory) {
+        changedDirectories.add(directory);
+    }
+
+    /**
+     * Directory deleted Event.
+     * 
+     * @param directory The directory deleted
+     */
+    public void onDirectoryDelete(final File directory) {
+        deletedDirectories.add(directory);
+    }
+
+    /**
+     * File created Event.
+     * 
+     * @param file The file created
+     */
+    public void onFileCreate(final File file) {
+        createdFiles.add(file);
+    }
+
+    /**
+     * File changed Event.
+     * 
+     * @param file The file changed
+     */
+    public void onFileChange(final File file) {
+        changedFiles.add(file);
+    }
+
+    /**
+     * File deleted Event.
+     * 
+     * @param file The file deleted
+     */
+    public void onFileDelete(final File file) {
+        deletedFiles.add(file);
+    }
+
+    /**
+     * File system observer finished checking event.
+     *
+     * @param observer The file system observer
+     */
+    public void onStop(final FilesystemObserver observer) {
+    }
+
+}

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

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

Added: commons/proper/io/trunk/src/test/org/apache/commons/io/monitor/FilesystemObserverTestCase.java
URL: http://svn.apache.org/viewvc/commons/proper/io/trunk/src/test/org/apache/commons/io/monitor/FilesystemObserverTestCase.java?rev=645221&view=auto
==============================================================================
--- commons/proper/io/trunk/src/test/org/apache/commons/io/monitor/FilesystemObserverTestCase.java (added)
+++ commons/proper/io/trunk/src/test/org/apache/commons/io/monitor/FilesystemObserverTestCase.java Sun Apr  6 02:56:32 2008
@@ -0,0 +1,449 @@
+/*
+ * 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.monitor;
+
+import java.io.File;
+import java.io.FileFilter;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.FileFilterUtils;
+import org.apache.commons.io.filefilter.HiddenFileFilter;
+import org.apache.commons.io.filefilter.IOFileFilter;
+
+import junit.framework.TestCase;
+
+/**
+ * {@link FilesystemObserver} Test Case.
+ */
+public class FilesystemObserverTestCase extends TestCase {
+
+    /** Filesystem observer */
+    protected FilesystemObserver observer;
+
+    /** Listener which collects file changes */
+    protected CollectionFilesystemListener listener = new CollectionFilesystemListener();
+
+    /** Directory for test files */
+    protected File testDir;
+
+    /**
+     * Construct a new test case.
+     *
+     * @param name The name of the test
+     */
+    public FilesystemObserverTestCase(String name) {
+        super(name);
+    }
+
+    protected void setUp() throws Exception {
+        testDir = new File(new File("."), "test-monitor");
+        if (testDir.exists()) {
+            FileUtils.cleanDirectory(testDir);
+        } else {
+            testDir.mkdir();
+        }
+
+        IOFileFilter files = FileFilterUtils.fileFileFilter();
+        IOFileFilter javaSuffix = FileFilterUtils.suffixFileFilter(".java");
+        IOFileFilter fileFilter = FileFilterUtils.andFileFilter(files, javaSuffix);
+        
+        IOFileFilter directories = FileFilterUtils.directoryFileFilter();
+        IOFileFilter visible = HiddenFileFilter.VISIBLE;
+        IOFileFilter dirFilter = FileFilterUtils.andFileFilter(directories, visible);
+
+        IOFileFilter filter = FileFilterUtils.orFileFilter(dirFilter, fileFilter);
+        
+        createObserver(testDir, filter);
+    }
+
+    /**
+     * Create a {@link FilesystemObserver}.
+     * 
+     * @param file The directory to observe
+     * @param fileFilter The file filter to apply
+     */
+    protected void createObserver(File file, FileFilter fileFilter) {
+        observer = new FilesystemObserver(file, fileFilter);
+        // FilesystemListener debuglistener = new JDKLoggingFilesystemListener(getClass().getSimpleName(), Level.FINEST); 
+        // observer.addListener(debuglistener);
+        observer.addListener(listener);
+        try {
+            observer.initialize();
+        } catch (Exception e) {
+            fail("Observer init() threw " + e);
+        }
+    }
+
+    protected void tearDown() throws Exception {
+        FileUtils.deleteDirectory(testDir);
+    }
+
+    /**
+     * Test checkAndNotify() method
+     */
+    public void testDirectory() {
+        try {
+            checkAndNotify();
+            checkCollectionsEmpty("A");
+            File testDirA = new File(testDir, "test-dir-A");
+            File testDirB = new File(testDir, "test-dir-B");
+            File testDirC = new File(testDir, "test-dir-C");
+            testDirA.mkdir();
+            testDirB.mkdir();
+            testDirC.mkdir();
+            File testDirAFile1 = touch(new File(testDirA, "A-file1.java"));
+            File testDirAFile2 = touch(new File(testDirA, "A-file2.txt")); // filter should ignore this
+            File testDirAFile3 = touch(new File(testDirA, "A-file3.java"));
+            File testDirAFile4 = touch(new File(testDirA, "A-file4.java"));
+            File testDirBFile1 = touch(new File(testDirB, "B-file1.java"));
+ 
+            checkAndNotify();
+            checkCollectionSizes("B", 3, 0, 0, 4, 0, 0);
+            assertTrue("B testDirA",   listener.getCreatedDirectories().contains(testDirA));
+            assertTrue("B testDirB",   listener.getCreatedDirectories().contains(testDirB));
+            assertTrue("B testDirC",   listener.getCreatedDirectories().contains(testDirC));
+            assertTrue("B testDirAFile1", listener.getCreatedFiles().contains(testDirAFile1));
+            assertFalse("B testDirAFile2", listener.getCreatedFiles().contains(testDirAFile2));
+            assertTrue("B testDirAFile3", listener.getCreatedFiles().contains(testDirAFile3));
+            assertTrue("B testDirAFile4", listener.getCreatedFiles().contains(testDirAFile4));
+            assertTrue("B testDirBFile1", listener.getCreatedFiles().contains(testDirBFile1));
+
+            checkAndNotify();
+            checkCollectionsEmpty("C");
+
+            touch(testDirAFile4);
+            FileUtils.deleteDirectory(testDirB);
+            checkAndNotify();
+            checkCollectionSizes("D", 0, 0, 1, 0, 1, 1);
+            assertTrue("D testDirB",   listener.getDeletedDirectories().contains(testDirB));
+            assertTrue("D testDirAFile4", listener.getChangedFiles().contains(testDirAFile4));
+            assertTrue("D testDirBFile1", listener.getDeletedFiles().contains(testDirBFile1));
+
+            FileUtils.deleteDirectory(testDir);
+            checkAndNotify();
+            checkCollectionSizes("E", 0, 0, 2, 0, 0, 3);
+            assertTrue("E testDirA",   listener.getDeletedDirectories().contains(testDirA));
+            assertTrue("E testDirAFile1", listener.getDeletedFiles().contains(testDirAFile1));
+            assertFalse("E testDirAFile2", listener.getDeletedFiles().contains(testDirAFile2));
+            assertTrue("E testDirAFile3", listener.getDeletedFiles().contains(testDirAFile3));
+            assertTrue("E testDirAFile4", listener.getDeletedFiles().contains(testDirAFile4));
+            
+            testDir.mkdir();
+            checkAndNotify();
+            checkCollectionsEmpty("F");
+
+            checkAndNotify();
+            checkCollectionsEmpty("G");
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail("Threw " + e);
+        }
+    }
+
+    /**
+     * Test checkAndNotify() creating
+     */
+    public void testFileCreate() {
+        try {
+            checkAndNotify();
+            checkCollectionsEmpty("A");
+            File testDirA = new File(testDir, "test-dir-A");
+            testDirA.mkdir();
+            touch(testDir);
+            touch(testDirA);
+            File testDirAFile1 =       new File(testDirA, "A-file1.java");
+            File testDirAFile2 = touch(new File(testDirA, "A-file2.java"));
+            File testDirAFile3 =       new File(testDirA, "A-file3.java");
+            File testDirAFile4 = touch(new File(testDirA, "A-file4.java"));
+            File testDirAFile5 =       new File(testDirA, "A-file5.java");
+ 
+            checkAndNotify();
+            checkCollectionSizes("B", 1, 0, 0, 2, 0, 0);
+            assertFalse("B testDirAFile1", listener.getCreatedFiles().contains(testDirAFile1));
+            assertTrue("B testDirAFile2",  listener.getCreatedFiles().contains(testDirAFile2));
+            assertFalse("B testDirAFile3", listener.getCreatedFiles().contains(testDirAFile3));
+            assertTrue("B testDirAFile4",  listener.getCreatedFiles().contains(testDirAFile4));
+            assertFalse("B testDirAFile5", listener.getCreatedFiles().contains(testDirAFile5));
+
+            assertFalse("B testDirAFile1 exists", testDirAFile1.exists());
+            assertTrue("B testDirAFile2 exists",  testDirAFile2.exists());
+            assertFalse("B testDirAFile3 exists", testDirAFile3.exists());
+            assertTrue("B testDirAFile4 exists",  testDirAFile4.exists());
+            assertFalse("B testDirAFile5 exists", testDirAFile5.exists());
+
+            checkAndNotify();
+            checkCollectionsEmpty("C");
+
+            // Create file with name < first entry
+            touch(testDirAFile1);
+            touch(testDirA);
+            checkAndNotify();
+            checkCollectionSizes("D", 0, 1, 0, 1, 0, 0);
+            assertTrue("D testDirAFile1 exists", testDirAFile1.exists());
+            assertTrue("D testDirAFile1",  listener.getCreatedFiles().contains(testDirAFile1));
+
+            // Create file with name between 2 entries
+            touch(testDirAFile3);
+            touch(testDirA);
+            checkAndNotify();
+            checkCollectionSizes("E", 0, 1, 0, 1, 0, 0);
+            assertTrue("E testDirAFile3 exists", testDirAFile3.exists());
+            assertTrue("E testDirAFile3",  listener.getCreatedFiles().contains(testDirAFile3));
+
+            // Create file with name > last entry
+            touch(testDirAFile5);
+            touch(testDirA);
+            checkAndNotify();
+            checkCollectionSizes("F", 0, 1, 0, 1, 0, 0);
+            assertTrue("F testDirAFile5 exists", testDirAFile5.exists());
+            assertTrue("F testDirAFile5",  listener.getCreatedFiles().contains(testDirAFile5));
+        } catch (Exception e) {
+            fail("Threw " + e);
+        }
+    }
+
+    /**
+     * Test checkAndNotify() creating
+     */
+    public void testFileUpdate() {
+        try {
+            checkAndNotify();
+            checkCollectionsEmpty("A");
+            File testDirA = new File(testDir, "test-dir-A");
+            testDirA.mkdir();
+            touch(testDir);
+            touch(testDirA);
+            File testDirAFile1 = touch(new File(testDirA, "A-file1.java"));
+            File testDirAFile2 = touch(new File(testDirA, "A-file2.java"));
+            File testDirAFile3 = touch(new File(testDirA, "A-file3.java"));
+            File testDirAFile4 = touch(new File(testDirA, "A-file4.java"));
+            File testDirAFile5 = touch(new File(testDirA, "A-file5.java"));
+ 
+            checkAndNotify();
+            checkCollectionSizes("B", 1, 0, 0, 5, 0, 0);
+            assertTrue("B testDirAFile1", listener.getCreatedFiles().contains(testDirAFile1));
+            assertTrue("B testDirAFile2", listener.getCreatedFiles().contains(testDirAFile2));
+            assertTrue("B testDirAFile3", listener.getCreatedFiles().contains(testDirAFile3));
+            assertTrue("B testDirAFile4", listener.getCreatedFiles().contains(testDirAFile4));
+            assertTrue("B testDirAFile5", listener.getCreatedFiles().contains(testDirAFile5));
+
+            assertTrue("B testDirAFile1 exists", testDirAFile1.exists());
+            assertTrue("B testDirAFile2 exists", testDirAFile2.exists());
+            assertTrue("B testDirAFile3 exists", testDirAFile3.exists());
+            assertTrue("B testDirAFile4 exists", testDirAFile4.exists());
+            assertTrue("B testDirAFile5 exists", testDirAFile5.exists());
+
+            checkAndNotify();
+            checkCollectionsEmpty("C");
+
+            // Update first entry
+            touch(testDirAFile1);
+            touch(testDirA);
+            checkAndNotify();
+            checkCollectionSizes("D", 0, 1, 0, 0, 1, 0);
+            assertTrue("D testDirAFile1",  listener.getChangedFiles().contains(testDirAFile1));
+
+            // Update file with name between 2 entries
+            touch(testDirAFile3);
+            touch(testDirA);
+            checkAndNotify();
+            checkCollectionSizes("E", 0, 1, 0, 0, 1, 0);
+            assertTrue("E testDirAFile3",  listener.getChangedFiles().contains(testDirAFile3));
+
+            // Update last entry
+            touch(testDirAFile5);
+            touch(testDirA);
+            checkAndNotify();
+            checkCollectionSizes("F", 0, 1, 0, 0, 1, 0);
+            assertTrue("F testDirAFile5",  listener.getChangedFiles().contains(testDirAFile5));
+        } catch (Exception e) {
+            fail("Threw " + e);
+        }
+    }
+
+    /**
+     * Test checkAndNotify() deleting
+     */
+    public void testFileDelete() {
+        try {
+            checkAndNotify();
+            checkCollectionsEmpty("A");
+            File testDirA = new File(testDir, "test-dir-A");
+            testDirA.mkdir();
+            touch(testDir);
+            touch(testDirA);
+            File testDirAFile1 = touch(new File(testDirA, "A-file1.java"));
+            File testDirAFile2 = touch(new File(testDirA, "A-file2.java"));
+            File testDirAFile3 = touch(new File(testDirA, "A-file3.java"));
+            File testDirAFile4 = touch(new File(testDirA, "A-file4.java"));
+            File testDirAFile5 = touch(new File(testDirA, "A-file5.java"));
+
+            assertTrue("B testDirAFile1 exists", testDirAFile1.exists());
+            assertTrue("B testDirAFile2 exists", testDirAFile2.exists());
+            assertTrue("B testDirAFile3 exists", testDirAFile3.exists());
+            assertTrue("B testDirAFile4 exists", testDirAFile4.exists());
+            assertTrue("B testDirAFile5 exists", testDirAFile5.exists());
+
+            checkAndNotify();
+            checkCollectionSizes("B", 1, 0, 0, 5, 0, 0);
+            assertTrue("B testDirAFile1", listener.getCreatedFiles().contains(testDirAFile1));
+            assertTrue("B testDirAFile2", listener.getCreatedFiles().contains(testDirAFile2));
+            assertTrue("B testDirAFile3", listener.getCreatedFiles().contains(testDirAFile3));
+            assertTrue("B testDirAFile4", listener.getCreatedFiles().contains(testDirAFile4));
+            assertTrue("B testDirAFile5", listener.getCreatedFiles().contains(testDirAFile5));
+
+            checkAndNotify();
+            checkCollectionsEmpty("C");
+
+            // Delete first entry
+            FileUtils.deleteQuietly(testDirAFile1);
+            touch(testDirA);
+            checkAndNotify();
+            checkCollectionSizes("D", 0, 1, 0, 0, 0, 1);
+            assertFalse("D testDirAFile1 exists", testDirAFile1.exists());
+            assertTrue("D testDirAFile1",  listener.getDeletedFiles().contains(testDirAFile1));
+
+            // Delete file with name between 2 entries
+            FileUtils.deleteQuietly(testDirAFile3);
+            touch(testDirA);
+            checkAndNotify();
+            checkCollectionSizes("E", 0, 1, 0, 0, 0, 1);
+            assertFalse("E testDirAFile3 exists", testDirAFile3.exists());
+            assertTrue("E testDirAFile3",  listener.getDeletedFiles().contains(testDirAFile3));
+
+            // Delete last entry
+            FileUtils.deleteQuietly(testDirAFile5);
+            touch(testDirA);
+            checkAndNotify();
+            checkCollectionSizes("F", 0, 1, 0, 0, 0, 1);
+            assertFalse("F testDirAFile5 exists", testDirAFile5.exists());
+            assertTrue("F testDirAFile5",  listener.getDeletedFiles().contains(testDirAFile5));
+
+        } catch (Exception e) {
+            fail("Threw " + e);
+        }
+    }
+
+    /**
+     * Test checkAndNotify() method
+     */
+    public void testObserveSingleFile() {
+        try {
+            File testDirA = new File(testDir, "test-dir-A");
+            File testDirAFile1 = new File(testDirA, "A-file1.java");
+            testDirA.mkdir();
+
+            FileFilter nameFilter = FileFilterUtils.nameFileFilter(testDirAFile1.getName());
+            createObserver(testDirA, nameFilter);
+            checkAndNotify();
+            checkCollectionsEmpty("A");
+            assertFalse("A testDirAFile1 exists", testDirAFile1.exists());
+
+            // Create
+            touch(testDirAFile1);
+            File testDirAFile2 = touch(new File(testDirA, "A-file2.txt"));  /* filter should ignore */
+            File testDirAFile3 = touch(new File(testDirA, "A-file3.java")); /* filter should ignore */
+            assertTrue("B testDirAFile1 exists", testDirAFile1.exists());
+            assertTrue("B testDirAFile2 exists", testDirAFile2.exists());
+            assertTrue("B testDirAFile3 exists", testDirAFile3.exists());
+            checkAndNotify();
+            checkCollectionSizes("C", 0, 0, 0, 1, 0, 0);
+            assertTrue("C created", listener.getCreatedFiles().contains(testDirAFile1));
+            assertFalse("C created", listener.getCreatedFiles().contains(testDirAFile2));
+            assertFalse("C created", listener.getCreatedFiles().contains(testDirAFile3));
+
+            // Modify
+            touch(testDirAFile1);
+            touch(testDirAFile2);
+            touch(testDirAFile3);
+            checkAndNotify();
+            checkCollectionSizes("D", 0, 0, 0, 0, 1, 0);
+            assertTrue("D changed", listener.getChangedFiles().contains(testDirAFile1));
+            assertFalse("D changed", listener.getChangedFiles().contains(testDirAFile2));
+            assertFalse("D changed", listener.getChangedFiles().contains(testDirAFile3));
+
+            // Delete
+            FileUtils.deleteQuietly(testDirAFile1);
+            FileUtils.deleteQuietly(testDirAFile2);
+            FileUtils.deleteQuietly(testDirAFile3);
+            assertFalse("E testDirAFile1 exists", testDirAFile1.exists());
+            assertFalse("E testDirAFile2 exists", testDirAFile2.exists());
+            assertFalse("E testDirAFile3 exists", testDirAFile3.exists());
+            checkAndNotify();
+            checkCollectionSizes("E", 0, 0, 0, 0, 0, 1);
+            assertTrue("E deleted", listener.getDeletedFiles().contains(testDirAFile1));
+            assertFalse("E deleted", listener.getDeletedFiles().contains(testDirAFile2));
+            assertFalse("E deleted", listener.getDeletedFiles().contains(testDirAFile3));
+
+        } catch (Exception e) {
+            fail("Threw " + e);
+        }
+    }
+
+    /**
+     * Call {@link FilesystemObserver#checkAndNotify()}.
+     *
+     * @throws Exception if an error occurs
+     */
+    protected void checkAndNotify() throws Exception {
+        observer.checkAndNotify();
+    }
+
+    /**
+     * Check all the Collections are empty
+     */
+    private void checkCollectionsEmpty(String label) {
+        checkCollectionSizes("EMPTY-" + label, 0, 0, 0, 0, 0, 0);
+    }
+
+    /**
+     * Check all the Collections have the expected sizes.
+     */
+    private void checkCollectionSizes(String label, int dirCreate, int dirChange, int dirDelete, int fileCreate, int fileChange, int fileDelete) {
+        assertEquals(label + ": No. of directories created",  dirCreate,  listener.getCreatedDirectories().size());
+        assertEquals(label + ": No. of directories changed",  dirChange,  listener.getChangedDirectories().size());
+        assertEquals(label + ": No. of directories deleted",  dirDelete,  listener.getDeletedDirectories().size());
+        assertEquals(label + ": No. of files created", fileCreate, listener.getCreatedFiles().size());
+        assertEquals(label + ": No. of files changed", fileChange, listener.getChangedFiles().size());
+        assertEquals(label + ": No. of files deleted", fileDelete, listener.getDeletedFiles().size());
+    }
+
+    /**
+     * Either creates a file if it doesn't exist or updates the last modified date/time
+     * if it does.
+     *
+     * @param file The file to touch
+     * @return The file
+     */
+    private File touch(File file) {
+        long lastModified = file.exists() ? file.lastModified() : 0;
+        try {
+            FileUtils.touch(file);
+            while (lastModified == file.lastModified()) {
+                try {
+                    Thread.sleep(5);
+                } catch(InterruptedException ie) {
+                    // ignore
+                }
+                FileUtils.touch(file);
+            }
+        } catch (Exception e) {
+            fail("Touching " + file +": " + e);
+        }
+        return file;
+    }
+}

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

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