All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.io.DirectoryWalker Maven / Gradle / Ivy

Go to download

The Apache Commons IO library contains utility classes, stream implementations, file filters, file comparators, endian transformation classes, and much more.

There is a newer version: 62
Show newest version
/*
 * 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: *

* * *

1. Example Implementation

* * There are many possible extensions, for example, to delete all files and '.svn' directories, and return a list of * deleted files: * *
 * 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);
 *     }
 * }
 * 
* *

2. Filter Example

* *

* 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 {@code 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. *

* *

3. Cancellation

* *

* The DirectoryWalker contains some of the logic required for cancel processing. Subclasses must complete the * implementation. *

*

* What {@code DirectoryWalker} does provide for cancellation is: *

*
    *
  • {@link CancelException} which can be thrown in any of the lifecycle methods to stop processing.
  • *
  • The {@code walk()} method traps thrown {@link CancelException} and calls the {@code handleCancelled()} * method, providing a place for custom cancel processing.
  • *
*

* Implementations need to provide: *

*
    *
  • The decision logic on whether to cancel processing or not.
  • *
  • Constructing and throwing a {@link CancelException}.
  • *
  • Custom cancel processing in the {@code handleCancelled()} method. *
*

* Two possible scenarios are envisaged for cancellation: *

* *

* The following sections provide example implementations for these two different scenarios. *

* *

3.1 External / Multi-threaded

* *

* 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
 *     }
 * }
 * 
* *

3.2 Internal

* *

* 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 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 { /** * 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; /** * Construct an instance with no filtering and unlimited depth. */ protected DirectoryWalker() { this(null, -1); } /** * Constructs an instance with a filter and limit the depth navigated to. *

* 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; } //----------------------------------------------------------------------- /** * Internal method that walks the directory hierarchy in a depth-first manner. *

* 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 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 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 = filter == null ? directory.listFiles() : 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); } //----------------------------------------------------------------------- /** * Checks whether the walk has been cancelled by calling {@link #handleIsCancelled}, * throwing a {@code 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 results) throws IOException { if (handleIsCancelled(file, depth, results)) { throw new CancelException(file, depth); } } /** * Overridable callback method invoked to determine if the entire walk * operation should be immediately cancelled. *

* 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 results) throws IOException { // do nothing - overridable by subclass return false; // not cancelled } /** * 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 results, final CancelException cancel) throws IOException { // re-throw exception - overridable by subclass throw cancel; } //----------------------------------------------------------------------- /** * Overridable callback method invoked at the start of processing. *

* 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 results) throws IOException { // do nothing - overridable by subclass } /** * Overridable callback method invoked to determine if a directory should be processed. *

* 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 results) throws IOException { // do nothing - overridable by subclass return true; // process directory } /** * Overridable callback method invoked at the start of processing each directory. *

* 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 results) throws IOException { // do nothing - overridable by subclass } /** * Overridable callback method invoked with the contents of each directory. *

* 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 for each (non-directory) file. *

* 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 results) throws IOException { // do nothing - overridable by subclass } /** * Overridable callback method invoked for each restricted directory. *

* 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 results) throws IOException { // do nothing - overridable by subclass } /** * Overridable callback method invoked at the end of processing each directory. *

* 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 results) throws IOException { // do nothing - overridable by subclass } /** * Overridable callback method invoked at the end of processing. *

* 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 results) throws IOException { // do nothing - overridable by subclass } //----------------------------------------------------------------------- /** * 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 {@code 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 {@code 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 file when the operation was cancelled. * * @return the file when the operation was cancelled */ public File getFile() { return file; } /** * Returns the depth when the operation was cancelled. * * @return the depth when the operation was cancelled */ public int getDepth() { return depth; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy