Author: niallp Date: Thu Oct 12 21:46:16 2006 New Revision: 463560 URL: http://svn.apache.org/viewvc?view=rev&rev=463560 Log: IO-86 - Update cancellation behaviour in DirectoryWalker to use a new CancelException, also add IOException to "lifecycle" method definitions.
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 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=463560&r1=463559&r2=463560 ============================================================================== --- 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 Thu Oct 12 21:46:16 2006 @@ -18,6 +18,7 @@ import java.io.File; import java.io.FileFilter; +import java.io.IOException; import java.util.Collection; /** @@ -28,8 +29,19 @@ * limit the files and direcories visited. * Commons IO supplies many common filter implementations in the * <a href="filefilter/package-summary.html"> filefilter</a> package. + * <p> + * The following sections describe: + * <ul> + * <li><a href="#example">1. Example Implementation</a> - example + * <code>FileCleaner</code> implementation.</li> + * <li><a href="#filter">2. Filter Example</a> - using + * [EMAIL PROTECTED] FileFilter}(s) with <code>DirectoryWalker</code>.</li> + * <li><a href="#cancel">3. Cancellation</a> - how to implement cancellation + * behaviour.</li> + * </ul> * - * <h3>Example Implementation</h3> + * <a name="example"></a> + * <h3>1. Example Implementation</h3> * * There are many possible extensions, for example, to delete all * files and '.svn' directories, and return a list of deleted files: @@ -37,7 +49,7 @@ * public class FileCleaner extends DirectoryWalker { * * public FileCleaner() { - * super(null, -1); + * super(); * } * * public List clean(File startDirectory) { @@ -65,7 +77,8 @@ * } * </pre> * - * <h3>Filter Example</h3> + * <a name="filter"></a> + * <h3>2. Filter Example</h3> * * If you wanted all directories which are not hidden * and files which end in ".txt" - you could build a composite filter @@ -94,28 +107,49 @@ * * </pre> * - * <h3>Cancellation</h3> + * <a name="cancel"></a> + * <h3>3. 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. + * What <code>DirectoryWalker</code> does provide for cancellation is: + * <ul> + * <li>[EMAIL PROTECTED] CancelException} which can be thrown in any of the + * <i>lifecycle</i> methods to stop processing.</li> + * <li>The <code>walk()</code> method traps thrown [EMAIL PROTECTED] CancelException} + * and calls the <code>handleCancelled()</code> method, providing + * a place for custom cancel processing.</li> + * </ul> + * <p> + * Implementations need to provide: + * <ul> + * <li>The decision logic on whether to cancel processing or not.</li> + * <li>Constructing and throwing a [EMAIL PROTECTED] CancelException}.</li> + * <li>Custom cancel processing in the <code>handleCancelled()</code> method. + * </ul> * <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. + * Two possible scenarios are envisaged for cancellation: + * <ul> + * <li><a href="#external">3.1 External / Mult-threaded</a> - cancellation being + * decided/initiated by an external process.</li> + * <li><a href="#internal">3.2 Internal</a> - cancellation being decided/initiated + * from within a DirectoryWalker implementation.</li> + * </ul> * <p> - * The following example uses the + * The following sections provide example implementations for these two different + * scenarios. + * + * <a name="external"></a> + * <h4>3.1 External / Mult-threaded</h4> + * + * This example provides a <code>cancel()</code> method for external processes to + * indcate that processing must stop. Calling this method sets a * <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. + * volatile</a> flag to (hopefully) ensure it will work properly in + * a multi-threaded environment. In this implementation the flag is checked in two + * of the lifecycle methods using a convenience <code>checkIfCancelled()</code> method + * which throws a [EMAIL PROTECTED] CancelException} if cancellation has been requested. * * <pre> * public class FooDirectoryWalker extends DirectoryWalker { @@ -126,13 +160,56 @@ * cancelled = true; * } * - * public boolean isCancelled() { - * return cancelled; + * protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException { + * checkIfCancelled(directory, depth); // Cancel Check + * return true; + * } + * + * protected void handleFile(File file, int depth, Collection results) throws IOException { + * checkIfCancelled(file, depth); // Cancel Check + * results.add(file); + * } + * + * private void checkIfCancelled(File file, int depth) throws CancelException { + * if (cancelled) { + * throw new CancelException(file, depth); + * } * } * - * protected boolean handleCancelled(File file, int depth, Collection results) { - * // implement any cancel processing here - * return true; // accept cancellation + * protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) { + * // implement cancel processing here + * } + * } + * </pre> + * + * <a name="internal"></a> + * <h4>3.2 Internal</h4> + * + * This shows an example of how internal cancellation processing could be implemented. + * <b>Note</b> the decision logic and throwing a [EMAIL PROTECTED] CancelException} could be implemented + * in any of the <i>lifecycle</i> methods. + * + * <pre> + * public class BarDirectoryWalker extends DirectoryWalker { + * + * protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException { + * // cancel if hidden directory + * if (directory.isHidden()) { + * throw new CancelException(file, depth); + * } + * return true; + * } + * + * protected void handleFile(File file, int depth, Collection results) throws IOException { + * // cancel if read-only file + * if (!file.canWrite()) { + * throw new CancelException(file, depth); + * } + * results.add(file); + * } + * + * protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) { + * // implement cancel processing here * } * } * </pre> @@ -183,16 +260,20 @@ * * @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 + * @throws IOException if an I/O Error occurs */ - protected boolean walk(File startDirectory, Collection results) { - handleStart(startDirectory, results); - if (walk(startDirectory, 0, results) == false) { - return false; // cancelled + protected void walk(File startDirectory, Collection results) throws IOException { + if (startDirectory == null) { + throw new NullPointerException("Start Directory is null"); + } + try { + handleStart(startDirectory, results); + walk(startDirectory, 0, results); + handleEnd(results); + } catch(CancelException cancel) { + handleCancelled(startDirectory, results, cancel); } - handleEnd(results); - return true; } /** @@ -201,12 +282,9 @@ * @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 + * @throws IOException if an I/O Error occurs */ - private boolean walk(File directory, int depth, Collection results) { - if (isCancelled() && handleCancelled(directory, depth, results)) { - return false; // cancelled - } + private void walk(File directory, int depth, Collection results) throws IOException { if (handleDirectory(directory, depth, results)) { handleDirectoryStart(directory, depth, results); int childDepth = depth + 1; @@ -217,13 +295,8 @@ } else { for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { - if (walk(files[i], childDepth, results) == false) { - return false; // cancelled - } + walk(files[i], childDepth, results); } else { - if (isCancelled() && handleCancelled(files[i], childDepth, results)) { - return false; // cancelled - } handleFile(files[i], childDepth, results); } } @@ -231,7 +304,6 @@ } handleDirectoryEnd(directory, depth, results); } - return true; } //----------------------------------------------------------------------- @@ -242,8 +314,9 @@ * * @param startDirectory the directory to start from * @param results the collection of result objects, may be updated + * @throws IOException if an I/O Error occurs */ - protected void handleStart(File startDirectory, Collection results) { + protected void handleStart(File startDirectory, Collection results) throws IOException { // do nothing - overridable by subclass } @@ -260,8 +333,9 @@ * @param depth the current directory level (starting directory = 0) * @param results the collection of result objects, may be updated * @return true to process this directory, false to skip this directory + * @throws IOException if an I/O Error occurs */ - protected boolean handleDirectory(File directory, int depth, Collection results) { + protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException { // do nothing - overridable by subclass return true; // process directory } @@ -274,8 +348,9 @@ * @param directory the current directory being processed * @param depth the current directory level (starting directory = 0) * @param results the collection of result objects, may be updated + * @throws IOException if an I/O Error occurs */ - protected void handleDirectoryStart(File directory, int depth, Collection results) { + protected void handleDirectoryStart(File directory, int depth, Collection results) throws IOException { // do nothing - overridable by subclass } @@ -287,8 +362,9 @@ * @param file the current file being processed * @param depth the current directory level (starting directory = 0) * @param results the collection of result objects, may be updated + * @throws IOException if an I/O Error occurs */ - protected void handleFile(File file, int depth, Collection results) { + protected void handleFile(File file, int depth, Collection results) throws IOException { // do nothing - overridable by subclass } @@ -300,8 +376,9 @@ * @param directory the restricted directory * @param depth the current directory level (starting directory = 0) * @param results the collection of result objects, may be updated + * @throws IOException if an I/O Error occurs */ - protected void handleRestricted(File directory, int depth, Collection results) { + protected void handleRestricted(File directory, int depth, Collection results) throws IOException { // do nothing - overridable by subclass } @@ -313,8 +390,9 @@ * @param directory the directory being processed * @param depth the current directory level (starting directory = 0) * @param results the collection of result objects, may be updated + * @throws IOException if an I/O Error occurs */ - protected void handleDirectoryEnd(File directory, int depth, Collection results) { + protected void handleDirectoryEnd(File directory, int depth, Collection results) throws IOException { // do nothing - overridable by subclass } @@ -324,51 +402,80 @@ * This implementation does nothing. * * @param results the collection of result objects, may be updated + * @throws IOException if an I/O Error occurs */ - protected void handleEnd(Collection results) { + protected void handleEnd(Collection results) throws IOException { // 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. + * This implementation just re-throws the [EMAIL PROTECTED] CancelException}. * - * @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 startDirectory the directory to start from * @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 + * @param cancel The exception throw to cancel further processing + * containing details at the point of cancellation. + * @throws IOException if an I/O Error occurs + */ + protected void handleCancelled(File startDirectory, Collection results, + CancelException cancel) throws IOException { + // re-throw exception - overridable by subclass + throw cancel; } + /** + * CancelException is thrown in DirectoryWalker to cancel the current + * processing. + */ + public static class CancelException extends IOException { + + private File file; + private int depth = -1; + + /** + * Constructs a <code>CancelException</code> with + * the file and depth when cancellation occurred. + * + * @param file The file when the operation was cancelled + * @param depth The depth when the operation was cancelled + */ + public CancelException(File file, int depth) { + this("Operation Cancelled", file, depth); + } + + /** + * Constructs a <code>CancelException</code> with + * an appropriate message and the file and depth when + * cancellation occurred. + * + * @param message The detail message. + * @param file The file when the operation was cancelled + * @param depth The depth when the operation was cancelled + */ + public CancelException(String message, File file, int depth) { + super(message); + this.file = file; + this.depth = depth; + } + + /** + * Return the file when the operation was cancelled. + * + * @return The file when the operation was cancelled + */ + public File getFile() { + return file; + } + + /** + * Return the depth when the operation was cancelled. + * + * @return The depth when the operation was cancelled + */ + public int getDepth() { + return depth; + } + } } 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=463560&r1=463559&r2=463560 ============================================================================== --- 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 Thu Oct 12 21:46:16 2006 @@ -18,6 +18,7 @@ import java.io.File; import java.io.FileFilter; +import java.io.IOException; import java.util.List; import java.util.ArrayList; import java.util.Collection; @@ -209,14 +210,41 @@ * 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()); + String cancelName = null; + + // Cancel on a file + try { + cancelName = "DirectoryWalker.java"; + List results = new TestCancelWalker(cancelName, false).find(javaDir); + fail("CancelException not thrown for '" + cancelName + "'"); + } catch (DirectoryWalker.CancelException cancel) { + assertEquals("File: " + cancelName, cancelName, cancel.getFile().getName()); + assertEquals("Depth: " + cancelName, 5, cancel.getDepth()); + } catch(IOException ex) { + fail("IOException: " + cancelName + " " + ex); + } + + // Cancel on a directory + try { + cancelName = "commons"; + List results = new TestCancelWalker(cancelName, false).find(javaDir); + fail("CancelException not thrown for '" + cancelName + "'"); + } catch (DirectoryWalker.CancelException cancel) { + assertEquals("File: " + cancelName, cancelName, cancel.getFile().getName()); + assertEquals("Depth: " + cancelName, 3, cancel.getDepth()); + } catch(IOException ex) { + fail("IOException: " + cancelName + " " + ex); + } + + // Suppress CancelException (use same file name as preceeding test) + try { + List results = new TestCancelWalker(cancelName, true).find(javaDir); + File lastFile = (File)results.get(results.size() - 1); + assertEquals("Suppress: " + cancelName, cancelName, lastFile.getName()); + } catch(IOException ex) { + fail("Suppress threw " + ex); + } + } // ------------ Test DirectoryWalker implementation -------------------------- @@ -234,7 +262,11 @@ /** find files. */ protected List find(File startDirectory) { List results = new ArrayList(); - Assert.assertEquals(true, walk(startDirectory, results)); + try { + walk(startDirectory, results); + } catch(IOException ex) { + Assert.fail(ex.toString()); + } return results; } @@ -274,74 +306,44 @@ * applying a file filter. */ static class TestCancelWalker extends DirectoryWalker { - private boolean cancelled; - private int count; - private boolean accept; + private String cancelFileName; + private boolean suppressCancel; - TestCancelWalker(int count, boolean accept) { + TestCancelWalker(String cancelFileName,boolean suppressCancel) { super(); - this.count = count; - this.accept = accept; + this.cancelFileName = cancelFileName; + this.suppressCancel = suppressCancel; } /** find files. */ - public List find(File startDirectory) { + protected List find(File startDirectory) throws IOException { List results = new ArrayList(); - Assert.assertEquals(false, walk(startDirectory, results)); + 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); - } + protected void handleDirectoryEnd(File directory, int depth, Collection results) throws IOException { results.add(directory); - cancelled = (results.size() >= count); + if (cancelFileName.equals(directory.getName())) { + throw new CancelException(directory, depth); + } } /** 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); - } + protected void handleFile(File file, int depth, Collection results) throws IOException { 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); + if (cancelFileName.equals(file.getName())) { + throw new CancelException(file, depth); } } - /** Handles end. */ - protected boolean handleCancelled(File file, int depth, Collection results) { - Assert.assertEquals(true, cancelled); - if (accept) { - return true; + /** Handles Cancel. */ + protected void handleCancelled(File startDirectory, Collection results, + CancelException cancel) throws IOException { + if (!suppressCancel) { + super.handleCancelled(startDirectory, results, cancel); } - return (results.size() >= (count * 2)); } } --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]