You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by sc...@apache.org on 2006/10/07 17:15:26 UTC

svn commit: r453928 - in /jakarta/commons/proper/io/trunk/src: java/org/apache/commons/io/DirectoryWalker.java test/org/apache/commons/io/DirectoryWalkerTestCase.java test/org/apache/commons/io/PackageTestSuite.java

Author: scolebourne
Date: Sat Oct  7 08:15:26 2006
New Revision: 453928

URL: http://svn.apache.org/viewvc?view=rev&rev=453928
Log:
Add cancellation support to DirectoryWalker

Modified:
    jakarta/commons/proper/io/trunk/src/java/org/apache/commons/io/DirectoryWalker.java
    jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/DirectoryWalkerTestCase.java
    jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/PackageTestSuite.java

Modified: jakarta/commons/proper/io/trunk/src/java/org/apache/commons/io/DirectoryWalker.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/io/trunk/src/java/org/apache/commons/io/DirectoryWalker.java?view=diff&rev=453928&r1=453927&r2=453928
==============================================================================
--- jakarta/commons/proper/io/trunk/src/java/org/apache/commons/io/DirectoryWalker.java (original)
+++ jakarta/commons/proper/io/trunk/src/java/org/apache/commons/io/DirectoryWalker.java Sat Oct  7 08:15:26 2006
@@ -94,6 +94,49 @@
  *
  * </pre>
  *
+ * <h3>Cancellation</h3>
+ *
+ * The DirectoryWalker contains some of the logic required for cancel processing.
+ * Subclasses must complete the implementation.
+ * This is for performance and to ensure you think about the multihreaded implications.
+ * <p>
+ * Before any processing occurs on each file or directory the
+ * <code>isCancelled()</code> method is called. If it returns <code>true</code>
+ * then <code>handleCancelled()<code> is called. This method can decide whether
+ * to accept or ignore the cancellation. If it accepts it then all further
+ * processing is skipped and the operation returns. If it rejects it then
+ * processing continues on as before. This is useful if a group of files has
+ * meaning and cancellation cannot occur in the middle of the group.
+ * <p>
+ * The default implementation of <code>isCancelled()</code> always
+ * returns <code>false</code> and it is down to the implementation
+ * to fully implement the <code>isCancelled()</code> behaviour.
+ * <p>
+ * The following example uses the
+ * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#36930">
+ * volatile</a> keyword to (hopefully) ensure it will work properly in
+ * a multi-threaded environment.
+ *
+ * <pre>
+ *  public class FooDirectoryWalker extends DirectoryWalker {
+ *
+ *    private volatile boolean cancelled = false;
+ *
+ *    public void cancel() {
+ *        cancelled = true;
+ *    }
+ *
+ *    public boolean isCancelled() {
+ *        return cancelled;
+ *    }
+ *
+ *    protected boolean handleCancelled(File file, int depth, Collection results) {
+ *       // implement any cancel processing here
+ *       return true;  // accept cancellation
+ *    }
+ *  }
+ * </pre>
+ *
  * @since Commons IO 1.3
  * @version $Revision: 424748 $
  */
@@ -109,6 +152,13 @@
     private final int depthLimit;
 
     /**
+     * Construct an instance with no filtering and unlimited <i>depth</i>.
+     */
+    protected DirectoryWalker() {
+        this(null, -1);
+    }
+
+    /**
      * Construct an instance with a filter and limit the <i>depth</i> navigated to.
      *
      * @param filter  the filter to limit the navigation/results, may be null
@@ -131,35 +181,49 @@
      * Once called, this method will emit events as it walks the hierarchy.
      * The event methods have the prefix <code>handle</code>.
      *
-     * @param startDirectory  the directory to start from
+     * @param startDirectory  the directory to start from, not null
      * @param results  the collection of result objects, may be updated
+     * @return true if completed, false if cancelled
+     * @throws NullPointerException if the start directory is null
      */
