Skip to content

Commit 7abbdd1

Browse files
author
Stephen Colebourne
committed
Add additional cancellation support, including checkIsCancelled() and handleIsCancelled()
git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/io/trunk@481847 13f79535-47bb-0310-9956-ffa450edef68
1 parent 9561dfc commit 7abbdd1

2 files changed

Lines changed: 203 additions & 46 deletions

File tree

src/java/org/apache/commons/io/DirectoryWalker.java

Lines changed: 101 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,15 @@
184184
* <a name="external"></a>
185185
* <h4>3.1 External / Multi-threaded</h4>
186186
*
187-
* This example provides a <code>cancel()</code> method for external processes to
188-
* indcate that processing must stop. Calling this method sets a
189-
* <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#36930">volatile</a>
190-
* flag to (hopefully) ensure it will work properly in
191-
* a multi-threaded environment. In this implementation the flag is checked in two
192-
* of the lifecycle methods using a convenience <code>checkIfCancelled()</code> method
193-
* which throws a {@link CancelException} if cancellation has been requested.
187+
* This example provides a public <code>cancel()</code> method that can be
188+
* called by another thread to stop the processing. A typical example use-case
189+
* would be a cancel button on a GUI. Calling this method sets a
190+
* <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#36930">
191+
* volatile</a> flag to ensure it will work properly in a multi-threaded environment.
192+
* The flag is returned by the <code>handleIsCancelled()</code> method, which
193+
* will cause the walk to stop immediately. The <code>handleCancelled()</code>
194+
* method will be the next, and last, callback method received once cancellation
195+
* has occurred.
194196
*
195197
* <pre>
196198
* public class FooDirectoryWalker extends DirectoryWalker {
@@ -201,24 +203,12 @@
201203
* cancelled = true;
202204
* }
203205
*
204-
* protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException {
205-
* checkIfCancelled(directory, depth); // Cancel Check
206-
* return true;
207-
* }
208-
*
209-
* protected void handleFile(File file, int depth, Collection results) throws IOException {
210-
* checkIfCancelled(file, depth); // Cancel Check
211-
* results.add(file);
212-
* }
213-
*
214-
* private void checkIfCancelled(File file, int depth) throws CancelException {
215-
* if (cancelled) {
216-
* throw new CancelException(file, depth);
217-
* }
206+
* private void handleIsCancelled(File file, int depth, Collection results) {
207+
* return cancelled;
218208
* }
219209
*
220210
* protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
221-
* // implement cancel processing here
211+
* // implement processing required when a cancellation occurs
222212
* }
223213
* }
224214
* </pre>
@@ -250,7 +240,7 @@
250240
* }
251241
*
252242
* protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
253-
* // implement cancel processing here
243+
* // implement processing required when a cancellation occurs
254244
* }
255245
* }
256246
* </pre>
@@ -324,7 +314,7 @@ protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter,
324314
/**
325315
* Internal method that walks the directory hierarchy in a depth-first manner.
326316
* <p>
327-
* Most users of this class do not need to call this method. This method will
317+
* Users of this class do not need to call this method. This method will
328318
* be called automatically by another (public) method on the specific subclass.
329319
* <p>
330320
* Writers of subclasses should call this method to start the directory walk.
@@ -336,7 +326,7 @@ protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter,
336326
* @throws NullPointerException if the start directory is null
337327
* @throws IOException if an I/O Error occurs
338328
*/
339-
protected void walk(File startDirectory, Collection results) throws IOException {
329+
protected final void walk(File startDirectory, Collection results) throws IOException {
340330
if (startDirectory == null) {
341331
throw new NullPointerException("Start Directory is null");
342332
}
@@ -358,10 +348,12 @@ protected void walk(File startDirectory, Collection results) throws IOException
358348
* @throws IOException if an I/O Error occurs
359349
*/
360350
private void walk(File directory, int depth, Collection results) throws IOException {
351+
checkIfCancelled(directory, depth, results);
361352
if (handleDirectory(directory, depth, results)) {
362353
handleDirectoryStart(directory, depth, results);
363354
int childDepth = depth + 1;
364355
if (depthLimit < 0 || childDepth <= depthLimit) {
356+
checkIfCancelled(directory, depth, results);
365357
File[] childFiles = (filter == null ? directory.listFiles() : directory.listFiles(filter));
366358
if (childFiles == null) {
367359
handleRestricted(directory, childDepth, results);
@@ -371,13 +363,97 @@ private void walk(File directory, int depth, Collection results) throws IOExcept
371363
if (childFile.isDirectory()) {
372364
walk(childFile, childDepth, results);
373365
} else {
366+
checkIfCancelled(childFile, childDepth, results);
374367
handleFile(childFile, childDepth, results);
368+
checkIfCancelled(childFile, childDepth, results);
375369
}
376370
}
377371
}
378372
}
379373
handleDirectoryEnd(directory, depth, results);
380374
}
375+
checkIfCancelled(directory, depth, results);
376+
}
377+
378+
//-----------------------------------------------------------------------
379+
/**
380+
* Checks whether the walk has been cancelled by calling {@link #handleIsCancelled},
381+
* throwing a <code>CancelException</code> if it has.
382+
* <p>
383+
* Writers of subclasses should not normally call this method as it is called
384+
* automatically by the walk of the tree. However, sometimes a single method,
385+
* typically {@link #handleFile}, may take a long time to run. In that case,
386+
* you may wish to check for cancellation by calling this method.
387+
*
388+
* @param file the current file being processed
389+
* @param depth the current file level (starting directory = 0)
390+
* @param results the collection of result objects, may be updated
391+
* @return true to process this directory, false to skip this directory
392+
* @throws IOException if an I/O Error occurs
393+
*/
394+
protected final void checkIfCancelled(File file, int depth, Collection results) throws IOException {
395+
if (handleIsCancelled(file, depth, results)) {
396+
throw new CancelException(file, depth);
397+
}
398+
}
399+
400+
/**
401+
* Overridable callback method invoked to determine if the entire walk
402+
* operation should be immediately cancelled.
403+
* <p>
404+
* This method should be implemented by those subclasses that want to
405+
* provide a public <code>cancel()</code> method available from another
406+
* thread. The design pattern for the subclass should be as follows:
407+
* <pre>
408+
* public class FooDirectoryWalker extends DirectoryWalker {
409+
* private volatile boolean cancelled = false;
410+
*
411+
* public void cancel() {
412+
* cancelled = true;
413+
* }
414+
* private void handleIsCancelled(File file, int depth, Collection results) {
415+
* return cancelled;
416+
* }
417+
* protected void handleCancelled(File startDirectory,
418+
* Collection results, CancelException cancel) {
419+
* // implement processing required when a cancellation occurs
420+
* }
421+
* }
422+
* </pre>
423+
* <p>
424+
* If this method returns true, then the directory walk is immediately
425+
* cancelled. The next callback method will be {@link #handleCancelled}.
426+
* <p>
427+
* This implementation returns false.
428+
*
429+
* @param file the file or directory being processed
430+
* @param depth the current directory level (starting directory = 0)
431+
* @return true if the walk has been cancelled
432+
* @throws IOException if an I/O Error occurs
433+
*/
434+
protected boolean handleIsCancelled(
435+
File file, int depth, Collection results) throws IOException {
436+
// do nothing - overridable by subclass
437+
return false; // not cancelled
438+
}
439+
440+
/**
441+
* Overridable callback method invoked when the operation is cancelled.
442+
* The file being processed when the cancellation occurred can be
443+
* obtained from the exception.
444+
* <p>
445+
* This implementation just re-throws the {@link CancelException}.
446+
*
447+
* @param startDirectory the directory that the walk started from
448+
* @param results the collection of result objects, may be updated
449+
* @param cancel the exception throw to cancel further processing
450+
* containing details at the point of cancellation.
451+
* @throws IOException if an I/O Error occurs
452+
*/
453+
protected void handleCancelled(File startDirectory, Collection results,
454+
CancelException cancel) throws IOException {
455+
// re-throw exception - overridable by subclass
456+
throw cancel;
381457
}
382458

383459
//-----------------------------------------------------------------------
@@ -482,25 +558,6 @@ protected void handleEnd(Collection results) throws IOException {
482558
// do nothing - overridable by subclass
483559
}
484560

485-
/**
486-
* Overridable callback method invoked when the operation is cancelled.
487-
* The file being processed when the cancellation occurred can be
488-
* obtained from the exception.
489-
* <p>
490-
* This implementation just re-throws the {@link CancelException}.
491-
*
492-
* @param startDirectory the directory to start from
493-
* @param results the collection of result objects, may be updated
494-
* @param cancel the exception throw to cancel further processing
495-
* containing details at the point of cancellation.
496-
* @throws IOException if an I/O Error occurs
497-
*/
498-
protected void handleCancelled(File startDirectory, Collection results,
499-
CancelException cancel) throws IOException {
500-
// re-throw exception - overridable by subclass
501-
throw cancel;
502-
}
503-
504561
//-----------------------------------------------------------------------
505562
/**
506563
* CancelException is thrown in DirectoryWalker to cancel the current

src/test/org/apache/commons/io/DirectoryWalkerTestCase.java

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ public void testCancel() {
288288
// Cancel on a file
289289
try {
290290
cancelName = "DirectoryWalker.java";
291-
List results = new TestCancelWalker(cancelName, false).find(javaDir);
291+
new TestCancelWalker(cancelName, false).find(javaDir);
292292
fail("CancelException not thrown for '" + cancelName + "'");
293293
} catch (DirectoryWalker.CancelException cancel) {
294294
assertEquals("File: " + cancelName, cancelName, cancel.getFile().getName());
@@ -300,7 +300,7 @@ public void testCancel() {
300300
// Cancel on a directory
301301
try {
302302
cancelName = "commons";
303-
List results = new TestCancelWalker(cancelName, false).find(javaDir);
303+
new TestCancelWalker(cancelName, false).find(javaDir);
304304
fail("CancelException not thrown for '" + cancelName + "'");
305305
} catch (DirectoryWalker.CancelException cancel) {
306306
assertEquals("File: " + cancelName, cancelName, cancel.getFile().getName());
@@ -320,6 +320,51 @@ public void testCancel() {
320320

321321
}
322322

323+
/**
324+
* Test Cancel
325+
*/
326+
public void testMultiThreadCancel() {
327+
String cancelName = null;
328+
TestMultiThreadCancelWalker walker = null;
329+
// Cancel on a file
330+
try {
331+
cancelName = "DirectoryWalker.java";
332+
walker = new TestMultiThreadCancelWalker(cancelName, false);
333+
walker.find(javaDir);
334+
fail("CancelException not thrown for '" + cancelName + "'");
335+
} catch (DirectoryWalker.CancelException cancel) {
336+
File last = (File) walker.results.get(walker.results.size() - 1);
337+
assertEquals(cancelName, last.getName());
338+
assertEquals("Depth: " + cancelName, 5, cancel.getDepth());
339+
} catch(IOException ex) {
340+
fail("IOException: " + cancelName + " " + ex);
341+
}
342+
343+
// Cancel on a directory
344+
try {
345+
cancelName = "commons";
346+
walker = new TestMultiThreadCancelWalker(cancelName, false);
347+
walker.find(javaDir);
348+
fail("CancelException not thrown for '" + cancelName + "'");
349+
} catch (DirectoryWalker.CancelException cancel) {
350+
assertEquals("File: " + cancelName, cancelName, cancel.getFile().getName());
351+
assertEquals("Depth: " + cancelName, 3, cancel.getDepth());
352+
} catch(IOException ex) {
353+
fail("IOException: " + cancelName + " " + ex);
354+
}
355+
356+
// Suppress CancelException (use same file name as preceeding test)
357+
try {
358+
walker = new TestMultiThreadCancelWalker(cancelName, true);
359+
List results = walker.find(javaDir);
360+
File lastFile = (File) results.get(results.size() - 1);
361+
assertEquals("Suppress: " + cancelName, cancelName, lastFile.getName());
362+
} catch(IOException ex) {
363+
fail("Suppress threw " + ex);
364+
}
365+
366+
}
367+
323368
// ------------ Test DirectoryWalker implementation --------------------------
324369

325370
/**
@@ -424,4 +469,59 @@ protected void handleCancelled(File startDirectory, Collection results,
424469
}
425470
}
426471

472+
/**
473+
* Test DirectoryWalker implementation that finds files in a directory hierarchy
474+
* applying a file filter.
475+
*/
476+
static class TestMultiThreadCancelWalker extends DirectoryWalker {
477+
private String cancelFileName;
478+
private boolean suppressCancel;
479+
private boolean cancelled;
480+
public List results;
481+
482+
TestMultiThreadCancelWalker(String cancelFileName, boolean suppressCancel) {
483+
super();
484+
this.cancelFileName = cancelFileName;
485+
this.suppressCancel = suppressCancel;
486+
}
487+
488+
/** find files. */
489+
protected List find(File startDirectory) throws IOException {
490+
results = new ArrayList();
491+
walk(startDirectory, results);
492+
return results;
493+
}
494+
495+
/** Handles a directory end by adding the File to the result set. */
496+
protected void handleDirectoryEnd(File directory, int depth, Collection results) throws IOException {
497+
results.add(directory);
498+
assertEquals(false, cancelled);
499+
if (cancelFileName.equals(directory.getName())) {
500+
cancelled = true;
501+
}
502+
}
503+
504+
/** Handles a file by adding the File to the result set. */
505+
protected void handleFile(File file, int depth, Collection results) throws IOException {
506+
results.add(file);
507+
assertEquals(false, cancelled);
508+
if (cancelFileName.equals(file.getName())) {
509+
cancelled = true;
510+
}
511+
}
512+
513+
/** Handles Cancelled. */
514+
protected boolean handleIsCancelled(File file, int depth, Collection results) throws IOException {
515+
return cancelled;
516+
}
517+
518+
/** Handles Cancel. */
519+
protected void handleCancelled(File startDirectory, Collection results,
520+
CancelException cancel) throws IOException {
521+
if (!suppressCancel) {
522+
super.handleCancelled(startDirectory, results, cancel);
523+
}
524+
}
525+
}
526+
427527
}

0 commit comments

Comments
 (0)