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 *      https://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 */
017package org.apache.commons.io;
018
019import java.io.File;
020import java.io.FileFilter;
021import java.io.IOException;
022import java.nio.file.Files;
023import java.util.Collection;
024import java.util.Objects;
025
026import org.apache.commons.io.file.PathUtils;
027import org.apache.commons.io.filefilter.FileFilterUtils;
028import org.apache.commons.io.filefilter.IOFileFilter;
029import org.apache.commons.io.filefilter.TrueFileFilter;
030
031/**
032 * Abstract class that walks through a directory hierarchy and provides subclasses with convenient hooks to add specific
033 * behavior.
034 * <p>
035 * This class operates with a {@link FileFilter} and maximum depth to limit the files and directories visited. Commons
036 * IO supplies many common filter implementations in the <a href="filefilter/package-summary.html"> filefilter</a>
037 * package.
038 * </p>
039 * <p>
040 * The following sections describe:
041 * </p>
042 * <ul>
043 * <li><a href="#example">1. Example Implementation</a> - example {@link FileCleaner} implementation.</li>
044 * <li><a href="#filter">2. Filter Example</a> - using {@link FileFilter}(s) with {@link DirectoryWalker}.</li>
045 * <li><a href="#cancel">3. Cancellation</a> - how to implement cancellation behavior.</li>
046 * </ul>
047 *
048 * <h2 id="example">1. Example Implementation</h2>
049 *
050 * There are many possible extensions, for example, to delete all files and '.svn' directories, and return a list of
051 * deleted files:
052 *
053 * <pre>
054 * public class FileCleaner extends DirectoryWalker {
055 *
056 *     public FileCleaner() {
057 *         super();
058 *     }
059 *
060 *     public List clean(File startDirectory) {
061 *         List results = new ArrayList();
062 *         walk(startDirectory, results);
063 *         return results;
064 *     }
065 *
066 *     protected boolean handleDirectory(File directory, int depth, Collection results) {
067 *         // delete svn directories and then skip
068 *         if (".svn".equals(directory.getName())) {
069 *             directory.delete();
070 *             return false;
071 *         } else {
072 *             return true;
073 *         }
074 *
075 *     }
076 *
077 *     protected void handleFile(File file, int depth, Collection results) {
078 *         // delete file and add to list of deleted
079 *         file.delete();
080 *         results.add(file);
081 *     }
082 * }
083 * </pre>
084 *
085 * <h2 id="filter">2. Filter Example</h2>
086 *
087 * <p>
088 * Choosing which directories and files to process can be a key aspect of using this class. This information can be
089 * setup in three ways, via three different constructors.
090 * </p>
091 * <p>
092 * The first option is to visit all directories and files. This is achieved via the no-args constructor.
093 * </p>
094 * <p>
095 * The second constructor option is to supply a single {@link FileFilter} that describes the files and directories to
096 * visit. Care must be taken with this option as the same filter is used for both directories and files.
097 * </p>
098 * <p>
099 * 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 <em>lifecycle</em> 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.</li>
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 canceled = false;
200 *
201 *     public void cancel() {
202 *         canceled = true;
203 *     }
204 *
205 *     protected boolean handleIsCancelled(File file, int depth, Collection results) {
206 *         return canceled;
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. <strong>Note</strong> the decision logic
219 * and throwing a {@link CancelException} could be implemented in any of the <em>lifecycle</em> 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
255public 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
269        /** The file depth when the exception was thrown. */
270        private final int depth;
271
272        /**
273         * Constructs a {@link CancelException} with
274         * the file and depth when cancellation occurred.
275         *
276         * @param file  the file when the operation was canceled, may be null.
277         * @param depth  the depth when the operation was canceled, may be null.
278         */
279        public CancelException(final File file, final int depth) {
280            this("Operation Cancelled", file, depth);
281        }
282
283        /**
284         * Constructs a {@link CancelException} with
285         * an appropriate message and the file and depth when
286         * cancellation occurred.
287         *
288         * @param message  the detail message.
289         * @param file  the file when the operation was canceled.
290         * @param depth  the depth when the operation was canceled.
291         */
292        public CancelException(final String message, final File file, final int depth) {
293            super(message);
294            this.file = file;
295            this.depth = depth;
296        }
297
298        /**
299         * Returns the depth when the operation was canceled.
300         *
301         * @return the depth when the operation was canceled.
302         */
303        public int getDepth() {
304            return depth;
305        }
306
307        /**
308         * Returns the file when the operation was canceled.
309         *
310         * @return the file when the operation was canceled.
311         */
312        public File getFile() {
313            return file;
314        }
315    }
316
317    /**
318     * The file filter to use to filter files and directories.
319     */
320    private final FileFilter filter;
321
322    /**
323     * The limit on the directory depth to walk.
324     */
325    private final int depthLimit;
326
327    /**
328     * Constructs an instance with no filtering and unlimited <em>depth</em>.
329     */
330    protected DirectoryWalker() {
331        this(null, -1);
332    }
333
334    /**
335     * Constructs an instance with a filter and limit the <em>depth</em> navigated to.
336     * <p>
337     * The filter controls which files and directories will be navigated to as
338     * part of the walk. The {@link FileFilterUtils} class is useful for combining
339     * various filters together. A {@code null} filter means that no
340     * filtering should occur and all files and directories will be visited.
341     * </p>
342     *
343     * @param filter  the filter to apply, null means visit all files.
344     * @param depthLimit  controls how <em>deep</em> the hierarchy is
345     *  navigated to (less than 0 means unlimited).
346     */
347    protected DirectoryWalker(final FileFilter filter, final int depthLimit) {
348        this.filter = filter;
349        this.depthLimit = depthLimit;
350    }
351
352    /**
353     * Constructs an instance with a directory and a file filter and an optional
354     * limit on the <em>depth</em> navigated to.
355     * <p>
356     * The filters control which files and directories will be navigated to as part
357     * of the walk. This constructor uses {@link FileFilterUtils#makeDirectoryOnly(IOFileFilter)}
358     * and {@link FileFilterUtils#makeFileOnly(IOFileFilter)} internally to combine the filters.
359     * A {@code null} filter means that no filtering should occur.
360     * </p>
361     *
362     * @param directoryFilter  the filter to apply to directories, null means visit all directories.
363     * @param fileFilter  the filter to apply to files, null means visit all files.
364     * @param depthLimit  controls how <em>deep</em> the hierarchy is
365     *  navigated to (less than 0 means unlimited).
366     */
367    protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, final int depthLimit) {
368        if (directoryFilter == null && fileFilter == null) {
369            this.filter = null;
370        } else {
371            directoryFilter = directoryFilter != null ? directoryFilter : TrueFileFilter.TRUE;
372            fileFilter = fileFilter != null ? fileFilter : TrueFileFilter.TRUE;
373            directoryFilter = FileFilterUtils.makeDirectoryOnly(directoryFilter);
374            fileFilter = FileFilterUtils.makeFileOnly(fileFilter);
375            this.filter = directoryFilter.or(fileFilter);
376        }
377        this.depthLimit = depthLimit;
378    }
379
380    /**
381     * Checks whether the walk has been canceled by calling {@link #handleIsCancelled},
382     * throwing a {@link CancelException} if it has.
383     * <p>
384     * Writers of subclasses should not normally call this method as it is called
385     * automatically by the walk of the tree. However, sometimes a single method,
386     * typically {@link #handleFile}, may take a long time to run. In that case,
387     * you may wish to check for cancellation by calling this method.
388     * </p>
389     *
390     * @param file  the current file being processed.
391     * @param depth  the current file level (starting directory = 0).
392     * @param results  the collection of result objects, may be updated.
393     * @throws IOException if an I/O Error occurs.
394     */
395    protected final void checkIfCancelled(final File file, final int depth, final Collection<T> results) throws
396            IOException {
397        if (handleIsCancelled(file, depth, results)) {
398            throw new CancelException(file, depth);
399        }
400    }
401
402    /**
403     * Overridable callback method invoked with the contents of each directory.
404     * <p>
405     * This implementation returns the files unchanged
406     * </p>
407     *
408     * @param directory  the current directory being processed.
409     * @param depth  the current directory level (starting directory = 0).
410     * @param files the files (possibly filtered) in the directory, may be {@code null}
411     * @return the filtered list of files.
412     * @throws IOException if an I/O Error occurs.
413     * @since 2.0
414     */
415    @SuppressWarnings("unused") // Possibly thrown from subclasses.
416    protected File[] filterDirectoryContents(final File directory, final int depth, final File... files) throws
417            IOException {
418        return files;
419    }
420
421    /**
422     * Overridable callback method invoked when the operation is canceled.
423     * The file being processed when the cancellation occurred can be
424     * obtained from the exception.
425     * <p>
426     * This implementation just re-throws the {@link CancelException}.
427     * </p>
428     *
429     * @param startDirectory  the directory that the walk started from.
430     * @param results  the collection of result objects, may be updated.
431     * @param cancel  the exception throw to cancel further processing
432     * containing details at the point of cancellation.
433     * @throws IOException if an I/O Error occurs.
434     */
435    protected void handleCancelled(final File startDirectory, final Collection<T> results,
436                       final CancelException cancel) throws IOException {
437        // re-throw exception - overridable by subclass
438        throw cancel;
439    }
440
441    /**
442     * Overridable callback method invoked to determine if a directory should be processed.
443     * <p>
444     * This method returns a boolean to indicate if the directory should be examined or not.
445     * If you return false, the entire directory and any subdirectories will be skipped.
446     * Note that this functionality is in addition to the filtering by file filter.
447     * </p>
448     * <p>
449     * This implementation does nothing and returns true.
450     * </p>
451     *
452     * @param directory  the current directory being processed.
453     * @param depth  the current directory level (starting directory = 0).
454     * @param results  the collection of result objects, may be updated.
455     * @return true to process this directory, false to skip this directory.
456     * @throws IOException if an I/O Error occurs.
457     */
458    @SuppressWarnings("unused") // Possibly thrown from subclasses.
459    protected boolean handleDirectory(final File directory, final int depth, final Collection<T> results) throws
460            IOException {
461        // do nothing - overridable by subclass
462        return true;  // process directory
463    }
464
465    /**
466     * Overridable callback method invoked at the end of processing each directory.
467     * <p>
468     * This implementation does nothing.
469     * </p>
470     *
471     * @param directory  the directory being processed.
472     * @param depth  the current directory level (starting directory = 0).
473     * @param results  the collection of result objects, may be updated.
474     * @throws IOException if an I/O Error occurs.
475     */
476    @SuppressWarnings("unused") // Possibly thrown from subclasses.
477    protected void handleDirectoryEnd(final File directory, final int depth, final Collection<T> results) throws
478            IOException {
479        // do nothing - overridable by subclass
480    }
481
482    /**
483     * Overridable callback method invoked at the start of processing each directory.
484     * <p>
485     * This implementation does nothing.
486     * </p>
487     *
488     * @param directory  the current directory being processed.
489     * @param depth  the current directory level (starting directory = 0).
490     * @param results  the collection of result objects, may be updated.
491     * @throws IOException if an I/O Error occurs.
492     */
493    @SuppressWarnings("unused") // Possibly thrown from subclasses.
494    protected void handleDirectoryStart(final File directory, final int depth, final Collection<T> results) throws
495            IOException {
496        // do nothing - overridable by subclass
497    }
498
499    /**
500     * Overridable callback method invoked at the end of processing.
501     * <p>
502     * This implementation does nothing.
503     * </p>
504     *
505     * @param results  the collection of result objects, may be updated.
506     * @throws IOException if an I/O Error occurs.
507     */
508    @SuppressWarnings("unused") // Possibly thrown from subclasses.
509    protected void handleEnd(final Collection<T> results) throws IOException {
510        // do nothing - overridable by subclass
511    }
512
513    /**
514     * Overridable callback method invoked for each (non-directory) file.
515     * <p>
516     * This implementation does nothing.
517     * </p>
518     *
519     * @param file  the current file being processed.
520     * @param depth  the current directory level (starting directory = 0).
521     * @param results  the collection of result objects, may be updated.
522     * @throws IOException if an I/O Error occurs.
523     */
524    @SuppressWarnings("unused") // Possibly thrown from subclasses.
525    protected void handleFile(final File file, final int depth, final Collection<T> results) throws IOException {
526        // do nothing - overridable by subclass
527    }
528
529    /**
530     * Overridable callback method invoked to determine if the entire walk
531     * operation should be immediately canceled.
532     * <p>
533     * This method should be implemented by those subclasses that want to
534     * provide a public {@code cancel()} method available from another
535     * thread. The design pattern for the subclass should be as follows:
536     * </p>
537     * <pre>
538     *  public class FooDirectoryWalker extends DirectoryWalker {
539     *    private volatile boolean canceled = false;
540     *
541     *    public void cancel() {
542     *        canceled = true;
543     *    }
544     *    private void handleIsCancelled(File file, int depth, Collection results) {
545     *        return canceled;
546     *    }
547     *    protected void handleCancelled(File startDirectory,
548     *              Collection results, CancelException cancel) {
549     *        // implement processing required when a cancellation occurs
550     *    }
551     *  }
552     * </pre>
553     * <p>
554     * If this method returns true, then the directory walk is immediately
555     * canceled. The next callback method will be {@link #handleCancelled}.
556     * </p>
557     * <p>
558     * This implementation returns false.
559     * </p>
560     *
561     * @param file  the file or directory being processed.
562     * @param depth  the current directory level (starting directory = 0).
563     * @param results  the collection of result objects, may be updated.
564     * @return true if the walk has been canceled.
565     * @throws IOException if an I/O Error occurs.
566     */
567    @SuppressWarnings("unused") // Possibly thrown from subclasses.
568    protected boolean handleIsCancelled(
569            final File file, final int depth, final Collection<T> results) throws IOException {
570        // do nothing - overridable by subclass
571        return false;  // not canceled
572    }
573
574    /**
575     * Overridable callback method invoked for each restricted directory.
576     * <p>
577     * This implementation does nothing.
578     * </p>
579     *
580     * @param directory  the restricted directory.
581     * @param depth  the current directory level (starting directory = 0).
582     * @param results  the collection of result objects, may be updated.
583     * @throws IOException if an I/O Error occurs.
584     */
585    @SuppressWarnings("unused") // Possibly thrown from subclasses.
586    protected void handleRestricted(final File directory, final int depth, final Collection<T> results) throws
587            IOException {
588        // do nothing - overridable by subclass
589    }
590
591    /**
592     * Overridable callback method invoked at the start of processing.
593     * <p>
594     * This implementation does nothing.
595     * </p>
596     *
597     * @param startDirectory  the directory to start from.
598     * @param results  the collection of result objects, may be updated.
599     * @throws IOException if an I/O Error occurs.
600     */
601    @SuppressWarnings("unused") // Possibly thrown from subclasses.
602    protected void handleStart(final File startDirectory, final Collection<T> results) throws IOException {
603        // do nothing - overridable by subclass
604    }
605
606    /**
607     * Internal method that walks the directory hierarchy in a depth-first manner.
608     * <p>
609     * Users of this class do not need to call this method. This method will
610     * be called automatically by another (public) method on the specific subclass.
611     * </p>
612     * <p>
613     * Writers of subclasses should call this method to start the directory walk.
614     * Once called, this method will emit events as it walks the hierarchy.
615     * The event methods have the prefix {@code handle}.
616     * </p>
617     *
618     * @param startDirectory  the directory to start from, not null.
619     * @param results  the collection of result objects, may be updated.
620     * @throws NullPointerException if the start directory is null.
621     * @throws IOException if an I/O Error occurs.
622     */
623    protected final void walk(final File startDirectory, final Collection<T> results) throws IOException {
624        Objects.requireNonNull(startDirectory, "startDirectory");
625        try {
626            handleStart(startDirectory, results);
627            walk(startDirectory, 0, results);
628            handleEnd(results);
629        } catch (final CancelException cancel) {
630            handleCancelled(startDirectory, results, cancel);
631        }
632    }
633
634    /**
635     * Main recursive method to examine the directory hierarchy.
636     *
637     * @param directory  the directory to examine, not null.
638     * @param depth  the directory level (starting directory = 0).
639     * @param results  the collection of result objects, may be updated.
640     * @throws IOException if an I/O Error occurs.
641     */
642    private void walk(final File directory, final int depth, final Collection<T> results) throws IOException {
643        checkIfCancelled(directory, depth, results);
644        if (handleDirectory(directory, depth, results)) {
645            handleDirectoryStart(directory, depth, results);
646            final int childDepth = depth + 1;
647            if (depthLimit < 0 || childDepth <= depthLimit) {
648                checkIfCancelled(directory, depth, results);
649                File[] childFiles = directory.listFiles(filter);
650                childFiles = filterDirectoryContents(directory, depth, childFiles);
651                if (childFiles == null) {
652                    handleRestricted(directory, childDepth, results);
653                } else {
654                    for (final File childFile : childFiles) {
655                        if (childFile.isDirectory()) {
656                            walk(childFile, childDepth, results);
657                        } else {
658                            checkIfCancelled(childFile, childDepth, results);
659                            handleFile(childFile, childDepth, results);
660                            checkIfCancelled(childFile, childDepth, results);
661                        }
662                    }
663                }
664            }
665            handleDirectoryEnd(directory, depth, results);
666        }
667        checkIfCancelled(directory, depth, results);
668    }
669}