-    protected void walk(File startDirectory, Collection results) {
+    protected boolean walk(File startDirectory, Collection results) {
         handleStart(startDirectory, results);
-        walk(startDirectory, 0, results);
+        if (walk(startDirectory, 0, results) == false) {
+            return false;  // cancelled
+        }
         handleEnd(results);
+        return true;
     }
 
     /**
      * Main recursive method to examine the directory hierarchy.
      *
-     * @param directory  the directory to examine
+     * @param directory  the directory to examine, not null
      * @param depth  the directory level (starting directory = 0)
      * @param results  the collection of result objects, may be updated
+     * @return false if cancelled
      */
-    private void walk(File directory, int depth, Collection results) {
+    private boolean walk(File directory, int depth, Collection results) {
+        if (isCancelled() && handleCancelled(directory, depth, results)) {
+            return false;  // cancelled
+        }
         if (handleDirectory(directory, depth, results)) {
             handleDirectoryStart(directory, depth, results);
             int childDepth = depth + 1;
             if (depthLimit < 0 || childDepth <= depthLimit) {
                 File[] files = (filter == null ? directory.listFiles() : directory.listFiles(filter));
                 if (files == null) {
-                    handleRestricted(directory, results);
+                    handleRestricted(directory, childDepth, results);
                 } else {
                     for (int i = 0; i < files.length; i++) {
                         if (files[i].isDirectory()) {
-                            walk(files[i], childDepth, results);
+                            if (walk(files[i], childDepth, results) == false) {
+                                return false;  // cancelled
+                            }
                         } else {
+                            if (isCancelled() && handleCancelled(files[i], childDepth, results)) {
+                                return false;  // cancelled
+                            }
                             handleFile(files[i], childDepth, results);
                         }
                     }
@@ -167,6 +231,7 @@
             }
             handleDirectoryEnd(directory, depth, results);
         }
+        return true;
     }
 
     //-----------------------------------------------------------------------
@@ -198,7 +263,7 @@
      */
     protected boolean handleDirectory(File directory, int depth, Collection results) {
         // do nothing - overridable by subclass
-        return true;
+        return true;  // process directory
     }
 
     /**
@@ -233,9 +298,10 @@
      * This implementation does nothing.
      *
      * @param directory  the restricted directory
+     * @param depth  the current directory level (starting directory = 0)
      * @param results  the collection of result objects, may be updated
      */
-    protected void handleRestricted(File directory, Collection results) {
+    protected void handleRestricted(File directory, int depth, Collection results) {
         // do nothing - overridable by subclass
     }
 
@@ -261,6 +327,48 @@
      */
     protected void handleEnd(Collection results) {
         // do nothing - overridable by subclass
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Indicates whether the operation has been cancelled or not.
+     * <p>
+     * This implementation always returns <code>false</code>.
+     *
+     * @return true if the operation has been cancelled
+     */
+    protected boolean isCancelled() {
+        return false;
+    }
+
+    /**
+     * Overridable callback method invoked when the operation is cancelled.
+     * <p>
+     * This method returns a boolean to indicate if the cancellation is being
+     * accepted or rejected. This could be useful if you need to finish processing
+     * all the files in a directory before accepting the cancellation request.
+     * For example, this only accepts the cancel when the current directory is complete:
+     * <pre>
+     * protected boolean handleCancelled(File file, int depth, Collection results) {
+     *   return file.isDirectory();
+     * }
+     * </pre>
+     * If you return true, then the whole operation is cancelled and no more event
+     * methods will be called.
+     * <p>
+     * If you return false, then normal processing will continue until the next time
+     * the <code>isCancelled()</code> method returns false.
+     * <p>
+     * This implementation returns true, accepting the cancellation.
+     *
+     * @param file  the file about to be processed which may be a file or a directory
+     * @param depth  the current directory level (starting directory = 0)
+     * @param results  the collection of result objects, may be updated
+     * @return true to accept the cancellation, false to reject it
+     */
+    protected boolean handleCancelled(File file, int depth, Collection results) {
+        // do nothing - overridable by subclass
+        return true;  // accept cancellation
     }
 
 }

Modified: jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/DirectoryWalkerTestCase.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/DirectoryWalkerTestCase.java?view=diff&rev=453928&r1=453927&r2=453928
==============================================================================
--- jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/DirectoryWalkerTestCase.java (original)
+++ jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/DirectoryWalkerTestCase.java Sat Oct  7 08:15:26 2006
@@ -21,6 +21,8 @@
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Collection;
+
+import junit.framework.Assert;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
@@ -124,11 +126,11 @@
      */
     public void testFilterAndLimitC() {
         List results = new TestFileFinder(NOT_SVN, 3).find(javaDir);
-        assertEquals("[A] Result Size", 4, results.size());
-        assertTrue("[A] Start Dir",   results.contains(javaDir));
-        assertTrue("[A] Org Dir",     results.contains(orgDir));
-        assertTrue("[A] Apache Dir",  results.contains(apacheDir));
-        assertTrue("[A] Commons Dir", results.contains(commonsDir));
+        assertEquals("[C] Result Size", 4, results.size());
+        assertTrue("[C] Start Dir",   results.contains(javaDir));
+        assertTrue("[C] Org Dir",     results.contains(orgDir));
+        assertTrue("[C] Apache Dir",  results.contains(apacheDir));
+        assertTrue("[C] Commons Dir", results.contains(commonsDir));
     }
 
     /**
@@ -162,7 +164,6 @@
         assertEquals("Result Size", 1, results.size());
         assertTrue("Current Dir", results.contains(invalidDir));
  
-        // TODO is this what we want with Null directory?
         try {
             new TestFileFinder(null, -1).find(null);
             fail("Null start directory didn't throw Exception");
@@ -204,6 +205,20 @@
         return new NameFileFilter(names);
     }
 
+    /**
+     * Test Cancel
+     */
+    public void testCancel() {
+        List results = new TestCancelWalker(2, true).find(javaDir);
+        assertEquals(2, results.size());
+        
+        results = new TestCancelWalker(3, true).find(javaDir);
+        assertEquals(3, results.size());
+        
+        results = new TestCancelWalker(3, false).find(javaDir);
+        assertEquals(6, results.size());
+    }
+
     // ------------ Test DirectoryWalker implementation --------------------------
 
     /**
@@ -219,7 +234,7 @@
         /** find files. */
         protected List find(File startDirectory) {
            List results = new ArrayList();
-           walk(startDirectory, results);
+           Assert.assertEquals(true, walk(startDirectory, results));
            return results;
         }
 
@@ -249,6 +264,84 @@
         /** Always returns false. */
         protected boolean handleDirectory(File directory, int depth, Collection results) {
             return false;
+        }
+    }
+
+    // ------------ Test DirectoryWalker implementation --------------------------
+
+    /**
+     * Test DirectoryWalker implementation that finds files in a directory hierarchy
+     * applying a file filter.
+     */
+    static class TestCancelWalker extends DirectoryWalker {
+        private boolean cancelled;
+        private int count;
+        private boolean accept;
+
+        TestCancelWalker(int count, boolean accept) {
+            super();
+            this.count = count;
+            this.accept = accept;
+        }
+
+        /** find files. */
+        public List find(File startDirectory) {
+           List results = new ArrayList();
+           Assert.assertEquals(false, walk(startDirectory, results));
+           return results;
+        }
+
+        /** Return cancelled flag. */
+        protected boolean isCancelled() {
+            return cancelled;
+        }
+
+        /** Handles a directory start. */
+        protected void handleDirectoryStart(File directory, int depth, Collection results) {
+            if (accept) {
+                Assert.assertEquals(false, cancelled);
+            }
+        }
+
+        /** Handles a directory end by adding the File to the result set. */
+        protected void handleDirectoryEnd(File directory, int depth, Collection results) {
+            if (accept) {
+                Assert.assertEquals(false, cancelled);
+            }
+            results.add(directory);
+            cancelled = (results.size() >= count);
+        }
+
+        /** Handles a file by adding the File to the result set. */
+        protected void handleFile(File file, int depth, Collection results) {
+            if (accept) {
+                Assert.assertEquals(false, cancelled);
+            }
+            results.add(file);
+            cancelled = (results.size() >= count);
+        }
+
+        /** Handles start. */
+        protected void handleStart(File directory, Collection results) {
+            if (accept) {
+                Assert.assertEquals(false, cancelled);
+            }
+        }
+
+        /** Handles end. */
+        protected void handleEnd(Collection results) {
+            if (accept) {
+                Assert.assertEquals(false, cancelled);
+            }
+        }
+
+        /** Handles end. */
+        protected boolean handleCancelled(File file, int depth, Collection results) {
+            Assert.assertEquals(true, cancelled);
+            if (accept) {
+                return true;
+            }
+            return (results.size() >= (count * 2));
         }
     }
 

Modified: jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/PackageTestSuite.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/PackageTestSuite.java?view=diff&rev=453928&r1=453927&r2=453928
==============================================================================
--- jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/PackageTestSuite.java (original)
+++ jakarta/commons/proper/io/trunk/src/test/org/apache/commons/io/PackageTestSuite.java Sat Oct  7 08:15:26 2006
@@ -37,6 +37,7 @@
         TestSuite suite = new TestSuite("IO Utilities");
         suite.addTest(new TestSuite(CopyUtilsTest.class));
         suite.addTest(new TestSuite(DemuxTestCase.class));
+        suite.addTest(new TestSuite(DirectoryWalkerTestCase.class));
         suite.addTest(new TestSuite(EndianUtilsTest.class));
         suite.addTest(new TestSuite(FileCleanerTestCase.class));
         suite.addTest(new TestSuite(FileDeleteStrategyTestCase.class));



---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org