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: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]