Home | History | Annotate | Download | only in io
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 package org.apache.commons.io;
     18 
     19 import java.io.File;
     20 import java.io.FileFilter;
     21 import java.io.IOException;
     22 import java.util.Collection;
     23 
     24 import org.apache.commons.io.filefilter.FileFilterUtils;
     25 import org.apache.commons.io.filefilter.IOFileFilter;
     26 import org.apache.commons.io.filefilter.TrueFileFilter;
     27 
     28 /**
     29  * Abstract class that walks through a directory hierarchy and provides
     30  * subclasses with convenient hooks to add specific behaviour.
     31  * <p>
     32  * This class operates with a {@link FileFilter} and maximum depth to
     33  * limit the files and direcories visited.
     34  * Commons IO supplies many common filter implementations in the
     35  * <a href="filefilter/package-summary.html"> filefilter</a> package.
     36  * <p>
     37  * The following sections describe:
     38  *   <ul>
     39  *      <li><a href="#example">1. Example Implementation</a> - example
     40  *          <code>FileCleaner</code> implementation.</li>
     41  *      <li><a href="#filter">2. Filter Example</a> - using
     42  *          {@link FileFilter}(s) with <code>DirectoryWalker</code>.</li>
     43  *      <li><a href="#cancel">3. Cancellation</a> - how to implement cancellation
     44  *          behaviour.</li>
     45  *   </ul>
     46  *
     47  * <a name="example"></a>
     48  * <h3>1. Example Implementation</h3>
     49  *
     50  * There are many possible extensions, for example, to delete all
     51  * files and '.svn' directories, and return a list of deleted files:
     52  * <pre>
     53  *  public class FileCleaner extends DirectoryWalker {
     54  *
     55  *    public FileCleaner() {
     56  *      super();
     57  *    }
     58  *
     59  *    public List clean(File startDirectory) {
     60  *      List results = new ArrayList();
     61  *      walk(startDirectory, results);
     62  *      return results;
     63  *    }
     64  *
     65  *    protected boolean handleDirectory(File directory, int depth, Collection results) {
     66  *      // delete svn directories and then skip
     67  *      if (".svn".equals(directory.getName())) {
     68  *        directory.delete();
     69  *        return false;
     70  *      } else {
     71  *        return true;
     72  *      }
     73  *
     74  *    }
     75  *
     76  *    protected void handleFile(File file, int depth, Collection results) {
     77  *      // delete file and add to list of deleted
     78  *      file.delete();
     79  *      results.add(file);
     80  *    }
     81  *  }
     82  * </pre>
     83  *
     84  * <a name="filter"></a>
     85  * <h3>2. Filter Example</h3>
     86  *
     87  * Choosing which directories and files to process can be a key aspect
     88  * of using this class. This information can be setup in three ways,
     89  * via three different constructors.
     90  * <p>
     91  * The first option is to visit all directories and files.
     92  * This is achieved via the no-args constructor.
     93  * <p>
     94  * The second constructor option is to supply a single {@link FileFilter}
     95  * that describes the files and directories to visit. Care must be taken
     96  * with this option as the same filter is used for both directories
     97  * and files.
     98  * <p>
     99  * For example, if you wanted all directories which are not hidden
    100  * and files which end in ".txt":
    101  * <pre>
    102  *  public class FooDirectoryWalker extends DirectoryWalker {
    103  *    public FooDirectoryWalker(FileFilter filter) {
    104  *      super(filter, -1);
    105  *    }
    106  *  }
    107  *
    108  *  // Build up the filters and create the walker
    109  *    // Create a filter for Non-hidden directories
    110  *    IOFileFilter fooDirFilter =
    111  *        FileFilterUtils.andFileFilter(FileFilterUtils.directoryFileFilter,
    112  *                                      HiddenFileFilter.VISIBLE);
    113  *
    114  *    // Create a filter for Files ending in ".txt"
    115  *    IOFileFilter fooFileFilter =
    116  *        FileFilterUtils.andFileFilter(FileFilterUtils.fileFileFilter,
    117  *                                      FileFilterUtils.suffixFileFilter(".txt"));
    118  *
    119  *    // Combine the directory and file filters using an OR condition
    120  *    java.io.FileFilter fooFilter =
    121  *        FileFilterUtils.orFileFilter(fooDirFilter, fooFileFilter);
    122  *
    123  *    // Use the filter to construct a DirectoryWalker implementation
    124  *    FooDirectoryWalker walker = new FooDirectoryWalker(fooFilter);
    125  * </pre>
    126  * <p>
    127  * The third constructor option is to specify separate filters, one for
    128  * directories and one for files. These are combined internally to form
    129  * the correct <code>FileFilter</code>, something which is very easy to
    130  * get wrong when attempted manually, particularly when trying to
    131  * express constructs like 'any file in directories named docs'.
    132  * <p>
    133  * For example, if you wanted all directories which are not hidden
    134  * and files which end in ".txt":
    135  * <pre>
    136  *  public class FooDirectoryWalker extends DirectoryWalker {
    137  *    public FooDirectoryWalker(IOFileFilter dirFilter, IOFileFilter fileFilter) {
    138  *      super(dirFilter, fileFilter, -1);
    139  *    }
    140  *  }
    141  *
    142  *  // Use the filters to construct the walker
    143  *  FooDirectoryWalker walker = new FooDirectoryWalker(
    144  *    HiddenFileFilter.VISIBLE,
    145  *    FileFilterUtils.suffixFileFilter(".txt"),
    146  *  );
    147  * </pre>
    148  * This is much simpler than the previous example, and is why it is the preferred
    149  * option for filtering.
    150  *
    151  * <a name="cancel"></a>
    152  * <h3>3. Cancellation</h3>
    153  *
    154  * The DirectoryWalker contains some of the logic required for cancel processing.
    155  * Subclasses must complete the implementation.
    156  * <p>
    157  * What <code>DirectoryWalker</code> does provide for cancellation is:
    158  * <ul>
    159  *    <li>{@link CancelException} which can be thrown in any of the
    160  *        <i>lifecycle</i> methods to stop processing.</li>
    161  *    <li>The <code>walk()</code> method traps thrown {@link CancelException}
    162  *        and calls the <code>handleCancelled()</code> method, providing
    163  *        a place for custom cancel processing.</li>
    164  * </ul>
    165  * <p>
    166  * Implementations need to provide:
    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()</code> method.
    171  * </ul>
    172  * <p>
    173  * Two possible scenarios are envisaged for cancellation:
    174  * <ul>
    175  *    <li><a href="#external">3.1 External / Mult-threaded</a> - cancellation being
    176  *        decided/initiated by an external process.</li>
    177  *    <li><a href="#internal">3.2 Internal</a> - cancellation being decided/initiated
    178  *        from within a DirectoryWalker implementation.</li>
    179  * </ul>
    180  * <p>
    181  * The following sections provide example implementations for these two different
    182  * scenarios.
    183  *
    184  * <a name="external"></a>
    185  * <h4>3.1 External / Multi-threaded</h4>
    186  *
    187  * This example provides a public <code>cancel()</code> method that can be
    188  * called by another thread to stop the processing. A typical example use-case
    189  * would be a cancel button on a GUI. Calling this method sets a
    190  * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#36930">
    191  * volatile</a> flag to ensure it will work properly in a multi-threaded environment.
    192  * The flag is returned by the <code>handleIsCancelled()</code> method, which
    193  * will cause the walk to stop immediately. The <code>handleCancelled()</code>
    194  * method will be the next, and last, callback method received once cancellation
    195  * has occurred.
    196  *
    197  * <pre>
    198  *  public class FooDirectoryWalker extends DirectoryWalker {
    199  *
    200  *    private volatile boolean cancelled = false;
    201  *
    202  *    public void cancel() {
    203  *        cancelled = true;
    204  *    }
    205  *
    206  *    private void handleIsCancelled(File file, int depth, Collection results) {
    207  *        return cancelled;
    208  *    }
    209  *
    210  *    protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
    211  *        // implement processing required when a cancellation occurs
    212  *    }
    213  *  }
    214  * </pre>
    215  *
    216  * <a name="internal"></a>
    217  * <h4>3.2 Internal</h4>
    218  *
    219  * This shows an example of how internal cancellation processing could be implemented.
    220  * <b>Note</b> the decision logic and throwing a {@link CancelException} could be implemented
    221  * in any of the <i>lifecycle</i> methods.
    222  *
    223  * <pre>
    224  *  public class BarDirectoryWalker extends DirectoryWalker {
    225  *
    226  *    protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException {
    227  *        // cancel if hidden directory
    228  *        if (directory.isHidden()) {
    229  *            throw new CancelException(file, depth);
    230  *        }
    231  *        return true;
    232  *    }
    233  *
    234  *    protected void handleFile(File file, int depth, Collection results) throws IOException {
    235  *        // cancel if read-only file
    236  *        if (!file.canWrite()) {
    237  *            throw new CancelException(file, depth);
    238  *        }
    239  *        results.add(file);
    240  *    }
    241  *
    242  *    protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) {
    243  *        // implement processing required when a cancellation occurs
    244  *    }
    245  *  }
    246  * </pre>
    247  *
    248  * @since Commons IO 1.3
    249  * @version $Revision: 424748 $
    250  */
    251 public abstract class DirectoryWalker {
    252 
    253     /**
    254      * The file filter to use to filter files and directories.
    255      */
    256     private final FileFilter filter;
    257     /**
    258      * The limit on the directory depth to walk.
    259      */
    260     private final int depthLimit;
    261 
    262     /**
    263      * Construct an instance with no filtering and unlimited <i>depth</i>.
    264      */
    265     protected DirectoryWalker() {
    266         this(null, -1);
    267     }
    268 
    269     /**
    270      * Construct an instance with a filter and limit the <i>depth</i> navigated to.
    271      * <p>
    272      * The filter controls which files and directories will be navigated to as
    273      * part of the walk. The {@link FileFilterUtils} class is useful for combining
    274      * various filters together. A <code>null</code> filter means that no
    275      * filtering should occur and all files and directories will be visited.
    276      *
    277      * @param filter  the filter to apply, null means visit all files
    278      * @param depthLimit  controls how <i>deep</i> the hierarchy is
    279      *  navigated to (less than 0 means unlimited)
    280      */
    281     protected DirectoryWalker(FileFilter filter, int depthLimit) {
    282         this.filter = filter;
    283         this.depthLimit = depthLimit;
    284     }
    285 
    286     /**
    287      * Construct an instance with a directory and a file filter and an optional
    288      * limit on the <i>depth</i> navigated to.
    289      * <p>
    290      * The filters control which files and directories will be navigated to as part
    291      * of the walk. This constructor uses {@link FileFilterUtils#makeDirectoryOnly(IOFileFilter)}
    292      * and {@link FileFilterUtils#makeFileOnly(IOFileFilter)} internally to combine the filters.
    293      * A <code>null</code> filter means that no filtering should occur.
    294      *
    295      * @param directoryFilter  the filter to apply to directories, null means visit all directories
    296      * @param fileFilter  the filter to apply to files, null means visit all files
    297      * @param depthLimit  controls how <i>deep</i> the hierarchy is
    298      *  navigated to (less than 0 means unlimited)
    299      */
    300     protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, int depthLimit) {
    301         if (directoryFilter == null && fileFilter == null) {
    302             this.filter = null;
    303         } else {
    304             directoryFilter = (directoryFilter != null ? directoryFilter : TrueFileFilter.TRUE);
    305             fileFilter = (fileFilter != null ? fileFilter : TrueFileFilter.TRUE);
    306             directoryFilter = FileFilterUtils.makeDirectoryOnly(directoryFilter);
    307             fileFilter = FileFilterUtils.makeFileOnly(fileFilter);
    308             this.filter = FileFilterUtils.orFileFilter(directoryFilter, fileFilter);
    309         }
    310         this.depthLimit = depthLimit;
    311     }
    312 
    313     //-----------------------------------------------------------------------
    314     /**
    315      * Internal method that walks the directory hierarchy in a depth-first manner.
    316      * <p>
    317      * Users of this class do not need to call this method. This method will
    318      * be called automatically by another (public) method on the specific subclass.
    319      * <p>
    320      * Writers of subclasses should call this method to start the directory walk.
    321      * Once called, this method will emit events as it walks the hierarchy.
    322      * The event methods have the prefix <code>handle</code>.
    323      *
    324      * @param startDirectory  the directory to start from, not null
    325      * @param results  the collection of result objects, may be updated
    326      * @throws NullPointerException if the start directory is null
    327      * @throws IOException if an I/O Error occurs
    328      */
    329     protected final void walk(File startDirectory, Collection results) throws IOException {
    330         if (startDirectory == null) {
    331             throw new NullPointerException("Start Directory is null");
    332         }
    333         try {
    334             handleStart(startDirectory, results);
    335             walk(startDirectory, 0, results);
    336             handleEnd(results);
    337         } catch(CancelException cancel) {
    338             handleCancelled(startDirectory, results, cancel);
    339         }
    340     }
    341 
    342     /**
    343      * Main recursive method to examine the directory hierarchy.
    344      *
    345      * @param directory  the directory to examine, not null
    346      * @param depth  the directory level (starting directory = 0)
    347      * @param results  the collection of result objects, may be updated
    348      * @throws IOException if an I/O Error occurs
    349      */
    350     private void walk(File directory, int depth, Collection results) throws IOException {
    351         checkIfCancelled(directory, depth, results);
    352         if (handleDirectory(directory, depth, results)) {
    353             handleDirectoryStart(directory, depth, results);
    354             int childDepth = depth + 1;
    355             if (depthLimit < 0 || childDepth <= depthLimit) {
    356                 checkIfCancelled(directory, depth, results);
    357                 File[] childFiles = (filter == null ? directory.listFiles() : directory.listFiles(filter));
    358                 if (childFiles == null) {
    359                     handleRestricted(directory, childDepth, results);
    360                 } else {
    361                     for (int i = 0; i < childFiles.length; i++) {
    362                         File childFile = childFiles[i];
    363                         if (childFile.isDirectory()) {
    364                             walk(childFile, childDepth, results);
    365                         } else {
    366                             checkIfCancelled(childFile, childDepth, results);
    367                             handleFile(childFile, childDepth, results);
    368                             checkIfCancelled(childFile, childDepth, results);
    369                         }
    370                     }
    371                 }
    372             }
    373             handleDirectoryEnd(directory, depth, results);
    374         }
    375         checkIfCancelled(directory, depth, results);
    376     }
    377 
    378     //-----------------------------------------------------------------------
    379     /**
    380      * Checks whether the walk has been cancelled by calling {@link #handleIsCancelled},
    381      * throwing a <code>CancelException</code> if it has.
    382      * <p>
    383      * Writers of subclasses should not normally call this method as it is called
    384      * automatically by the walk of the tree. However, sometimes a single method,
    385      * typically {@link #handleFile}, may take a long time to run. In that case,
    386      * you may wish to check for cancellation by calling this method.
    387      *
    388      * @param file  the current file being processed
    389      * @param depth  the current file level (starting directory = 0)
    390      * @param results  the collection of result objects, may be updated
    391      * @throws IOException if an I/O Error occurs
    392      */
    393     protected final void checkIfCancelled(File file, int depth, Collection results) throws IOException {
    394         if (handleIsCancelled(file, depth, results)) {
    395             throw new CancelException(file, depth);
    396         }
    397     }
    398 
    399     /**
    400      * Overridable callback method invoked to determine if the entire walk
    401      * operation should be immediately cancelled.
    402      * <p>
    403      * This method should be implemented by those subclasses that want to
    404      * provide a public <code>cancel()</code> method available from another
    405      * thread. The design pattern for the subclass should be as follows:
    406      * <pre>
    407      *  public class FooDirectoryWalker extends DirectoryWalker {
    408      *    private volatile boolean cancelled = false;
    409      *
    410      *    public void cancel() {
    411      *        cancelled = true;
    412      *    }
    413      *    private void handleIsCancelled(File file, int depth, Collection results) {
    414      *        return cancelled;
    415      *    }
    416      *    protected void handleCancelled(File startDirectory,
    417      *              Collection results, CancelException cancel) {
    418      *        // implement processing required when a cancellation occurs
    419      *    }
    420      *  }
    421      * </pre>
    422      * <p>
    423      * If this method returns true, then the directory walk is immediately
    424      * cancelled. The next callback method will be {@link #handleCancelled}.
    425      * <p>
    426      * This implementation returns false.
    427      *
    428      * @param file  the file or directory being processed
    429      * @param depth  the current directory level (starting directory = 0)
    430      * @param results  the collection of result objects, may be updated
    431      * @return true if the walk has been cancelled
    432      * @throws IOException if an I/O Error occurs
    433      */
    434     protected boolean handleIsCancelled(
    435             File file, int depth, Collection results) throws IOException {
    436         // do nothing - overridable by subclass
    437         return false;  // not cancelled
    438     }
    439 
    440     /**
    441      * Overridable callback method invoked when the operation is cancelled.
    442      * The file being processed when the cancellation occurred can be
    443      * obtained from the exception.
    444      * <p>
    445      * This implementation just re-throws the {@link CancelException}.
    446      *
    447      * @param startDirectory  the directory that the walk started from
    448      * @param results  the collection of result objects, may be updated
    449      * @param cancel  the exception throw to cancel further processing
    450      * containing details at the point of cancellation.
    451      * @throws IOException if an I/O Error occurs
    452      */
    453     protected void handleCancelled(File startDirectory, Collection results,
    454                        CancelException cancel) throws IOException {
    455         // re-throw exception - overridable by subclass
    456         throw cancel;
    457     }
    458 
    459     //-----------------------------------------------------------------------
    460     /**
    461      * Overridable callback method invoked at the start of processing.
    462      * <p>
    463      * This implementation does nothing.
    464      *
    465      * @param startDirectory  the directory to start from
    466      * @param results  the collection of result objects, may be updated
    467      * @throws IOException if an I/O Error occurs
    468      */
    469     protected void handleStart(File startDirectory, Collection results) throws IOException {
    470         // do nothing - overridable by subclass
    471     }
    472 
    473     /**
    474      * Overridable callback method invoked to determine if a directory should be processed.
    475      * <p>
    476      * This method returns a boolean to indicate if the directory should be examined or not.
    477      * If you return false, the entire directory and any subdirectories will be skipped.
    478      * Note that this functionality is in addition to the filtering by file filter.
    479      * <p>
    480      * This implementation does nothing and returns true.
    481      *
    482      * @param directory  the current directory being processed
    483      * @param depth  the current directory level (starting directory = 0)
    484      * @param results  the collection of result objects, may be updated
    485      * @return true to process this directory, false to skip this directory
    486      * @throws IOException if an I/O Error occurs
    487      */
    488     protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException {
    489         // do nothing - overridable by subclass
    490         return true;  // process directory
    491     }
    492 
    493     /**
    494      * Overridable callback method invoked at the start of processing each directory.
    495      * <p>
    496      * This implementation does nothing.
    497      *
    498      * @param directory  the current directory being processed
    499      * @param depth  the current directory level (starting directory = 0)
    500      * @param results  the collection of result objects, may be updated
    501      * @throws IOException if an I/O Error occurs
    502      */
    503     protected void handleDirectoryStart(File directory, int depth, Collection results) throws IOException {
    504         // do nothing - overridable by subclass
    505     }
    506 
    507     /**
    508      * Overridable callback method invoked for each (non-directory) file.
    509      * <p>
    510      * This implementation does nothing.
    511      *
    512      * @param file  the current file being processed
    513      * @param depth  the current directory level (starting directory = 0)
    514      * @param results  the collection of result objects, may be updated
    515      * @throws IOException if an I/O Error occurs
    516      */
    517     protected void handleFile(File file, int depth, Collection results) throws IOException {
    518         // do nothing - overridable by subclass
    519     }
    520 
    521     /**
    522      * Overridable callback method invoked for each restricted directory.
    523      * <p>
    524      * This implementation does nothing.
    525      *
    526      * @param directory  the restricted directory
    527      * @param depth  the current directory level (starting directory = 0)
    528      * @param results  the collection of result objects, may be updated
    529      * @throws IOException if an I/O Error occurs
    530      */
    531     protected void handleRestricted(File directory, int depth, Collection results) throws IOException  {
    532         // do nothing - overridable by subclass
    533     }
    534 
    535     /**
    536      * Overridable callback method invoked at the end of processing each directory.
    537      * <p>
    538      * This implementation does nothing.
    539      *
    540      * @param directory  the directory being processed
    541      * @param depth  the current directory level (starting directory = 0)
    542      * @param results  the collection of result objects, may be updated
    543      * @throws IOException if an I/O Error occurs
    544      */
    545     protected void handleDirectoryEnd(File directory, int depth, Collection results) throws IOException {
    546         // do nothing - overridable by subclass
    547     }
    548 
    549     /**
    550      * Overridable callback method invoked at the end of processing.
    551      * <p>
    552      * This implementation does nothing.
    553      *
    554      * @param results  the collection of result objects, may be updated
    555      * @throws IOException if an I/O Error occurs
    556      */
    557     protected void handleEnd(Collection results) throws IOException {
    558         // do nothing - overridable by subclass
    559     }
    560 
    561     //-----------------------------------------------------------------------
    562     /**
    563      * CancelException is thrown in DirectoryWalker to cancel the current
    564      * processing.
    565      */
    566     public static class CancelException extends IOException {
    567 
    568         /** Serialization id. */
    569         private static final long serialVersionUID = 1347339620135041008L;
    570 
    571         /** The file being processed when the exception was thrown. */
    572         private File file;
    573         /** The file depth when the exception was thrown. */
    574         private int depth = -1;
    575 
    576         /**
    577          * Constructs a <code>CancelException</code> with
    578          * the file and depth when cancellation occurred.
    579          *
    580          * @param file  the file when the operation was cancelled, may be null
    581          * @param depth  the depth when the operation was cancelled, may be null
    582          */
    583         public CancelException(File file, int depth) {
    584             this("Operation Cancelled", file, depth);
    585         }
    586 
    587         /**
    588          * Constructs a <code>CancelException</code> with
    589          * an appropriate message and the file and depth when
    590          * cancellation occurred.
    591          *
    592          * @param message  the detail message
    593          * @param file  the file when the operation was cancelled
    594          * @param depth  the depth when the operation was cancelled
    595          */
    596         public CancelException(String message, File file, int depth) {
    597             super(message);
    598             this.file = file;
    599             this.depth = depth;
    600         }
    601 
    602         /**
    603          * Return the file when the operation was cancelled.
    604          *
    605          * @return the file when the operation was cancelled
    606          */
    607         public File getFile() {
    608             return file;
    609         }
    610 
    611         /**
    612          * Return the depth when the operation was cancelled.
    613          *
    614          * @return the depth when the operation was cancelled
    615          */
    616         public int getDepth() {
    617             return depth;
    618         }
    619     }
    620 }
    621