/* * 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. *
* 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 filefilter * package. *
** The following sections describe: *
** 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); * } * } ** *
* 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. *
** The first option is to visit all directories and files. This is achieved via the no-args constructor. *
** 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. *
** For example, if you wanted all directories which are not hidden and files which end in ".txt": *
* ** 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); **
* 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'. *
** For example, if you wanted all directories which are not hidden and files which end in ".txt": *
* ** 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"), * ); **
* This is much simpler than the previous example, and is why it is the preferred option for filtering. *
* ** The DirectoryWalker contains some of the logic required for cancel processing. Subclasses must complete the * implementation. *
** What {@link DirectoryWalker} does provide for cancellation is: *
** Implementations need to provide: *
** Two possible scenarios are envisaged for cancellation: *
** The following sections provide example implementations for these two different scenarios. *
* ** This example provides a public {@code cancel()} method that can be called by another thread to stop the * processing. A typical example use-case would be a cancel button on a GUI. Calling this method sets a * volatile flag to ensure * it will work properly in a multi-threaded environment. The flag is returned by the {@code handleIsCancelled()} * method, which will cause the walk to stop immediately. The {@code handleCancelled()} method will be the next, * and last, callback method received once cancellation has occurred. *
* ** 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 * } * } ** *
* This shows an example of how internal cancellation processing could be implemented. Note the decision logic * and throwing a {@link CancelException} could be implemented in any of the lifecycle methods. *
* ** 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 * } * } ** * @param
* 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. *
* * @param filter the filter to apply, null means visit all files * @param depthLimit controls how deep 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 depth navigated to. ** 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. *
* * @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 deep 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. ** 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. *
* * @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* This implementation returns the files unchanged *
* * @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. ** This implementation just re-throws the {@link CancelException}. *
* * @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* 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. *
** This implementation does nothing and returns true. *
* * @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* This implementation does nothing. *
* * @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* This implementation does nothing. *
* * @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* This implementation does nothing. *
* * @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* This implementation does nothing. *
* * @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* 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: *
** 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 * } * } **
* If this method returns true, then the directory walk is immediately * cancelled. The next callback method will be {@link #handleCancelled}. *
** This implementation returns false. *
* * @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* This implementation does nothing. *
* * @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* This implementation does nothing. *
* * @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* 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. *
** 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}. *
* * @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