DirectoryWalker.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.commons.io;
- import java.io.File;
- import java.io.FileFilter;
- import java.io.IOException;
- import java.nio.file.Files;
- import java.util.Collection;
- import java.util.Objects;
- import org.apache.commons.io.file.PathUtils;
- import org.apache.commons.io.filefilter.FileFilterUtils;
- import org.apache.commons.io.filefilter.IOFileFilter;
- import org.apache.commons.io.filefilter.TrueFileFilter;
- /**
- * Abstract class that walks through a directory hierarchy and provides subclasses with convenient hooks to add specific
- * behavior.
- * <p>
- * This class operates with a {@link FileFilter} and maximum depth to limit the files and directories visited. Commons
- * IO supplies many common filter implementations in the <a href="filefilter/package-summary.html"> filefilter</a>
- * package.
- * </p>
- * <p>
- * The following sections describe:
- * </p>
- * <ul>
- * <li><a href="#example">1. Example Implementation</a> - example {@link FileCleaner} implementation.</li>
- * <li><a href="#filter">2. Filter Example</a> - using {@link FileFilter}(s) with {@link DirectoryWalker}.</li>
- * <li><a href="#cancel">3. Cancellation</a> - how to implement cancellation behavior.</li>
- * </ul>
- *
- * <h2 id="example">1. Example Implementation</h2>
- *
- * There are many possible extensions, for example, to delete all files and '.svn' directories, and return a list of
- * deleted files:
- *
- * <pre>
- * public class FileCleaner extends DirectoryWalker {
- *
- * public FileCleaner() {
- * super();
- * }
- *
- * public List clean(File startDirectory) {
- * List results = new ArrayList();
- * walk(startDirectory, results);
- * return results;
- * }
- *
- * protected boolean handleDirectory(File directory, int depth, Collection results) {
- * // delete svn directories and then skip
- * if (".svn".equals(directory.getName())) {
- * directory.delete();
- * return false;
- * } else {
- * return true;
- * }
- *
- * }
- *
- * protected void handleFile(File file, int depth, Collection results) {
- * // delete file and add to list of deleted
- * file.delete();
- * results.add(file);
- * }
- * }
- * </pre>
- *
- * <h2 id="filter">2. Filter Example</h2>
- *
- * <p>
- * Choosing which directories and files to process can be a key aspect of using this class. This information can be
- * setup in three ways, via three different constructors.
- * </p>
- * <p>
- * The first option is to visit all directories and files. This is achieved via the no-args constructor.
- * </p>
- * <p>
- * The second constructor option is to supply a single {@link FileFilter} that describes the files and directories to
- * visit. Care must be taken with this option as the same filter is used for both directories and files.
- * </p>
- * <p>
- * For example, if you wanted all directories which are not hidden and files which end in ".txt":
- * </p>
- *
- * <pre>
- * public class FooDirectoryWalker extends DirectoryWalker {
- * public FooDirectoryWalker(FileFilter filter) {
- * super(filter, -1);
- * }
- * }
- *
- * // Build up the filters and create the walker
- * // Create a filter for Non-hidden directories
- * IOFileFilter fooDirFilter = FileFilterUtils.andFileFilter(FileFilterUtils.directoryFileFilter,
- * HiddenFileFilter.VISIBLE);
- *
- * // Create a filter for Files ending in ".txt"
- * IOFileFilter fooFileFilter = FileFilterUtils.andFileFilter(FileFilterUtils.fileFileFilter,
- * FileFilterUtils.suffixFileFilter(".txt"));
- *
- * // Combine the directory and file filters using an OR condition
- * java.io.FileFilter fooFilter = FileFilterUtils.orFileFilter(fooDirFilter, fooFileFilter);
- *
- * // Use the filter to construct a DirectoryWalker implementation
- * FooDirectoryWalker walker = new FooDirectoryWalker(fooFilter);
- * </pre>
- * <p>
- * The third constructor option is to specify separate filters, one for directories and one for files. These are
- * combined internally to form the correct {@link FileFilter}, something which is very easy to get wrong when
- * attempted manually, particularly when trying to express constructs like 'any file in directories named docs'.
- * </p>
- * <p>
- * For example, if you wanted all directories which are not hidden and files which end in ".txt":
- * </p>
- *
- * <pre>
- * public class FooDirectoryWalker extends DirectoryWalker {
- * public FooDirectoryWalker(IOFileFilter dirFilter, IOFileFilter fileFilter) {
- * super(dirFilter, fileFilter, -1);
- * }
- * }
- *
- * // Use the filters to construct the walker
- * FooDirectoryWalker walker = new FooDirectoryWalker(
- * HiddenFileFilter.VISIBLE,
- * FileFilterUtils.suffixFileFilter(".txt"),
- * );
- * </pre>
- * <p>
- * This is much simpler than the previous example, and is why it is the preferred option for filtering.
- * </p>
- *
- * <h2 id="cancel">3. Cancellation</h2>
- *
- * <p>
- * The DirectoryWalker contains some of the logic required for cancel processing. Subclasses must complete the
- * implementation.
- * </p>
- * <p>
- * What {@link DirectoryWalker} does provide for cancellation is:
- * </p>
- * <ul>
- * <li>{@link CancelException} which can be thrown in any of the <em>lifecycle</em> methods to stop processing.</li>
- * <li>The {@code walk()} method traps thrown {@link CancelException} and calls the {@code handleCancelled()}
- * method, providing a place for custom cancel processing.</li>
- * </ul>
- * <p>
- * Implementations need to provide:
- * </p>
- * <ul>
- * <li>The decision logic on whether to cancel processing or not.</li>
- * <li>Constructing and throwing a {@link CancelException}.</li>
- * <li>Custom cancel processing in the {@code handleCancelled()} method.
- * </ul>
- * <p>
- * Two possible scenarios are envisaged for cancellation:
- * </p>
- * <ul>
- * <li><a href="#external">3.1 External / Multi-threaded</a> - cancellation being decided/initiated by an external
- * process.</li>
- * <li><a href="#internal">3.2 Internal</a> - cancellation being decided/initiated from within a DirectoryWalker
- * implementation.</li>
- * </ul>
- * <p>
- * The following sections provide example implementations for these two different scenarios.
- * </p>
- *
- * <h3 id="external">3.1 External / Multi-threaded</h3>
- *
- * <p>
- * This example provides a public {@code cancel()} method that can be called by another thread to stop the
- * processing. A typical example use-case is a cancel button on a GUI. Calling this method sets a
- * <a href='https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#d5e12277'>(@code volatile}</a>
- * flag to ensure it works properly in a multi-threaded environment.
- * The flag is returned by the {@code handleIsCancelled()} method, which causes the walk to stop
- * immediately. The {@code handleCancelled()} method will be the next, and last, callback method received once cancellation has occurred.
- * </p>
- *
- * <pre>
- * public class FooDirectoryWalker extends DirectoryWalker {
- *
- * private volatile boolean cancelled = false;
- *
- * public void cancel() {
- * cancelled = true;
- * }
- *
- * protected boolean handleIsCancelled(File file, int depth, Collection results) {
- * return cancelled;
- * }
- *
- * protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
- * // implement processing required when a cancellation occurs
- * }
- * }
- * </pre>
- *
- * <h3 id="internal">3.2 Internal</h3>
- *
- * <p>
- * This shows an example of how internal cancellation processing could be implemented. <strong>Note</strong> the decision logic
- * and throwing a {@link CancelException} could be implemented in any of the <em>lifecycle</em> methods.
- * </p>
- *
- * <pre>
- * public class BarDirectoryWalker extends DirectoryWalker {
- *
- * protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException {
- * // cancel if hidden directory
- * if (directory.isHidden()) {
- * throw new CancelException(file, depth);
- * }
- * return true;
- * }
- *
- * protected void handleFile(File file, int depth, Collection results) throws IOException {
- * // cancel if read-only file
- * if (!file.canWrite()) {
- * throw new CancelException(file, depth);
- * }
- * results.add(file);
- * }
- *
- * protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
- * // implement processing required when a cancellation occurs
- * }
- * }
- * </pre>
- *
- * @param <T> The result type, like {@link File}.
- * @since 1.3
- * @deprecated Apache Commons IO no longer uses this class. Instead, use
- * {@link PathUtils#walk(java.nio.file.Path, org.apache.commons.io.file.PathFilter, int, boolean, java.nio.file.FileVisitOption...)}
- * or {@link Files#walkFileTree(java.nio.file.Path, java.util.Set, int, java.nio.file.FileVisitor)}, and
- * friends.
- */
- @Deprecated
- public abstract class DirectoryWalker<T> {
- /**
- * CancelException is thrown in DirectoryWalker to cancel the current
- * processing.
- */
- public static class CancelException extends IOException {
- /** Serialization id. */
- private static final long serialVersionUID = 1347339620135041008L;
- /** The file being processed when the exception was thrown. */
- private final File file;
- /** The file depth when the exception was thrown. */
- private final int depth;
- /**
- * Constructs a {@link CancelException} with
- * the file and depth when cancellation occurred.
- *
- * @param file the file when the operation was cancelled, may be null
- * @param depth the depth when the operation was cancelled, may be null
- */
- public CancelException(final File file, final int depth) {
- this("Operation Cancelled", file, depth);
- }
- /**
- * Constructs a {@link CancelException} with
- * an appropriate message and the file and depth when
- * cancellation occurred.
- *
- * @param message the detail message
- * @param file the file when the operation was cancelled
- * @param depth the depth when the operation was cancelled
- */
- public CancelException(final String message, final File file, final int depth) {
- super(message);
- this.file = file;
- this.depth = depth;
- }
- /**
- * Returns the depth when the operation was cancelled.
- *
- * @return the depth when the operation was cancelled
- */
- public int getDepth() {
- return depth;
- }
- /**
- * Returns the file when the operation was cancelled.
- *
- * @return the file when the operation was cancelled
- */
- public File getFile() {
- return file;
- }
- }
- /**
- * The file filter to use to filter files and directories.
- */
- private final FileFilter filter;
- /**
- * The limit on the directory depth to walk.
- */
- private final int depthLimit;
- /**
- * Constructs an instance with no filtering and unlimited <em>depth</em>.
- */
- protected DirectoryWalker() {
- this(null, -1);
- }
- /**
- * Constructs an instance with a filter and limit the <em>depth</em> navigated to.
- * <p>
- * The filter controls which files and directories will be navigated to as
- * part of the walk. The {@link FileFilterUtils} class is useful for combining
- * various filters together. A {@code null} filter means that no
- * filtering should occur and all files and directories will be visited.
- * </p>
- *
- * @param filter the filter to apply, null means visit all files
- * @param depthLimit controls how <em>deep</em> the hierarchy is
- * navigated to (less than 0 means unlimited)
- */
- protected DirectoryWalker(final FileFilter filter, final int depthLimit) {
- this.filter = filter;
- this.depthLimit = depthLimit;
- }
- /**
- * Constructs an instance with a directory and a file filter and an optional
- * limit on the <em>depth</em> navigated to.
- * <p>
- * The filters control which files and directories will be navigated to as part
- * of the walk. This constructor uses {@link FileFilterUtils#makeDirectoryOnly(IOFileFilter)}
- * and {@link FileFilterUtils#makeFileOnly(IOFileFilter)} internally to combine the filters.
- * A {@code null} filter means that no filtering should occur.
- * </p>
- *
- * @param directoryFilter the filter to apply to directories, null means visit all directories
- * @param fileFilter the filter to apply to files, null means visit all files
- * @param depthLimit controls how <em>deep</em> the hierarchy is
- * navigated to (less than 0 means unlimited)
- */
- protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, final int depthLimit) {
- if (directoryFilter == null && fileFilter == null) {
- this.filter = null;
- } else {
- directoryFilter = directoryFilter != null ? directoryFilter : TrueFileFilter.TRUE;
- fileFilter = fileFilter != null ? fileFilter : TrueFileFilter.TRUE;
- directoryFilter = FileFilterUtils.makeDirectoryOnly(directoryFilter);
- fileFilter = FileFilterUtils.makeFileOnly(fileFilter);
- this.filter = directoryFilter.or(fileFilter);
- }
- this.depthLimit = depthLimit;
- }
- /**
- * Checks whether the walk has been cancelled by calling {@link #handleIsCancelled},
- * throwing a {@link CancelException} if it has.
- * <p>
- * Writers of subclasses should not normally call this method as it is called
- * automatically by the walk of the tree. However, sometimes a single method,
- * typically {@link #handleFile}, may take a long time to run. In that case,
- * you may wish to check for cancellation by calling this method.
- * </p>
- *
- * @param file the current file being processed
- * @param depth the current file level (starting directory = 0)
- * @param results the collection of result objects, may be updated
- * @throws IOException if an I/O Error occurs
- */
- protected final void checkIfCancelled(final File file, final int depth, final Collection<T> results) throws
- IOException {
- if (handleIsCancelled(file, depth, results)) {
- throw new CancelException(file, depth);
- }
- }
- /**
- * Overridable callback method invoked with the contents of each directory.
- * <p>
- * This implementation returns the files unchanged
- * </p>
- *
- * @param directory the current directory being processed
- * @param depth the current directory level (starting directory = 0)
- * @param files the files (possibly filtered) in the directory, may be {@code null}
- * @return the filtered list of files
- * @throws IOException if an I/O Error occurs
- * @since 2.0
- */
- @SuppressWarnings("unused") // Possibly thrown from subclasses.
- protected File[] filterDirectoryContents(final File directory, final int depth, final File... files) throws
- IOException {
- return files;
- }
- /**
- * Overridable callback method invoked when the operation is cancelled.
- * The file being processed when the cancellation occurred can be
- * obtained from the exception.
- * <p>
- * This implementation just re-throws the {@link CancelException}.
- * </p>
- *
- * @param startDirectory the directory that the walk started from
- * @param results the collection of result objects, may be updated
- * @param cancel the exception throw to cancel further processing
- * containing details at the point of cancellation.
- * @throws IOException if an I/O Error occurs
- */
- protected void handleCancelled(final File startDirectory, final Collection<T> results,
- final CancelException cancel) throws IOException {
- // re-throw exception - overridable by subclass
- throw cancel;
- }
- /**
- * Overridable callback method invoked to determine if a directory should be processed.
- * <p>
- * This method returns a boolean to indicate if the directory should be examined or not.
- * If you return false, the entire directory and any subdirectories will be skipped.
- * Note that this functionality is in addition to the filtering by file filter.
- * </p>
- * <p>
- * This implementation does nothing and returns true.
- * </p>
- *
- * @param directory the current directory being processed
- * @param depth the current directory level (starting directory = 0)
- * @param results the collection of result objects, may be updated
- * @return true to process this directory, false to skip this directory
- * @throws IOException if an I/O Error occurs
- */
- @SuppressWarnings("unused") // Possibly thrown from subclasses.
- protected boolean handleDirectory(final File directory, final int depth, final Collection<T> results) throws
- IOException {
- // do nothing - overridable by subclass
- return true; // process directory
- }
- /**
- * Overridable callback method invoked at the end of processing each directory.
- * <p>
- * This implementation does nothing.
- * </p>
- *
- * @param directory the directory being processed
- * @param depth the current directory level (starting directory = 0)
- * @param results the collection of result objects, may be updated
- * @throws IOException if an I/O Error occurs
- */
- @SuppressWarnings("unused") // Possibly thrown from subclasses.
- protected void handleDirectoryEnd(final File directory, final int depth, final Collection<T> results) throws
- IOException {
- // do nothing - overridable by subclass
- }
- /**
- * Overridable callback method invoked at the start of processing each directory.
- * <p>
- * This implementation does nothing.
- * </p>
- *
- * @param directory the current directory being processed
- * @param depth the current directory level (starting directory = 0)
- * @param results the collection of result objects, may be updated
- * @throws IOException if an I/O Error occurs
- */
- @SuppressWarnings("unused") // Possibly thrown from subclasses.
- protected void handleDirectoryStart(final File directory, final int depth, final Collection<T> results) throws
- IOException {
- // do nothing - overridable by subclass
- }
- /**
- * Overridable callback method invoked at the end of processing.
- * <p>
- * This implementation does nothing.
- * </p>
- *
- * @param results the collection of result objects, may be updated
- * @throws IOException if an I/O Error occurs
- */
- @SuppressWarnings("unused") // Possibly thrown from subclasses.
- protected void handleEnd(final Collection<T> results) throws IOException {
- // do nothing - overridable by subclass
- }
- /**
- * Overridable callback method invoked for each (non-directory) file.
- * <p>
- * This implementation does nothing.
- * </p>
- *
- * @param file the current file being processed
- * @param depth the current directory level (starting directory = 0)
- * @param results the collection of result objects, may be updated
- * @throws IOException if an I/O Error occurs
- */
- @SuppressWarnings("unused") // Possibly thrown from subclasses.
- protected void handleFile(final File file, final int depth, final Collection<T> results) throws IOException {
- // do nothing - overridable by subclass
- }
- /**
- * Overridable callback method invoked to determine if the entire walk
- * operation should be immediately cancelled.
- * <p>
- * This method should be implemented by those subclasses that want to
- * provide a public {@code cancel()} method available from another
- * thread. The design pattern for the subclass should be as follows:
- * </p>
- * <pre>
- * public class FooDirectoryWalker extends DirectoryWalker {
- * private volatile boolean cancelled = false;
- *
- * public void cancel() {
- * cancelled = true;
- * }
- * private void handleIsCancelled(File file, int depth, Collection results) {
- * return cancelled;
- * }
- * protected void handleCancelled(File startDirectory,
- * Collection results, CancelException cancel) {
- * // implement processing required when a cancellation occurs
- * }
- * }
- * </pre>
- * <p>
- * If this method returns true, then the directory walk is immediately
- * cancelled. The next callback method will be {@link #handleCancelled}.
- * </p>
- * <p>
- * This implementation returns false.
- * </p>
- *
- * @param file the file or directory being processed
- * @param depth the current directory level (starting directory = 0)
- * @param results the collection of result objects, may be updated
- * @return true if the walk has been cancelled
- * @throws IOException if an I/O Error occurs
- */
- @SuppressWarnings("unused") // Possibly thrown from subclasses.
- protected boolean handleIsCancelled(
- final File file, final int depth, final Collection<T> results) throws IOException {
- // do nothing - overridable by subclass
- return false; // not cancelled
- }
- /**
- * Overridable callback method invoked for each restricted directory.
- * <p>
- * This implementation does nothing.
- * </p>
- *
- * @param directory the restricted directory
- * @param depth the current directory level (starting directory = 0)
- * @param results the collection of result objects, may be updated
- * @throws IOException if an I/O Error occurs
- */
- @SuppressWarnings("unused") // Possibly thrown from subclasses.
- protected void handleRestricted(final File directory, final int depth, final Collection<T> results) throws
- IOException {
- // do nothing - overridable by subclass
- }
- /**
- * Overridable callback method invoked at the start of processing.
- * <p>
- * This implementation does nothing.
- * </p>
- *
- * @param startDirectory the directory to start from
- * @param results the collection of result objects, may be updated
- * @throws IOException if an I/O Error occurs
- */
- @SuppressWarnings("unused") // Possibly thrown from subclasses.
- protected void handleStart(final File startDirectory, final Collection<T> results) throws IOException {
- // do nothing - overridable by subclass
- }
- /**
- * Internal method that walks the directory hierarchy in a depth-first manner.
- * <p>
- * Users of this class do not need to call this method. This method will
- * be called automatically by another (public) method on the specific subclass.
- * </p>
- * <p>
- * Writers of subclasses should call this method to start the directory walk.
- * Once called, this method will emit events as it walks the hierarchy.
- * The event methods have the prefix {@code handle}.
- * </p>
- *
- * @param startDirectory the directory to start from, not null
- * @param results the collection of result objects, may be updated
- * @throws NullPointerException if the start directory is null
- * @throws IOException if an I/O Error occurs
- */
- protected final void walk(final File startDirectory, final Collection<T> results) throws IOException {
- Objects.requireNonNull(startDirectory, "startDirectory");
- try {
- handleStart(startDirectory, results);
- walk(startDirectory, 0, results);
- handleEnd(results);
- } catch (final CancelException cancel) {
- handleCancelled(startDirectory, results, cancel);
- }
- }
- /**
- * Main recursive method to examine the directory hierarchy.
- *
- * @param directory the directory to examine, not null
- * @param depth the directory level (starting directory = 0)
- * @param results the collection of result objects, may be updated
- * @throws IOException if an I/O Error occurs
- */
- private void walk(final File directory, final int depth, final Collection<T> results) throws IOException {
- checkIfCancelled(directory, depth, results);
- if (handleDirectory(directory, depth, results)) {
- handleDirectoryStart(directory, depth, results);
- final int childDepth = depth + 1;
- if (depthLimit < 0 || childDepth <= depthLimit) {
- checkIfCancelled(directory, depth, results);
- File[] childFiles = directory.listFiles(filter);
- childFiles = filterDirectoryContents(directory, depth, childFiles);
- if (childFiles == null) {
- handleRestricted(directory, childDepth, results);
- } else {
- for (final File childFile : childFiles) {
- if (childFile.isDirectory()) {
- walk(childFile, childDepth, results);
- } else {
- checkIfCancelled(childFile, childDepth, results);
- handleFile(childFile, childDepth, results);
- checkIfCancelled(childFile, childDepth, results);
- }
- }
- }
- }
- handleDirectoryEnd(directory, depth, results);
- }
- checkIfCancelled(directory, depth, results);
- }
- }