1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package org.apache.commons.io;
18 
19 import java.io.File;
20 import java.io.FileFilter;
21 import java.io.IOException;
22 import java.util.Collection;
23 
24 import org.apache.commons.io.filefilter.FileFilterUtils;
25 import org.apache.commons.io.filefilter.IOFileFilter;
26 import org.apache.commons.io.filefilter.TrueFileFilter;
27 
28 /**
29  * Abstract class that walks through a directory hierarchy and provides
30  * subclasses with convenient hooks to add specific behaviour.
31  * <p>
32  * This class operates with a {@link FileFilter} and maximum depth to
33  * limit the files and direcories visited.
34  * Commons IO supplies many common filter implementations in the
35  * <a href="filefilter/package-summary.html"> filefilter</a> package.
36  * <p>
37  * The following sections describe:
38  *   <ul>
39  *      <li><a href="#example">1. Example Implementation</a> - example
40  *          <code>FileCleaner</code> implementation.</li>
41  *      <li><a href="#filter">2. Filter Example</a> - using
42  *          {@link FileFilter}(s) with <code>DirectoryWalker</code>.</li>
43  *      <li><a href="#cancel">3. Cancellation</a> - how to implement cancellation
44  *          behaviour.</li>
45  *   </ul>
46  *
47  * <a name="example"></a>
48  * <h3>1. Example Implementation</h3>
49  *
50  * There are many possible extensions, for example, to delete all
51  * files and '.svn' directories, and return a list of deleted files:
52  * <pre>
53  *  public class FileCleaner extends DirectoryWalker {
54  *
55  *    public FileCleaner() {
56  *      super();
57  *    }
58  *
59  *    public List clean(File startDirectory) {
60  *      List results = new ArrayList();
61  *      walk(startDirectory, results);
62  *      return results;
63  *    }
64  *
65  *    protected boolean handleDirectory(File directory, int depth, Collection results) {
66  *      // delete svn directories and then skip
67  *      if (".svn".equals(directory.getName())) {
68  *        directory.delete();
69  *        return false;
70  *      } else {
71  *        return true;
72  *      }
73  *
74  *    }
75  *
76  *    protected void handleFile(File file, int depth, Collection results) {
77  *      // delete file and add to list of deleted
78  *      file.delete();
79  *      results.add(file);
80  *    }
81  *  }
82  * </pre>
83  *
84  * <a name="filter"></a>
85  * <h3>2. Filter Example</h3>
86  *
87  * Choosing which directories and files to process can be a key aspect
88  * of using this class. This information can be setup in three ways,
89  * via three different constructors.
90  * <p>
91  * The first option is to visit all directories and files.
92  * This is achieved via the no-args constructor.
93  * <p>
94  * The second constructor option is to supply a single {@link FileFilter}
95  * that describes the files and directories to visit. Care must be taken
96  * with this option as the same filter is used for both directories
97  * and files.
98  * <p>
99  * 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 Commons IO 1.3
249  * @version $Revision: 424748 $
250  */
251 public abstract class DirectoryWalker {
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      */
DirectoryWalker()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      */
DirectoryWalker(FileFilter filter, int depthLimit)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      */
DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, int depthLimit)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.orFileFilter(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      */
walk(File startDirectory, Collection results)329     protected final void walk(File startDirectory, Collection 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      */
walk(File directory, int depth, Collection results)350     private void walk(File directory, int depth, Collection 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                 if (childFiles == null) {
359                     handleRestricted(directory, childDepth, results);
360                 } else {
361                     for (int i = 0; i < childFiles.length; i++) {
362                         File childFile = childFiles[i];
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      */
checkIfCancelled(File file, int depth, Collection results)393     protected final void checkIfCancelled(File file, int depth, Collection 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      */
handleIsCancelled( File file, int depth, Collection results)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      */
handleCancelled(File startDirectory, Collection results, CancelException cancel)453     protected void handleCancelled(File startDirectory, Collection 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      */
handleStart(File startDirectory, Collection results)469     protected void handleStart(File startDirectory, Collection 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      */
handleDirectory(File directory, int depth, Collection results)488     protected boolean handleDirectory(File directory, int depth, Collection 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      */
handleDirectoryStart(File directory, int depth, Collection results)503     protected void handleDirectoryStart(File directory, int depth, Collection results) throws IOException {
504         // do nothing - overridable by subclass
505     }
506 
507     /**
508      * Overridable callback method invoked for each (non-directory) file.
509      * <p>
510      * This implementation does nothing.
511      *
512      * @param file  the current file being processed
513      * @param depth  the current directory level (starting directory = 0)
514      * @param results  the collection of result objects, may be updated
515      * @throws IOException if an I/O Error occurs
516      */
handleFile(File file, int depth, Collection results)517     protected void handleFile(File file, int depth, Collection results) throws IOException {
518         // do nothing - overridable by subclass
519     }
520 
521     /**
522      * Overridable callback method invoked for each restricted directory.
523      * <p>
524      * This implementation does nothing.
525      *
526      * @param directory  the restricted directory
527      * @param depth  the current directory level (starting directory = 0)
528      * @param results  the collection of result objects, may be updated
529      * @throws IOException if an I/O Error occurs
530      */
handleRestricted(File directory, int depth, Collection results)531     protected void handleRestricted(File directory, int depth, Collection results) throws IOException  {
532         // do nothing - overridable by subclass
533     }
534 
535     /**
536      * Overridable callback method invoked at the end of processing each directory.
537      * <p>
538      * This implementation does nothing.
539      *
540      * @param directory  the directory being processed
541      * @param depth  the current directory level (starting directory = 0)
542      * @param results  the collection of result objects, may be updated
543      * @throws IOException if an I/O Error occurs
544      */
handleDirectoryEnd(File directory, int depth, Collection results)545     protected void handleDirectoryEnd(File directory, int depth, Collection results) throws IOException {
546         // do nothing - overridable by subclass
547     }
548 
549     /**
550      * Overridable callback method invoked at the end of processing.
551      * <p>
552      * This implementation does nothing.
553      *
554      * @param results  the collection of result objects, may be updated
555      * @throws IOException if an I/O Error occurs
556      */
handleEnd(Collection results)557     protected void handleEnd(Collection results) throws IOException {
558         // do nothing - overridable by subclass
559     }
560 
561     //-----------------------------------------------------------------------
562     /**
563      * CancelException is thrown in DirectoryWalker to cancel the current
564      * processing.
565      */
566     public static class CancelException extends IOException {
567 
568         /** Serialization id. */
569         private static final long serialVersionUID = 1347339620135041008L;
570 
571         /** The file being processed when the exception was thrown. */
572         private File file;
573         /** The file depth when the exception was thrown. */
574         private int depth = -1;
575 
576         /**
577          * Constructs a <code>CancelException</code> with
578          * the file and depth when cancellation occurred.
579          *
580          * @param file  the file when the operation was cancelled, may be null
581          * @param depth  the depth when the operation was cancelled, may be null
582          */
CancelException(File file, int depth)583         public CancelException(File file, int depth) {
584             this("Operation Cancelled", file, depth);
585         }
586 
587         /**
588          * Constructs a <code>CancelException</code> with
589          * an appropriate message and the file and depth when
590          * cancellation occurred.
591          *
592          * @param message  the detail message
593          * @param file  the file when the operation was cancelled
594          * @param depth  the depth when the operation was cancelled
595          */
CancelException(String message, File file, int depth)596         public CancelException(String message, File file, int depth) {
597             super(message);
598             this.file = file;
599             this.depth = depth;
600         }
601 
602         /**
603          * Return the file when the operation was cancelled.
604          *
605          * @return the file when the operation was cancelled
606          */
getFile()607         public File getFile() {
608             return file;
609         }
610 
611         /**
612          * Return the depth when the operation was cancelled.
613          *
614          * @return the depth when the operation was cancelled
615          */
getDepth()616         public int getDepth() {
617             return depth;
618         }
619     }
620 }
621