View Javadoc
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.nio.file.Files;
23  import java.util.Collection;
24  import java.util.Objects;
25  
26  import org.apache.commons.io.file.PathUtils;
27  import org.apache.commons.io.filefilter.FileFilterUtils;
28  import org.apache.commons.io.filefilter.IOFileFilter;
29  import org.apache.commons.io.filefilter.TrueFileFilter;
30  
31  /**
32   * Abstract class that walks through a directory hierarchy and provides subclasses with convenient hooks to add specific
33   * behavior.
34   * <p>
35   * This class operates with a {@link FileFilter} and maximum depth to limit the files and directories visited. Commons
36   * IO supplies many common filter implementations in the <a href="filefilter/package-summary.html"> filefilter</a>
37   * package.
38   * </p>
39   * <p>
40   * The following sections describe:
41   * </p>
42   * <ul>
43   * <li><a href="#example">1. Example Implementation</a> - example {@link FileCleaner} implementation.</li>
44   * <li><a href="#filter">2. Filter Example</a> - using {@link FileFilter}(s) with {@link DirectoryWalker}.</li>
45   * <li><a href="#cancel">3. Cancellation</a> - how to implement cancellation behavior.</li>
46   * </ul>
47   *
48   * <h2 id="example">1. Example Implementation</h2>
49   *
50   * There are many possible extensions, for example, to delete all files and '.svn' directories, and return a list of
51   * deleted files:
52   *
53   * <pre>
54   * public class FileCleaner extends DirectoryWalker {
55   *
56   *     public FileCleaner() {
57   *         super();
58   *     }
59   *
60   *     public List clean(File startDirectory) {
61   *         List results = new ArrayList();
62   *         walk(startDirectory, results);
63   *         return results;
64   *     }
65   *
66   *     protected boolean handleDirectory(File directory, int depth, Collection results) {
67   *         // delete svn directories and then skip
68   *         if (".svn".equals(directory.getName())) {
69   *             directory.delete();
70   *             return false;
71   *         } else {
72   *             return true;
73   *         }
74   *
75   *     }
76   *
77   *     protected void handleFile(File file, int depth, Collection results) {
78   *         // delete file and add to list of deleted
79   *         file.delete();
80   *         results.add(file);
81   *     }
82   * }
83   * </pre>
84   *
85   * <h2 id="filter">2. Filter Example</h2>
86   *
87   * <p>
88   * Choosing which directories and files to process can be a key aspect of using this class. This information can be
89   * setup in three ways, via three different constructors.
90   * </p>
91   * <p>
92   * The first option is to visit all directories and files. This is achieved via the no-args constructor.
93   * </p>
94   * <p>
95   * The second constructor option is to supply a single {@link FileFilter} that describes the files and directories to
96   * visit. Care must be taken with this option as the same filter is used for both directories and files.
97   * </p>
98   * <p>
99   * For example, if you wanted all directories which are not hidden and files which end in ".txt":
100  * </p>
101  *
102  * <pre>
103  * public class FooDirectoryWalker extends DirectoryWalker {
104  *     public FooDirectoryWalker(FileFilter filter) {
105  *         super(filter, -1);
106  *     }
107  * }
108  *
109  * // Build up the filters and create the walker
110  * // Create a filter for Non-hidden directories
111  * IOFileFilter fooDirFilter = FileFilterUtils.andFileFilter(FileFilterUtils.directoryFileFilter,
112  *     HiddenFileFilter.VISIBLE);
113  *
114  * // Create a filter for Files ending in ".txt"
115  * IOFileFilter fooFileFilter = FileFilterUtils.andFileFilter(FileFilterUtils.fileFileFilter,
116  *     FileFilterUtils.suffixFileFilter(".txt"));
117  *
118  * // Combine the directory and file filters using an OR condition
119  * java.io.FileFilter fooFilter = FileFilterUtils.orFileFilter(fooDirFilter, fooFileFilter);
120  *
121  * // Use the filter to construct a DirectoryWalker implementation
122  * FooDirectoryWalker walker = new FooDirectoryWalker(fooFilter);
123  * </pre>
124  * <p>
125  * The third constructor option is to specify separate filters, one for directories and one for files. These are
126  * combined internally to form the correct {@link FileFilter}, something which is very easy to get wrong when
127  * attempted manually, particularly when trying to express constructs like 'any file in directories named docs'.
128  * </p>
129  * <p>
130  * For example, if you wanted all directories which are not hidden and files which end in ".txt":
131  * </p>
132  *
133  * <pre>
134  *  public class FooDirectoryWalker extends DirectoryWalker {
135  *    public FooDirectoryWalker(IOFileFilter dirFilter, IOFileFilter fileFilter) {
136  *      super(dirFilter, fileFilter, -1);
137  *    }
138  *  }
139  *
140  *  // Use the filters to construct the walker
141  *  FooDirectoryWalker walker = new FooDirectoryWalker(
142  *    HiddenFileFilter.VISIBLE,
143  *    FileFilterUtils.suffixFileFilter(".txt"),
144  *  );
145  * </pre>
146  * <p>
147  * This is much simpler than the previous example, and is why it is the preferred option for filtering.
148  * </p>
149  *
150  * <h2 id="cancel">3. Cancellation</h2>
151  *
152  * <p>
153  * The DirectoryWalker contains some of the logic required for cancel processing. Subclasses must complete the
154  * implementation.
155  * </p>
156  * <p>
157  * What {@link DirectoryWalker} does provide for cancellation is:
158  * </p>
159  * <ul>
160  * <li>{@link CancelException} which can be thrown in any of the <i>lifecycle</i> methods to stop processing.</li>
161  * <li>The {@code walk()} method traps thrown {@link CancelException} and calls the {@code handleCancelled()}
162  * method, providing a place for custom cancel processing.</li>
163  * </ul>
164  * <p>
165  * Implementations need to provide:
166  * </p>
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()} method.
171  * </ul>
172  * <p>
173  * Two possible scenarios are envisaged for cancellation:
174  * </p>
175  * <ul>
176  * <li><a href="#external">3.1 External / Multi-threaded</a> - cancellation being decided/initiated by an external
177  * process.</li>
178  * <li><a href="#internal">3.2 Internal</a> - cancellation being decided/initiated from within a DirectoryWalker
179  * implementation.</li>
180  * </ul>
181  * <p>
182  * The following sections provide example implementations for these two different scenarios.
183  * </p>
184  *
185  * <h3 id="external">3.1 External / Multi-threaded</h3>
186  *
187  * <p>
188  * This example provides a public {@code cancel()} method that can be called by another thread to stop the
189  * processing. A typical example use-case is a cancel button on a GUI. Calling this method sets a
190  * <a href='https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#d5e12277'>(@code volatile}</a>
191  * flag to ensure it works properly in a multi-threaded environment.
192  * The flag is returned by the {@code handleIsCancelled()} method, which causes the walk to stop
193  * immediately. The {@code handleCancelled()} method will be the next, and last, callback method received once cancellation has occurred.
194  * </p>
195  *
196  * <pre>
197  * public class FooDirectoryWalker extends DirectoryWalker {
198  *
199  *     private volatile boolean cancelled = false;
200  *
201  *     public void cancel() {
202  *         cancelled = true;
203  *     }
204  *
205  *     protected boolean handleIsCancelled(File file, int depth, Collection results) {
206  *         return cancelled;
207  *     }
208  *
209  *     protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
210  *         // implement processing required when a cancellation occurs
211  *     }
212  * }
213  * </pre>
214  *
215  * <h3 id="internal">3.2 Internal</h3>
216  *
217  * <p>
218  * This shows an example of how internal cancellation processing could be implemented. <b>Note</b> the decision logic
219  * and throwing a {@link CancelException} could be implemented in any of the <i>lifecycle</i> methods.
220  * </p>
221  *
222  * <pre>
223  * public class BarDirectoryWalker extends DirectoryWalker {
224  *
225  *     protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException {
226  *         // cancel if hidden directory
227  *         if (directory.isHidden()) {
228  *             throw new CancelException(file, depth);
229  *         }
230  *         return true;
231  *     }
232  *
233  *     protected void handleFile(File file, int depth, Collection results) throws IOException {
234  *         // cancel if read-only file
235  *         if (!file.canWrite()) {
236  *             throw new CancelException(file, depth);
237  *         }
238  *         results.add(file);
239  *     }
240  *
241  *     protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
242  *         // implement processing required when a cancellation occurs
243  *     }
244  * }
245  * </pre>
246  *
247  * @param <T> The result type, like {@link File}.
248  * @since 1.3
249  * @deprecated Apache Commons IO no longer uses this class. Instead, use
250  *             {@link PathUtils#walk(java.nio.file.Path, org.apache.commons.io.file.PathFilter, int, boolean, java.nio.file.FileVisitOption...)}
251  *             or {@link Files#walkFileTree(java.nio.file.Path, java.util.Set, int, java.nio.file.FileVisitor)}, and
252  *             friends.
253  */
254 @Deprecated
255 public abstract class DirectoryWalker<T> {
256 
257     /**
258      * CancelException is thrown in DirectoryWalker to cancel the current
259      * processing.
260      */
261     public static class CancelException extends IOException {
262 
263         /** Serialization id. */
264         private static final long serialVersionUID = 1347339620135041008L;
265 
266         /** The file being processed when the exception was thrown. */
267         private final File file;
268         /** The file depth when the exception was thrown. */
269         private final int depth;
270 
271         /**
272          * Constructs a {@link CancelException} with
273          * the file and depth when cancellation occurred.
274          *
275          * @param file  the file when the operation was cancelled, may be null
276          * @param depth  the depth when the operation was cancelled, may be null
277          */
278         public CancelException(final File file, final int depth) {
279             this("Operation Cancelled", file, depth);
280         }
281 
282         /**
283          * Constructs a {@link CancelException} with
284          * an appropriate message and the file and depth when
285          * cancellation occurred.
286          *
287          * @param message  the detail message
288          * @param file  the file when the operation was cancelled
289          * @param depth  the depth when the operation was cancelled
290          */
291         public CancelException(final String message, final File file, final int depth) {
292             super(message);
293             this.file = file;
294             this.depth = depth;
295         }
296 
297         /**
298          * Returns the depth when the operation was cancelled.
299          *
300          * @return the depth when the operation was cancelled
301          */
302         public int getDepth() {
303             return depth;
304         }
305 
306         /**
307          * Returns the file when the operation was cancelled.
308          *
309          * @return the file when the operation was cancelled
310          */
311         public File getFile() {
312             return file;
313         }
314     }
315     /**
316      * The file filter to use to filter files and directories.
317      */
318     private final FileFilter filter;
319 
320     /**
321      * The limit on the directory depth to walk.
322      */
323     private final int depthLimit;
324 
325     /**
326      * Constructs an instance with no filtering and unlimited <i>depth</i>.
327      */
328     protected DirectoryWalker() {
329         this(null, -1);
330     }
331 
332     /**
333      * Constructs an instance with a filter and limit the <i>depth</i> navigated to.
334      * <p>
335      * The filter controls which files and directories will be navigated to as
336      * part of the walk. The {@link FileFilterUtils} class is useful for combining
337      * various filters together. A {@code null} filter means that no
338      * filtering should occur and all files and directories will be visited.
339      * </p>
340      *
341      * @param filter  the filter to apply, null means visit all files
342      * @param depthLimit  controls how <i>deep</i> the hierarchy is
343      *  navigated to (less than 0 means unlimited)
344      */
345     protected DirectoryWalker(final FileFilter filter, final int depthLimit) {
346         this.filter = filter;
347         this.depthLimit = depthLimit;
348     }
349 
350     /**
351      * Constructs an instance with a directory and a file filter and an optional
352      * limit on the <i>depth</i> navigated to.
353      * <p>
354      * The filters control which files and directories will be navigated to as part
355      * of the walk. This constructor uses {@link FileFilterUtils#makeDirectoryOnly(IOFileFilter)}
356      * and {@link FileFilterUtils#makeFileOnly(IOFileFilter)} internally to combine the filters.
357      * A {@code null} filter means that no filtering should occur.
358      * </p>
359      *
360      * @param directoryFilter  the filter to apply to directories, null means visit all directories
361      * @param fileFilter  the filter to apply to files, null means visit all files
362      * @param depthLimit  controls how <i>deep</i> the hierarchy is
363      *  navigated to (less than 0 means unlimited)
364      */
365     protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, final int depthLimit) {
366         if (directoryFilter == null && fileFilter == null) {
367             this.filter = null;
368         } else {
369             directoryFilter = directoryFilter != null ? directoryFilter : TrueFileFilter.TRUE;
370             fileFilter = fileFilter != null ? fileFilter : TrueFileFilter.TRUE;
371             directoryFilter = FileFilterUtils.makeDirectoryOnly(directoryFilter);
372             fileFilter = FileFilterUtils.makeFileOnly(fileFilter);
373             this.filter = directoryFilter.or(fileFilter);
374         }
375         this.depthLimit = depthLimit;
376     }
377 
378     /**
379      * Checks whether the walk has been cancelled by calling {@link #handleIsCancelled},
380      * throwing a {@link CancelException} if it has.
381      * <p>
382      * Writers of subclasses should not normally call this method as it is called
383      * automatically by the walk of the tree. However, sometimes a single method,
384      * typically {@link #handleFile}, may take a long time to run. In that case,
385      * you may wish to check for cancellation by calling this method.
386      * </p>
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(final File file, final int depth, final Collection<T> results) throws
394             IOException {
395         if (handleIsCancelled(file, depth, results)) {
396             throw new CancelException(file, depth);
397         }
398     }
399 
400     /**
401      * Overridable callback method invoked with the contents of each directory.
402      * <p>
403      * This implementation returns the files unchanged
404      * </p>
405      *
406      * @param directory  the current directory being processed
407      * @param depth  the current directory level (starting directory = 0)
408      * @param files the files (possibly filtered) in the directory, may be {@code null}
409      * @return the filtered list of files
410      * @throws IOException if an I/O Error occurs
411      * @since 2.0
412      */
413     @SuppressWarnings("unused") // Possibly thrown from subclasses.
414     protected File[] filterDirectoryContents(final File directory, final int depth, final File... files) throws
415             IOException {
416         return files;
417     }
418 
419     /**
420      * Overridable callback method invoked when the operation is cancelled.
421      * The file being processed when the cancellation occurred can be
422      * obtained from the exception.
423      * <p>
424      * This implementation just re-throws the {@link CancelException}.
425      * </p>
426      *
427      * @param startDirectory  the directory that the walk started from
428      * @param results  the collection of result objects, may be updated
429      * @param cancel  the exception throw to cancel further processing
430      * containing details at the point of cancellation.
431      * @throws IOException if an I/O Error occurs
432      */
433     protected void handleCancelled(final File startDirectory, final Collection<T> results,
434                        final CancelException cancel) throws IOException {
435         // re-throw exception - overridable by subclass
436         throw cancel;
437     }
438 
439     /**
440      * Overridable callback method invoked to determine if a directory should be processed.
441      * <p>
442      * This method returns a boolean to indicate if the directory should be examined or not.
443      * If you return false, the entire directory and any subdirectories will be skipped.
444      * Note that this functionality is in addition to the filtering by file filter.
445      * </p>
446      * <p>
447      * This implementation does nothing and returns true.
448      * </p>
449      *
450      * @param directory  the current directory being processed
451      * @param depth  the current directory level (starting directory = 0)
452      * @param results  the collection of result objects, may be updated
453      * @return true to process this directory, false to skip this directory
454      * @throws IOException if an I/O Error occurs
455      */
456     @SuppressWarnings("unused") // Possibly thrown from subclasses.
457     protected boolean handleDirectory(final File directory, final int depth, final Collection<T> results) throws
458             IOException {
459         // do nothing - overridable by subclass
460         return true;  // process directory
461     }
462 
463     /**
464      * Overridable callback method invoked at the end of processing each directory.
465      * <p>
466      * This implementation does nothing.
467      * </p>
468      *
469      * @param directory  the directory being processed
470      * @param depth  the current directory level (starting directory = 0)
471      * @param results  the collection of result objects, may be updated
472      * @throws IOException if an I/O Error occurs
473      */
474     @SuppressWarnings("unused") // Possibly thrown from subclasses.
475     protected void handleDirectoryEnd(final File directory, final int depth, final Collection<T> results) throws
476             IOException {
477         // do nothing - overridable by subclass
478     }
479 
480     /**
481      * Overridable callback method invoked at the start of processing each directory.
482      * <p>
483      * This implementation does nothing.
484      * </p>
485      *
486      * @param directory  the current directory being processed
487      * @param depth  the current directory level (starting directory = 0)
488      * @param results  the collection of result objects, may be updated
489      * @throws IOException if an I/O Error occurs
490      */
491     @SuppressWarnings("unused") // Possibly thrown from subclasses.
492     protected void handleDirectoryStart(final File directory, final int depth, final Collection<T> results) throws
493             IOException {
494         // do nothing - overridable by subclass
495     }
496 
497     /**
498      * Overridable callback method invoked at the end of processing.
499      * <p>
500      * This implementation does nothing.
501      * </p>
502      *
503      * @param results  the collection of result objects, may be updated
504      * @throws IOException if an I/O Error occurs
505      */
506     @SuppressWarnings("unused") // Possibly thrown from subclasses.
507     protected void handleEnd(final Collection<T> results) throws IOException {
508         // do nothing - overridable by subclass
509     }
510 
511     /**
512      * Overridable callback method invoked for each (non-directory) file.
513      * <p>
514      * This implementation does nothing.
515      * </p>
516      *
517      * @param file  the current file being processed
518      * @param depth  the current directory level (starting directory = 0)
519      * @param results  the collection of result objects, may be updated
520      * @throws IOException if an I/O Error occurs
521      */
522     @SuppressWarnings("unused") // Possibly thrown from subclasses.
523     protected void handleFile(final File file, final int depth, final Collection<T> results) throws IOException {
524         // do nothing - overridable by subclass
525     }
526 
527     /**
528      * Overridable callback method invoked to determine if the entire walk
529      * operation should be immediately cancelled.
530      * <p>
531      * This method should be implemented by those subclasses that want to
532      * provide a public {@code cancel()} method available from another
533      * thread. The design pattern for the subclass should be as follows:
534      * </p>
535      * <pre>
536      *  public class FooDirectoryWalker extends DirectoryWalker {
537      *    private volatile boolean cancelled = false;
538      *
539      *    public void cancel() {
540      *        cancelled = true;
541      *    }
542      *    private void handleIsCancelled(File file, int depth, Collection results) {
543      *        return cancelled;
544      *    }
545      *    protected void handleCancelled(File startDirectory,
546      *              Collection results, CancelException cancel) {
547      *        // implement processing required when a cancellation occurs
548      *    }
549      *  }
550      * </pre>
551      * <p>
552      * If this method returns true, then the directory walk is immediately
553      * cancelled. The next callback method will be {@link #handleCancelled}.
554      * </p>
555      * <p>
556      * This implementation returns false.
557      * </p>
558      *
559      * @param file  the file or directory being processed
560      * @param depth  the current directory level (starting directory = 0)
561      * @param results  the collection of result objects, may be updated
562      * @return true if the walk has been cancelled
563      * @throws IOException if an I/O Error occurs
564      */
565     @SuppressWarnings("unused") // Possibly thrown from subclasses.
566     protected boolean handleIsCancelled(
567             final File file, final int depth, final Collection<T> results) throws IOException {
568         // do nothing - overridable by subclass
569         return false;  // not cancelled
570     }
571 
572     /**
573      * Overridable callback method invoked for each restricted directory.
574      * <p>
575      * This implementation does nothing.
576      * </p>
577      *
578      * @param directory  the restricted directory
579      * @param depth  the current directory level (starting directory = 0)
580      * @param results  the collection of result objects, may be updated
581      * @throws IOException if an I/O Error occurs
582      */
583     @SuppressWarnings("unused") // Possibly thrown from subclasses.
584     protected void handleRestricted(final File directory, final int depth, final Collection<T> results) throws
585             IOException {
586         // do nothing - overridable by subclass
587     }
588 
589     /**
590      * Overridable callback method invoked at the start of processing.
591      * <p>
592      * This implementation does nothing.
593      * </p>
594      *
595      * @param startDirectory  the directory to start from
596      * @param results  the collection of result objects, may be updated
597      * @throws IOException if an I/O Error occurs
598      */
599     @SuppressWarnings("unused") // Possibly thrown from subclasses.
600     protected void handleStart(final File startDirectory, final Collection<T> results) throws IOException {
601         // do nothing - overridable by subclass
602     }
603 
604     /**
605      * Internal method that walks the directory hierarchy in a depth-first manner.
606      * <p>
607      * Users of this class do not need to call this method. This method will
608      * be called automatically by another (public) method on the specific subclass.
609      * </p>
610      * <p>
611      * Writers of subclasses should call this method to start the directory walk.
612      * Once called, this method will emit events as it walks the hierarchy.
613      * The event methods have the prefix {@code handle}.
614      * </p>
615      *
616      * @param startDirectory  the directory to start from, not null
617      * @param results  the collection of result objects, may be updated
618      * @throws NullPointerException if the start directory is null
619      * @throws IOException if an I/O Error occurs
620      */
621     protected final void walk(final File startDirectory, final Collection<T> results) throws IOException {
622         Objects.requireNonNull(startDirectory, "startDirectory");
623         try {
624             handleStart(startDirectory, results);
625             walk(startDirectory, 0, results);
626             handleEnd(results);
627         } catch (final CancelException cancel) {
628             handleCancelled(startDirectory, results, cancel);
629         }
630     }
631 
632     /**
633      * Main recursive method to examine the directory hierarchy.
634      *
635      * @param directory  the directory to examine, not null
636      * @param depth  the directory level (starting directory = 0)
637      * @param results  the collection of result objects, may be updated
638      * @throws IOException if an I/O Error occurs
639      */
640     private void walk(final File directory, final int depth, final Collection<T> results) throws IOException {
641         checkIfCancelled(directory, depth, results);
642         if (handleDirectory(directory, depth, results)) {
643             handleDirectoryStart(directory, depth, results);
644             final int childDepth = depth + 1;
645             if (depthLimit < 0 || childDepth <= depthLimit) {
646                 checkIfCancelled(directory, depth, results);
647                 File[] childFiles = filter == null ? directory.listFiles() : directory.listFiles(filter);
648                 childFiles = filterDirectoryContents(directory, depth, childFiles);
649                 if (childFiles == null) {
650                     handleRestricted(directory, childDepth, results);
651                 } else {
652                     for (final File childFile : childFiles) {
653                         if (childFile.isDirectory()) {
654                             walk(childFile, childDepth, results);
655                         } else {
656                             checkIfCancelled(childFile, childDepth, results);
657                             handleFile(childFile, childDepth, results);
658                             checkIfCancelled(childFile, childDepth, results);
659                         }
660                     }
661                 }
662             }
663             handleDirectoryEnd(directory, depth, results);
664         }
665         checkIfCancelled(directory, depth, results);
666     }
667 }