content: /*
   * 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.util.Collection;
  
  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 behaviour.
   * <p>
   * This class operates with a {@link FileFilter} and maximum depth to
   * limit the files and direcories visited.
   * Commons IO supplies many common filter implementations in the 
   * <a href="filefilter/package-summary.html"> filefilter</a> package.
   * <p>
   * The following sections describe:
   *   <ul>
   *      <li><a href="#example">1. Example Implementation</a> - example
   *          <code>FileCleaner</code> implementation.</li>
   *      <li><a href="#filter">2. Filter Example</a> - using 
   *          {@link FileFilter}(s) with <code>DirectoryWalker</code>.</li>
   *      <li><a href="#cancel">3. Cancellation</a> - how to implement cancellation
   *          behaviour.</li>
   *   </ul>
   *
   * <a name="example"></a>
   * <h3>1. Example Implementation</h3>
   *
   * 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>
   *
   * <a name="filter"></a>
   * <h3>2. Filter Example</h3>
   *
   * 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>
   * The first option is to visit all directories and files.
   * This is achieved via the no-args constructor.
   * <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>
   * For example, if you wanted all directories which are not hidden
   * and files which end in ".txt":
   * <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 <code>FileFilter</code>, 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>
   * For example, if you wanted all directories which are not hidden
   * and files which end in ".txt":
   * <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>
   * This is much simpler than the previous example, and is why it is the preferred
   * option for filtering.
   *
   * <a name="cancel"></a>
   * <h3>3. Cancellation</h3>
   *
   * The DirectoryWalker contains some of the logic required for cancel processing.
   * Subclasses must complete the implementation.
   * <p>
   * What <code>DirectoryWalker</code> does provide for cancellation is:
   * <ul>
   *    <li>{@link CancelException} which can be thrown in any of the
   *        <i>lifecycle</i> methods to stop processing.</li>
   *    <li>The <code>walk()</code> method traps thrown {@link CancelException}
   *        and calls the <code>handleCancelled()</code> method, providing
   *        a place for custom cancel processing.</li>
   * </ul>
   * <p>
   * Implementations need to provide:
   * <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()</code> method.
   * </ul>
   * <p>
   * Two possible scenarios are envisaged for cancellation:
   * <ul>
   *    <li><a href="#external">3.1 External / Mult-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.
   *
   * <a name="external"></a>
   * <h4>3.1 External / Multi-threaded</h4>
   *
   * This example provides a public <code>cancel()</code> 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
   * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#36930">
   * volatile</a> flag to ensure it will work properly in a multi-threaded environment.
   * The flag is returned by the <code>handleIsCancelled()</code> method, which
   * will cause the walk to stop immediately. The <code>handleCancelled()</code>
   * method will be the next, and last, callback method received once cancellation
   * has occurred.
   *
   * <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>
   *
   * <a name="internal"></a>
   * <h4>3.2 Internal</h4>
   *
   * This shows an example of how internal cancellation processing could be implemented.
   * <b>Note</b> the decision logic and throwing a {@link CancelException} could be implemented
   * in any of the <i>lifecycle</i> methods. 
   *
   * <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>
   *
   * @since 1.3
   * @version $Id: DirectoryWalker.java 1307459 2012-03-30 15:11:44Z ggregory $
   */
  public abstract class DirectoryWalker<T> {
  
      /**
       * 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 <i>depth</i>.
       */
      protected DirectoryWalker() {
          this(null, -1);
      }
  
      /**
       * Construct an instance with a filter and limit the <i>depth</i> 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.
       *
       * @param filter  the filter to apply, null means visit all files
       * @param depthLimit  controls how <i>deep</i> the hierarchy is
       *  navigated to (less than 0 means unlimited)
       */
      protected DirectoryWalker(FileFilter filter, int depthLimit) {
          this.filter = filter;
          this.depthLimit = depthLimit;
      }
  
      /**
       * Construct an instance with a directory and a file filter and an optional
       * limit on the <i>depth</i> 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.
       *
       * @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 <i>deep</i> the hierarchy is
       *  navigated to (less than 0 means unlimited)
       */
      protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, 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 = FileFilterUtils.or(directoryFilter, fileFilter);
          }
          this.depthLimit = depthLimit;
      }
  
      //-----------------------------------------------------------------------
      /**
       * 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>
       * 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</code>.
       *
       * @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(File startDirectory, Collection<T> results) throws IOException {
          if (startDirectory == null) {
              throw new NullPointerException("Start Directory is null");
          }
          try {
              handleStart(startDirectory, results);
              walk(startDirectory, 0, results);
              handleEnd(results);
          } catch(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(File directory, int depth, Collection<T> results) throws IOException {
          checkIfCancelled(directory, depth, results);
          if (handleDirectory(directory, depth, results)) {
              handleDirectoryStart(directory, depth, results);
              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 (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</code> 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.
       * 
       * @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(File file, int depth, Collection<T> 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.
       * <p>
       * This method should be implemented by those subclasses that want to
       * provide a public <code>cancel()</code> method available from another
       * thread. The design pattern for the subclass should be as follows:
       * <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>
       * 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
       */
      protected boolean handleIsCancelled(
              File file, int depth, Collection<T> 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.
       * <p>
       * 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(File startDirectory, Collection<T> results,
                         CancelException cancel) throws IOException {
          // re-throw exception - overridable by subclass
          throw cancel;
      }
  
      //-----------------------------------------------------------------------
      /**
       * Overridable callback method invoked at the start of processing.
       * <p>
       * 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
       */
      protected void handleStart(File startDirectory, Collection<T> results) throws IOException {
          // do nothing - overridable by subclass
      }
  
      /**
       * 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>
       * 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
       */
      protected boolean handleDirectory(File directory, int depth, Collection<T> results) throws IOException {
          // do nothing - overridable by subclass
          return true;  // process directory
      }
  
      /**
       * Overridable callback method invoked at the start of processing each directory.
       * <p>
       * 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
       */
      protected void handleDirectoryStart(File directory, int depth, Collection<T> results) throws IOException {
          // do nothing - overridable by subclass
      }
  
      /**
       * Overridable callback method invoked with the contents of each directory.
       * <p>
       * 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
       * @return the filtered list of files
       * @throws IOException if an I/O Error occurs
       * @since 2.0
       */
      protected File[] filterDirectoryContents(File directory, int depth, File[] files) throws IOException {
          return files;
      }
  
      /**
       * Overridable callback method invoked for each (non-directory) file.
       * <p>
       * 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
       */
      protected void handleFile(File file, int depth, Collection<T> results) throws IOException {
          // do nothing - overridable by subclass
      }
  
      /**
       * Overridable callback method invoked for each restricted directory.
       * <p>
       * 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
       */
      protected void handleRestricted(File directory, int depth, Collection<T> results) throws IOException  {
          // do nothing - overridable by subclass
      }
  
      /**
       * Overridable callback method invoked at the end of processing each directory.
       * <p>
       * 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
       */
      protected void handleDirectoryEnd(File directory, int depth, Collection<T> results) throws IOException {
          // do nothing - overridable by subclass
      }
  
      /**
       * Overridable callback method invoked at the end of processing.
       * <p>
       * This implementation does nothing.
       *
       * @param results  the collection of result objects, may be updated
       * @throws IOException if an I/O Error occurs
       */
      protected void handleEnd(Collection<T> 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</code> 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(File file, int depth) {
              this("Operation Cancelled", file, depth);
          }
  
          /**
           * Constructs a <code>CancelException</code> 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(String message, File file, int depth) {
              super(message);
              this.file = file;
              this.depth = depth;
          }
  
          /**
           * Return the file when the operation was cancelled.
           *
           * @return the file when the operation was cancelled
           */
          public File getFile() {
              return file;
          }
  
          /**
           * Return the depth when the operation was cancelled.
           *
           * @return the depth when the operation was cancelled
           */
          public int getDepth() {
              return depth;
          }
      }
  }
  