001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.io;
018
019 import java.io.File;
020 import java.io.FileFilter;
021 import java.io.IOException;
022 import java.util.Collection;
023
024 import org.apache.commons.io.filefilter.FileFilterUtils;
025 import org.apache.commons.io.filefilter.IOFileFilter;
026 import org.apache.commons.io.filefilter.TrueFileFilter;
027
028 /**
029 * Abstract class that walks through a directory hierarchy and provides
030 * subclasses with convenient hooks to add specific behaviour.
031 * <p>
032 * This class operates with a {@link FileFilter} and maximum depth to
033 * limit the files and direcories visited.
034 * Commons IO supplies many common filter implementations in the
035 * <a href="filefilter/package-summary.html"> filefilter</a> package.
036 * <p>
037 * The following sections describe:
038 * <ul>
039 * <li><a href="#example">1. Example Implementation</a> - example
040 * <code>FileCleaner</code> implementation.</li>
041 * <li><a href="#filter">2. Filter Example</a> - using
042 * {@link FileFilter}(s) with <code>DirectoryWalker</code>.</li>
043 * <li><a href="#cancel">3. Cancellation</a> - how to implement cancellation
044 * behaviour.</li>
045 * </ul>
046 *
047 * <a name="example"></a>
048 * <h3>1. Example Implementation</h3>
049 *
050 * There are many possible extensions, for example, to delete all
051 * files and '.svn' directories, and return a list of deleted files:
052 * <pre>
053 * public class FileCleaner extends DirectoryWalker {
054 *
055 * public FileCleaner() {
056 * super();
057 * }
058 *
059 * public List clean(File startDirectory) {
060 * List results = new ArrayList();
061 * walk(startDirectory, results);
062 * return results;
063 * }
064 *
065 * protected boolean handleDirectory(File directory, int depth, Collection results) {
066 * // delete svn directories and then skip
067 * if (".svn".equals(directory.getName())) {
068 * directory.delete();
069 * return false;
070 * } else {
071 * return true;
072 * }
073 *
074 * }
075 *
076 * protected void handleFile(File file, int depth, Collection results) {
077 * // delete file and add to list of deleted
078 * file.delete();
079 * results.add(file);
080 * }
081 * }
082 * </pre>
083 *
084 * <a name="filter"></a>
085 * <h3>2. Filter Example</h3>
086 *
087 * Choosing which directories and files to process can be a key aspect
088 * of using this class. This information can be setup in three ways,
089 * via three different constructors.
090 * <p>
091 * The first option is to visit all directories and files.
092 * This is achieved via the no-args constructor.
093 * <p>
094 * The second constructor option is to supply a single {@link FileFilter}
095 * that describes the files and directories to visit. Care must be taken
096 * with this option as the same filter is used for both directories
097 * and files.
098 * <p>
099 * For example, if you wanted all directories which are not hidden
100 * and files which end in ".txt":
101 * <pre>
102 * public class FooDirectoryWalker extends DirectoryWalker {
103 * public FooDirectoryWalker(FileFilter filter) {
104 * super(filter, -1);
105 * }
106 * }
107 *
108 * // Build up the filters and create the walker
109 * // Create a filter for Non-hidden directories
110 * IOFileFilter fooDirFilter =
111 * FileFilterUtils.andFileFilter(FileFilterUtils.directoryFileFilter,
112 * HiddenFileFilter.VISIBLE);
113 *
114 * // Create a filter for Files ending in ".txt"
115 * IOFileFilter fooFileFilter =
116 * FileFilterUtils.andFileFilter(FileFilterUtils.fileFileFilter,
117 * FileFilterUtils.suffixFileFilter(".txt"));
118 *
119 * // Combine the directory and file filters using an OR condition
120 * java.io.FileFilter fooFilter =
121 * FileFilterUtils.orFileFilter(fooDirFilter, fooFileFilter);
122 *
123 * // Use the filter to construct a DirectoryWalker implementation
124 * FooDirectoryWalker walker = new FooDirectoryWalker(fooFilter);
125 * </pre>
126 * <p>
127 * The third constructor option is to specify separate filters, one for
128 * directories and one for files. These are combined internally to form
129 * the correct <code>FileFilter</code>, something which is very easy to
130 * get wrong when attempted manually, particularly when trying to
131 * express constructs like 'any file in directories named docs'.
132 * <p>
133 * For example, if you wanted all directories which are not hidden
134 * and files which end in ".txt":
135 * <pre>
136 * public class FooDirectoryWalker extends DirectoryWalker {
137 * public FooDirectoryWalker(IOFileFilter dirFilter, IOFileFilter fileFilter) {
138 * super(dirFilter, fileFilter, -1);
139 * }
140 * }
141 *
142 * // Use the filters to construct the walker
143 * FooDirectoryWalker walker = new FooDirectoryWalker(
144 * HiddenFileFilter.VISIBLE,
145 * FileFilterUtils.suffixFileFilter(".txt"),
146 * );
147 * </pre>
148 * This is much simpler than the previous example, and is why it is the preferred
149 * option for filtering.
150 *
151 * <a name="cancel"></a>
152 * <h3>3. Cancellation</h3>
153 *
154 * The DirectoryWalker contains some of the logic required for cancel processing.
155 * Subclasses must complete the implementation.
156 * <p>
157 * What <code>DirectoryWalker</code> does provide for cancellation is:
158 * <ul>
159 * <li>{@link CancelException} which can be thrown in any of the
160 * <i>lifecycle</i> methods to stop processing.</li>
161 * <li>The <code>walk()</code> method traps thrown {@link CancelException}
162 * and calls the <code>handleCancelled()</code> method, providing
163 * a place for custom cancel processing.</li>
164 * </ul>
165 * <p>
166 * Implementations need to provide:
167 * <ul>
168 * <li>The decision logic on whether to cancel processing or not.</li>
169 * <li>Constructing and throwing a {@link CancelException}.</li>
170 * <li>Custom cancel processing in the <code>handleCancelled()</code> method.
171 * </ul>
172 * <p>
173 * Two possible scenarios are envisaged for cancellation:
174 * <ul>
175 * <li><a href="#external">3.1 External / Mult-threaded</a> - cancellation being
176 * decided/initiated by an external process.</li>
177 * <li><a href="#internal">3.2 Internal</a> - cancellation being decided/initiated
178 * from within a DirectoryWalker implementation.</li>
179 * </ul>
180 * <p>
181 * The following sections provide example implementations for these two different
182 * scenarios.
183 *
184 * <a name="external"></a>
185 * <h4>3.1 External / Multi-threaded</h4>
186 *
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.
196 *
197 * <pre>
198 * public class FooDirectoryWalker extends DirectoryWalker {
199 *
200 * private volatile boolean cancelled = false;
201 *
202 * public void cancel() {
203 * cancelled = true;
204 * }
205 *
206 * private void handleIsCancelled(File file, int depth, Collection results) {
207 * return cancelled;
208 * }
209 *
210 * protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
211 * // implement processing required when a cancellation occurs
212 * }
213 * }
214 * </pre>
215 *
216 * <a name="internal"></a>
217 * <h4>3.2 Internal</h4>
218 *
219 * This shows an example of how internal cancellation processing could be implemented.
220 * <b>Note</b> the decision logic and throwing a {@link CancelException} could be implemented
221 * in any of the <i>lifecycle</i> methods.
222 *
223 * <pre>
224 * public class BarDirectoryWalker extends DirectoryWalker {
225 *
226 * protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException {
227 * // cancel if hidden directory
228 * if (directory.isHidden()) {
229 * throw new CancelException(file, depth);
230 * }
231 * return true;
232 * }
233 *
234 * protected void handleFile(File file, int depth, Collection results) throws IOException {
235 * // cancel if read-only file
236 * if (!file.canWrite()) {
237 * throw new CancelException(file, depth);
238 * }
239 * results.add(file);
240 * }
241 *
242 * protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
243 * // implement processing required when a cancellation occurs
244 * }
245 * }
246 * </pre>
247 *
248 * @since 1.3
249 * @version $Id: DirectoryWalker.java 1304052 2012-03-22 20:55:29Z ggregory $
250 */
251 public abstract class DirectoryWalker<T> {
252
253 /**
254 * The file filter to use to filter files and directories.
255 */
256 private final FileFilter filter;
257 /**
258 * The limit on the directory depth to walk.
259 */
260 private final int depthLimit;
261
262 /**
263 * Construct an instance with no filtering and unlimited <i>depth</i>.
264 */
265 protected DirectoryWalker() {
266 this(null, -1);
267 }
268
269 /**
270 * Construct an instance with a filter and limit the <i>depth</i> navigated to.
271 * <p>
272 * The filter controls which files and directories will be navigated to as
273 * part of the walk. The {@link FileFilterUtils} class is useful for combining
274 * various filters together. A <code>null</code> filter means that no
275 * filtering should occur and all files and directories will be visited.
276 *
277 * @param filter the filter to apply, null means visit all files
278 * @param depthLimit controls how <i>deep</i> the hierarchy is
279 * navigated to (less than 0 means unlimited)
280 */
281 protected DirectoryWalker(FileFilter filter, int depthLimit) {
282 this.filter = filter;
283 this.depthLimit = depthLimit;
284 }
285
286 /**
287 * Construct an instance with a directory and a file filter and an optional
288 * limit on the <i>depth</i> navigated to.
289 * <p>
290 * The filters control which files and directories will be navigated to as part
291 * of the walk. This constructor uses {@link FileFilterUtils#makeDirectoryOnly(IOFileFilter)}
292 * and {@link FileFilterUtils#makeFileOnly(IOFileFilter)} internally to combine the filters.
293 * A <code>null</code> filter means that no filtering should occur.
294 *
295 * @param directoryFilter the filter to apply to directories, null means visit all directories
296 * @param fileFilter the filter to apply to files, null means visit all files
297 * @param depthLimit controls how <i>deep</i> the hierarchy is
298 * navigated to (less than 0 means unlimited)
299 */
300 protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, int depthLimit) {
301 if (directoryFilter == null && fileFilter == null) {
302 this.filter = null;
303 } else {
304 directoryFilter = directoryFilter != null ? directoryFilter : TrueFileFilter.TRUE;
305 fileFilter = fileFilter != null ? fileFilter : TrueFileFilter.TRUE;
306 directoryFilter = FileFilterUtils.makeDirectoryOnly(directoryFilter);
307 fileFilter = FileFilterUtils.makeFileOnly(fileFilter);
308 this.filter = FileFilterUtils.or(directoryFilter, fileFilter);
309 }
310 this.depthLimit = depthLimit;
311 }
312
313 //-----------------------------------------------------------------------
314 /**
315 * Internal method that walks the directory hierarchy in a depth-first manner.
316 * <p>
317 * Users of this class do not need to call this method. This method will
318 * be called automatically by another (public) method on the specific subclass.
319 * <p>
320 * Writers of subclasses should call this method to start the directory walk.
321 * Once called, this method will emit events as it walks the hierarchy.
322 * The event methods have the prefix <code>handle</code>.
323 *
324 * @param startDirectory the directory to start from, not null
325 * @param results the collection of result objects, may be updated
326 * @throws NullPointerException if the start directory is null
327 * @throws IOException if an I/O Error occurs
328 */
329 protected final void walk(File startDirectory, Collection<T> results) throws IOException {
330 if (startDirectory == null) {
331 throw new NullPointerException("Start Directory is null");
332 }
333 try {
334 handleStart(startDirectory, results);
335 walk(startDirectory, 0, results);
336 handleEnd(results);
337 } catch(CancelException cancel) {
338 handleCancelled(startDirectory, results, cancel);
339 }
340 }
341
342 /**
343 * Main recursive method to examine the directory hierarchy.
344 *
345 * @param directory the directory to examine, not null
346 * @param depth the directory level (starting directory = 0)
347 * @param results the collection of result objects, may be updated
348 * @throws IOException if an I/O Error occurs
349 */
350 private void walk(File directory, int depth, Collection<T> results) throws IOException {
351 checkIfCancelled(directory, depth, results);
352 if (handleDirectory(directory, depth, results)) {
353 handleDirectoryStart(directory, depth, results);
354 int childDepth = depth + 1;
355 if (depthLimit < 0 || childDepth <= depthLimit) {
356 checkIfCancelled(directory, depth, results);
357 File[] childFiles = filter == null ? directory.listFiles() : directory.listFiles(filter);
358 childFiles = filterDirectoryContents(directory, depth, childFiles);
359 if (childFiles == null) {
360 handleRestricted(directory, childDepth, results);
361 } else {
362 for (File childFile : childFiles) {
363 if (childFile.isDirectory()) {
364 walk(childFile, childDepth, results);
365 } else {
366 checkIfCancelled(childFile, childDepth, results);
367 handleFile(childFile, childDepth, results);
368 checkIfCancelled(childFile, childDepth, results);
369 }
370 }
371 }
372 }
373 handleDirectoryEnd(directory, depth, results);
374 }
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 * @throws IOException if an I/O Error occurs
392 */
393 protected final void checkIfCancelled(File file, int depth, Collection<T> results) throws IOException {
394 if (handleIsCancelled(file, depth, results)) {
395 throw new CancelException(file, depth);
396 }
397 }
398
399 /**
400 * Overridable callback method invoked to determine if the entire walk
401 * operation should be immediately cancelled.
402 * <p>
403 * This method should be implemented by those subclasses that want to
404 * provide a public <code>cancel()</code> method available from another
405 * thread. The design pattern for the subclass should be as follows:
406 * <pre>
407 * public class FooDirectoryWalker extends DirectoryWalker {
408 * private volatile boolean cancelled = false;
409 *
410 * public void cancel() {
411 * cancelled = true;
412 * }
413 * private void handleIsCancelled(File file, int depth, Collection results) {
414 * return cancelled;
415 * }
416 * protected void handleCancelled(File startDirectory,
417 * Collection results, CancelException cancel) {
418 * // implement processing required when a cancellation occurs
419 * }
420 * }
421 * </pre>
422 * <p>
423 * If this method returns true, then the directory walk is immediately
424 * cancelled. The next callback method will be {@link #handleCancelled}.
425 * <p>
426 * This implementation returns false.
427 *
428 * @param file the file or directory being processed
429 * @param depth the current directory level (starting directory = 0)
430 * @param results the collection of result objects, may be updated
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<T> 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<T> results,
454 CancelException cancel) throws IOException {
455 // re-throw exception - overridable by subclass
456 throw cancel;
457 }
458
459 //-----------------------------------------------------------------------
460 /**
461 * Overridable callback method invoked at the start of processing.
462 * <p>
463 * This implementation does nothing.
464 *
465 * @param startDirectory the directory to start from
466 * @param results the collection of result objects, may be updated
467 * @throws IOException if an I/O Error occurs
468 */
469 protected void handleStart(File startDirectory, Collection<T> results) throws IOException {
470 // do nothing - overridable by subclass
471 }
472
473 /**
474 * Overridable callback method invoked to determine if a directory should be processed.
475 * <p>
476 * This method returns a boolean to indicate if the directory should be examined or not.
477 * If you return false, the entire directory and any subdirectories will be skipped.
478 * Note that this functionality is in addition to the filtering by file filter.
479 * <p>
480 * This implementation does nothing and returns true.
481 *
482 * @param directory the current directory being processed
483 * @param depth the current directory level (starting directory = 0)
484 * @param results the collection of result objects, may be updated
485 * @return true to process this directory, false to skip this directory
486 * @throws IOException if an I/O Error occurs
487 */
488 protected boolean handleDirectory(File directory, int depth, Collection<T> results) throws IOException {
489 // do nothing - overridable by subclass
490 return true; // process directory
491 }
492
493 /**
494 * Overridable callback method invoked at the start of processing each directory.
495 * <p>
496 * This implementation does nothing.
497 *
498 * @param directory the current directory being processed
499 * @param depth the current directory level (starting directory = 0)
500 * @param results the collection of result objects, may be updated
501 * @throws IOException if an I/O Error occurs
502 */
503 protected void handleDirectoryStart(File directory, int depth, Collection<T> results) throws IOException {
504 // do nothing - overridable by subclass
505 }
506
507 /**
508 * Overridable callback method invoked with the contents of each directory.
509 * <p>
510 * This implementation returns the files unchanged
511 *
512 * @param directory the current directory being processed
513 * @param depth the current directory level (starting directory = 0)
514 * @param files the files (possibly filtered) in the directory
515 * @return the filtered list of files
516 * @throws IOException if an I/O Error occurs
517 * @since 2.0
518 */
519 protected File[] filterDirectoryContents(File directory, int depth, File[] files) throws IOException {
520 return files;
521 }
522
523 /**
524 * Overridable callback method invoked for each (non-directory) file.
525 * <p>
526 * This implementation does nothing.
527 *
528 * @param file the current file being processed
529 * @param depth the current directory level (starting directory = 0)
530 * @param results the collection of result objects, may be updated
531 * @throws IOException if an I/O Error occurs
532 */
533 protected void handleFile(File file, int depth, Collection<T> results) throws IOException {
534 // do nothing - overridable by subclass
535 }
536
537 /**
538 * Overridable callback method invoked for each restricted directory.
539 * <p>
540 * This implementation does nothing.
541 *
542 * @param directory the restricted directory
543 * @param depth the current directory level (starting directory = 0)
544 * @param results the collection of result objects, may be updated
545 * @throws IOException if an I/O Error occurs
546 */
547 protected void handleRestricted(File directory, int depth, Collection<T> results) throws IOException {
548 // do nothing - overridable by subclass
549 }
550
551 /**
552 * Overridable callback method invoked at the end of processing each directory.
553 * <p>
554 * This implementation does nothing.
555 *
556 * @param directory the directory being processed
557 * @param depth the current directory level (starting directory = 0)
558 * @param results the collection of result objects, may be updated
559 * @throws IOException if an I/O Error occurs
560 */
561 protected void handleDirectoryEnd(File directory, int depth, Collection<T> results) throws IOException {
562 // do nothing - overridable by subclass
563 }
564
565 /**
566 * Overridable callback method invoked at the end of processing.
567 * <p>
568 * This implementation does nothing.
569 *
570 * @param results the collection of result objects, may be updated
571 * @throws IOException if an I/O Error occurs
572 */
573 protected void handleEnd(Collection<T> results) throws IOException {
574 // do nothing - overridable by subclass
575 }
576
577 //-----------------------------------------------------------------------
578 /**
579 * CancelException is thrown in DirectoryWalker to cancel the current
580 * processing.
581 */
582 public static class CancelException extends IOException {
583
584 /** Serialization id. */
585 private static final long serialVersionUID = 1347339620135041008L;
586
587 /** The file being processed when the exception was thrown. */
588 private final File file;
589 /** The file depth when the exception was thrown. */
590 private final int depth;
591
592 /**
593 * Constructs a <code>CancelException</code> with
594 * the file and depth when cancellation occurred.
595 *
596 * @param file the file when the operation was cancelled, may be null
597 * @param depth the depth when the operation was cancelled, may be null
598 */
599 public CancelException(File file, int depth) {
600 this("Operation Cancelled", file, depth);
601 }
602
603 /**
604 * Constructs a <code>CancelException</code> with
605 * an appropriate message and the file and depth when
606 * cancellation occurred.
607 *
608 * @param message the detail message
609 * @param file the file when the operation was cancelled
610 * @param depth the depth when the operation was cancelled
611 */
612 public CancelException(String message, File file, int depth) {
613 super(message);
614 this.file = file;
615 this.depth = depth;
616 }
617
618 /**
619 * Return the file when the operation was cancelled.
620 *
621 * @return the file when the operation was cancelled
622 */
623 public File getFile() {
624 return file;
625 }
626
627 /**
628 * Return the depth when the operation was cancelled.
629 *
630 * @return the depth when the operation was cancelled
631 */
632 public int getDepth() {
633 return depth;
634 }
635 }
636 }