001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.io; 018 019import java.io.File; 020import java.io.FileFilter; 021import java.io.IOException; 022import java.nio.file.Files; 023import java.util.Collection; 024import java.util.Objects; 025 026import org.apache.commons.io.file.PathUtils; 027import org.apache.commons.io.filefilter.FileFilterUtils; 028import org.apache.commons.io.filefilter.IOFileFilter; 029import org.apache.commons.io.filefilter.TrueFileFilter; 030 031/** 032 * Abstract class that walks through a directory hierarchy and provides subclasses with convenient hooks to add specific 033 * behavior. 034 * <p> 035 * This class operates with a {@link FileFilter} and maximum depth to limit the files and directories visited. Commons 036 * IO supplies many common filter implementations in the <a href="filefilter/package-summary.html"> filefilter</a> 037 * package. 038 * </p> 039 * <p> 040 * The following sections describe: 041 * </p> 042 * <ul> 043 * <li><a href="#example">1. Example Implementation</a> - example {@code FileCleaner} implementation.</li> 044 * <li><a href="#filter">2. Filter Example</a> - using {@link FileFilter}(s) with {@code DirectoryWalker}.</li> 045 * <li><a href="#cancel">3. Cancellation</a> - how to implement cancellation behavior.</li> 046 * </ul> 047 * 048 * <h2 id="example">1. Example Implementation</h2> 049 * 050 * There are many possible extensions, for example, to delete all files and '.svn' directories, and return a list of 051 * deleted files: 052 * 053 * <pre> 054 * public class FileCleaner extends DirectoryWalker { 055 * 056 * public FileCleaner() { 057 * super(); 058 * } 059 * 060 * public List clean(File startDirectory) { 061 * List results = new ArrayList(); 062 * walk(startDirectory, results); 063 * return results; 064 * } 065 * 066 * protected boolean handleDirectory(File directory, int depth, Collection results) { 067 * // delete svn directories and then skip 068 * if (".svn".equals(directory.getName())) { 069 * directory.delete(); 070 * return false; 071 * } else { 072 * return true; 073 * } 074 * 075 * } 076 * 077 * protected void handleFile(File file, int depth, Collection results) { 078 * // delete file and add to list of deleted 079 * file.delete(); 080 * results.add(file); 081 * } 082 * } 083 * </pre> 084 * 085 * <h2 id="filter">2. Filter Example</h2> 086 * 087 * <p> 088 * Choosing which directories and files to process can be a key aspect of using this class. This information can be 089 * setup in three ways, via three different constructors. 090 * </p> 091 * <p> 092 * The first option is to visit all directories and files. This is achieved via the no-args constructor. 093 * </p> 094 * <p> 095 * The second constructor option is to supply a single {@link FileFilter} that describes the files and directories to 096 * visit. Care must be taken with this option as the same filter is used for both directories and files. 097 * </p> 098 * <p> 099 * For example, if you wanted all directories which are not hidden and files which end in ".txt": 100 * </p> 101 * 102 * <pre> 103 * public class FooDirectoryWalker extends DirectoryWalker { 104 * public FooDirectoryWalker(FileFilter filter) { 105 * super(filter, -1); 106 * } 107 * } 108 * 109 * // Build up the filters and create the walker 110 * // Create a filter for Non-hidden directories 111 * IOFileFilter fooDirFilter = FileFilterUtils.andFileFilter(FileFilterUtils.directoryFileFilter, 112 * HiddenFileFilter.VISIBLE); 113 * 114 * // Create a filter for Files ending in ".txt" 115 * IOFileFilter fooFileFilter = FileFilterUtils.andFileFilter(FileFilterUtils.fileFileFilter, 116 * FileFilterUtils.suffixFileFilter(".txt")); 117 * 118 * // Combine the directory and file filters using an OR condition 119 * java.io.FileFilter fooFilter = FileFilterUtils.orFileFilter(fooDirFilter, fooFileFilter); 120 * 121 * // Use the filter to construct a DirectoryWalker implementation 122 * FooDirectoryWalker walker = new FooDirectoryWalker(fooFilter); 123 * </pre> 124 * <p> 125 * The third constructor option is to specify separate filters, one for directories and one for files. These are 126 * combined internally to form the correct {@code FileFilter}, something which is very easy to get wrong when 127 * attempted manually, particularly when trying to express constructs like 'any file in directories named docs'. 128 * </p> 129 * <p> 130 * For example, if you wanted all directories which are not hidden and files which end in ".txt": 131 * </p> 132 * 133 * <pre> 134 * public class FooDirectoryWalker extends DirectoryWalker { 135 * public FooDirectoryWalker(IOFileFilter dirFilter, IOFileFilter fileFilter) { 136 * super(dirFilter, fileFilter, -1); 137 * } 138 * } 139 * 140 * // Use the filters to construct the walker 141 * FooDirectoryWalker walker = new FooDirectoryWalker( 142 * HiddenFileFilter.VISIBLE, 143 * FileFilterUtils.suffixFileFilter(".txt"), 144 * ); 145 * </pre> 146 * <p> 147 * This is much simpler than the previous example, and is why it is the preferred option for filtering. 148 * </p> 149 * 150 * <h2 id="cancel">3. Cancellation</h2> 151 * 152 * <p> 153 * The DirectoryWalker contains some of the logic required for cancel processing. Subclasses must complete the 154 * implementation. 155 * </p> 156 * <p> 157 * What {@code DirectoryWalker} does provide for cancellation is: 158 * </p> 159 * <ul> 160 * <li>{@link CancelException} which can be thrown in any of the <i>lifecycle</i> methods to stop processing.</li> 161 * <li>The {@code walk()} method traps thrown {@link CancelException} and calls the {@code handleCancelled()} 162 * method, providing a place for custom cancel processing.</li> 163 * </ul> 164 * <p> 165 * Implementations need to provide: 166 * </p> 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()} method. 171 * </ul> 172 * <p> 173 * Two possible scenarios are envisaged for cancellation: 174 * </p> 175 * <ul> 176 * <li><a href="#external">3.1 External / Multi-threaded</a> - cancellation being decided/initiated by an external 177 * process.</li> 178 * <li><a href="#internal">3.2 Internal</a> - cancellation being decided/initiated from within a DirectoryWalker 179 * implementation.</li> 180 * </ul> 181 * <p> 182 * The following sections provide example implementations for these two different scenarios. 183 * </p> 184 * 185 * <h3 id="external">3.1 External / Multi-threaded</h3> 186 * 187 * <p> 188 * This example provides a public {@code cancel()} method that can be called by another thread to stop the 189 * processing. A typical example use-case 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"> volatile</a> flag to ensure 191 * it will work properly in a multi-threaded environment. The flag is returned by the {@code handleIsCancelled()} 192 * method, which will cause the walk to stop immediately. The {@code handleCancelled()} method will be the next, 193 * and last, callback method received once cancellation has occurred. 194 * </p> 195 * 196 * <pre> 197 * public class FooDirectoryWalker extends DirectoryWalker { 198 * 199 * private volatile boolean cancelled = false; 200 * 201 * public void cancel() { 202 * cancelled = true; 203 * } 204 * 205 * protected boolean handleIsCancelled(File file, int depth, Collection results) { 206 * return cancelled; 207 * } 208 * 209 * protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) { 210 * // implement processing required when a cancellation occurs 211 * } 212 * } 213 * </pre> 214 * 215 * <h3 id="internal">3.2 Internal</h3> 216 * 217 * <p> 218 * This shows an example of how internal cancellation processing could be implemented. <b>Note</b> the decision logic 219 * and throwing a {@link CancelException} could be implemented in any of the <i>lifecycle</i> methods. 220 * </p> 221 * 222 * <pre> 223 * public class BarDirectoryWalker extends DirectoryWalker { 224 * 225 * protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException { 226 * // cancel if hidden directory 227 * if (directory.isHidden()) { 228 * throw new CancelException(file, depth); 229 * } 230 * return true; 231 * } 232 * 233 * protected void handleFile(File file, int depth, Collection results) throws IOException { 234 * // cancel if read-only file 235 * if (!file.canWrite()) { 236 * throw new CancelException(file, depth); 237 * } 238 * results.add(file); 239 * } 240 * 241 * protected void handleCancelled(File startDirectory, Collection results, CancelException cancel) { 242 * // implement processing required when a cancellation occurs 243 * } 244 * } 245 * </pre> 246 * 247 * @param <T> The result type, like {@link File}. 248 * @since 1.3 249 * @deprecated Apache Commons IO no longer uses this class. Instead, use 250 * {@link PathUtils#walk(java.nio.file.Path, org.apache.commons.io.file.PathFilter, int, boolean, java.nio.file.FileVisitOption...)} 251 * or {@link Files#walkFileTree(java.nio.file.Path, java.util.Set, int, java.nio.file.FileVisitor)}, and 252 * friends. 253 */ 254@Deprecated 255public abstract class DirectoryWalker<T> { 256 257 /** 258 * The file filter to use to filter files and directories. 259 */ 260 private final FileFilter filter; 261 /** 262 * The limit on the directory depth to walk. 263 */ 264 private final int depthLimit; 265 266 /** 267 * Construct an instance with no filtering and unlimited <i>depth</i>. 268 */ 269 protected DirectoryWalker() { 270 this(null, -1); 271 } 272 273 /** 274 * Constructs an instance with a filter and limit the <i>depth</i> navigated to. 275 * <p> 276 * The filter controls which files and directories will be navigated to as 277 * part of the walk. The {@link FileFilterUtils} class is useful for combining 278 * various filters together. A {@code null} filter means that no 279 * filtering should occur and all files and directories will be visited. 280 * </p> 281 * 282 * @param filter the filter to apply, null means visit all files 283 * @param depthLimit controls how <i>deep</i> the hierarchy is 284 * navigated to (less than 0 means unlimited) 285 */ 286 protected DirectoryWalker(final FileFilter filter, final int depthLimit) { 287 this.filter = filter; 288 this.depthLimit = depthLimit; 289 } 290 291 /** 292 * Constructs an instance with a directory and a file filter and an optional 293 * limit on the <i>depth</i> navigated to. 294 * <p> 295 * The filters control which files and directories will be navigated to as part 296 * of the walk. This constructor uses {@link FileFilterUtils#makeDirectoryOnly(IOFileFilter)} 297 * and {@link FileFilterUtils#makeFileOnly(IOFileFilter)} internally to combine the filters. 298 * A {@code null} filter means that no filtering should occur. 299 * </p> 300 * 301 * @param directoryFilter the filter to apply to directories, null means visit all directories 302 * @param fileFilter the filter to apply to files, null means visit all files 303 * @param depthLimit controls how <i>deep</i> the hierarchy is 304 * navigated to (less than 0 means unlimited) 305 */ 306 protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, final int depthLimit) { 307 if (directoryFilter == null && fileFilter == null) { 308 this.filter = null; 309 } else { 310 directoryFilter = directoryFilter != null ? directoryFilter : TrueFileFilter.TRUE; 311 fileFilter = fileFilter != null ? fileFilter : TrueFileFilter.TRUE; 312 directoryFilter = FileFilterUtils.makeDirectoryOnly(directoryFilter); 313 fileFilter = FileFilterUtils.makeFileOnly(fileFilter); 314 this.filter = directoryFilter.or(fileFilter); 315 } 316 this.depthLimit = depthLimit; 317 } 318 319 //----------------------------------------------------------------------- 320 /** 321 * Internal method that walks the directory hierarchy in a depth-first manner. 322 * <p> 323 * Users of this class do not need to call this method. This method will 324 * be called automatically by another (public) method on the specific subclass. 325 * </p> 326 * <p> 327 * Writers of subclasses should call this method to start the directory walk. 328 * Once called, this method will emit events as it walks the hierarchy. 329 * The event methods have the prefix {@code handle}. 330 * </p> 331 * 332 * @param startDirectory the directory to start from, not null 333 * @param results the collection of result objects, may be updated 334 * @throws NullPointerException if the start directory is null 335 * @throws IOException if an I/O Error occurs 336 */ 337 protected final void walk(final File startDirectory, final Collection<T> results) throws IOException { 338 Objects.requireNonNull(startDirectory, "startDirectory"); 339 try { 340 handleStart(startDirectory, results); 341 walk(startDirectory, 0, results); 342 handleEnd(results); 343 } catch(final CancelException cancel) { 344 handleCancelled(startDirectory, results, cancel); 345 } 346 } 347 348 /** 349 * Main recursive method to examine the directory hierarchy. 350 * 351 * @param directory the directory to examine, not null 352 * @param depth the directory level (starting directory = 0) 353 * @param results the collection of result objects, may be updated 354 * @throws IOException if an I/O Error occurs 355 */ 356 private void walk(final File directory, final int depth, final Collection<T> results) throws IOException { 357 checkIfCancelled(directory, depth, results); 358 if (handleDirectory(directory, depth, results)) { 359 handleDirectoryStart(directory, depth, results); 360 final int childDepth = depth + 1; 361 if (depthLimit < 0 || childDepth <= depthLimit) { 362 checkIfCancelled(directory, depth, results); 363 File[] childFiles = filter == null ? directory.listFiles() : directory.listFiles(filter); 364 childFiles = filterDirectoryContents(directory, depth, childFiles); 365 if (childFiles == null) { 366 handleRestricted(directory, childDepth, results); 367 } else { 368 for (final File childFile : childFiles) { 369 if (childFile.isDirectory()) { 370 walk(childFile, childDepth, results); 371 } else { 372 checkIfCancelled(childFile, childDepth, results); 373 handleFile(childFile, childDepth, results); 374 checkIfCancelled(childFile, childDepth, results); 375 } 376 } 377 } 378 } 379 handleDirectoryEnd(directory, depth, results); 380 } 381 checkIfCancelled(directory, depth, results); 382 } 383 384 //----------------------------------------------------------------------- 385 /** 386 * Checks whether the walk has been cancelled by calling {@link #handleIsCancelled}, 387 * throwing a {@code CancelException} if it has. 388 * <p> 389 * Writers of subclasses should not normally call this method as it is called 390 * automatically by the walk of the tree. However, sometimes a single method, 391 * typically {@link #handleFile}, may take a long time to run. In that case, 392 * you may wish to check for cancellation by calling this method. 393 * </p> 394 * 395 * @param file the current file being processed 396 * @param depth the current file level (starting directory = 0) 397 * @param results the collection of result objects, may be updated 398 * @throws IOException if an I/O Error occurs 399 */ 400 protected final void checkIfCancelled(final File file, final int depth, final Collection<T> results) throws 401 IOException { 402 if (handleIsCancelled(file, depth, results)) { 403 throw new CancelException(file, depth); 404 } 405 } 406 407 /** 408 * Overridable callback method invoked to determine if the entire walk 409 * operation should be immediately cancelled. 410 * <p> 411 * This method should be implemented by those subclasses that want to 412 * provide a public {@code cancel()} method available from another 413 * thread. The design pattern for the subclass should be as follows: 414 * </p> 415 * <pre> 416 * public class FooDirectoryWalker extends DirectoryWalker { 417 * private volatile boolean cancelled = false; 418 * 419 * public void cancel() { 420 * cancelled = true; 421 * } 422 * private void handleIsCancelled(File file, int depth, Collection results) { 423 * return cancelled; 424 * } 425 * protected void handleCancelled(File startDirectory, 426 * Collection results, CancelException cancel) { 427 * // implement processing required when a cancellation occurs 428 * } 429 * } 430 * </pre> 431 * <p> 432 * If this method returns true, then the directory walk is immediately 433 * cancelled. The next callback method will be {@link #handleCancelled}. 434 * </p> 435 * <p> 436 * This implementation returns false. 437 * </p> 438 * 439 * @param file the file or directory being processed 440 * @param depth the current directory level (starting directory = 0) 441 * @param results the collection of result objects, may be updated 442 * @return true if the walk has been cancelled 443 * @throws IOException if an I/O Error occurs 444 */ 445 @SuppressWarnings("unused") // Possibly thrown from subclasses. 446 protected boolean handleIsCancelled( 447 final File file, final int depth, final Collection<T> results) throws IOException { 448 // do nothing - overridable by subclass 449 return false; // not cancelled 450 } 451 452 /** 453 * Overridable callback method invoked when the operation is cancelled. 454 * The file being processed when the cancellation occurred can be 455 * obtained from the exception. 456 * <p> 457 * This implementation just re-throws the {@link CancelException}. 458 * </p> 459 * 460 * @param startDirectory the directory that the walk started from 461 * @param results the collection of result objects, may be updated 462 * @param cancel the exception throw to cancel further processing 463 * containing details at the point of cancellation. 464 * @throws IOException if an I/O Error occurs 465 */ 466 protected void handleCancelled(final File startDirectory, final Collection<T> results, 467 final CancelException cancel) throws IOException { 468 // re-throw exception - overridable by subclass 469 throw cancel; 470 } 471 472 //----------------------------------------------------------------------- 473 /** 474 * Overridable callback method invoked at the start of processing. 475 * <p> 476 * This implementation does nothing. 477 * </p> 478 * 479 * @param startDirectory the directory to start from 480 * @param results the collection of result objects, may be updated 481 * @throws IOException if an I/O Error occurs 482 */ 483 @SuppressWarnings("unused") // Possibly thrown from subclasses. 484 protected void handleStart(final File startDirectory, final Collection<T> results) throws IOException { 485 // do nothing - overridable by subclass 486 } 487 488 /** 489 * Overridable callback method invoked to determine if a directory should be processed. 490 * <p> 491 * This method returns a boolean to indicate if the directory should be examined or not. 492 * If you return false, the entire directory and any subdirectories will be skipped. 493 * Note that this functionality is in addition to the filtering by file filter. 494 * </p> 495 * <p> 496 * This implementation does nothing and returns true. 497 * </p> 498 * 499 * @param directory the current directory being processed 500 * @param depth the current directory level (starting directory = 0) 501 * @param results the collection of result objects, may be updated 502 * @return true to process this directory, false to skip this directory 503 * @throws IOException if an I/O Error occurs 504 */ 505 @SuppressWarnings("unused") // Possibly thrown from subclasses. 506 protected boolean handleDirectory(final File directory, final int depth, final Collection<T> results) throws 507 IOException { 508 // do nothing - overridable by subclass 509 return true; // process directory 510 } 511 512 /** 513 * Overridable callback method invoked at the start of processing each directory. 514 * <p> 515 * This implementation does nothing. 516 * </p> 517 * 518 * @param directory the current directory being processed 519 * @param depth the current directory level (starting directory = 0) 520 * @param results the collection of result objects, may be updated 521 * @throws IOException if an I/O Error occurs 522 */ 523 @SuppressWarnings("unused") // Possibly thrown from subclasses. 524 protected void handleDirectoryStart(final File directory, final int depth, final Collection<T> results) throws 525 IOException { 526 // do nothing - overridable by subclass 527 } 528 529 /** 530 * Overridable callback method invoked with the contents of each directory. 531 * <p> 532 * This implementation returns the files unchanged 533 * </p> 534 * 535 * @param directory the current directory being processed 536 * @param depth the current directory level (starting directory = 0) 537 * @param files the files (possibly filtered) in the directory, may be {@code null} 538 * @return the filtered list of files 539 * @throws IOException if an I/O Error occurs 540 * @since 2.0 541 */ 542 @SuppressWarnings("unused") // Possibly thrown from subclasses. 543 protected File[] filterDirectoryContents(final File directory, final int depth, final File... files) throws 544 IOException { 545 return files; 546 } 547 548 /** 549 * Overridable callback method invoked for each (non-directory) file. 550 * <p> 551 * This implementation does nothing. 552 * </p> 553 * 554 * @param file the current file being processed 555 * @param depth the current directory level (starting directory = 0) 556 * @param results the collection of result objects, may be updated 557 * @throws IOException if an I/O Error occurs 558 */ 559 @SuppressWarnings("unused") // Possibly thrown from subclasses. 560 protected void handleFile(final File file, final int depth, final Collection<T> results) throws IOException { 561 // do nothing - overridable by subclass 562 } 563 564 /** 565 * Overridable callback method invoked for each restricted directory. 566 * <p> 567 * This implementation does nothing. 568 * </p> 569 * 570 * @param directory the restricted directory 571 * @param depth the current directory level (starting directory = 0) 572 * @param results the collection of result objects, may be updated 573 * @throws IOException if an I/O Error occurs 574 */ 575 @SuppressWarnings("unused") // Possibly thrown from subclasses. 576 protected void handleRestricted(final File directory, final int depth, final Collection<T> results) throws 577 IOException { 578 // do nothing - overridable by subclass 579 } 580 581 /** 582 * Overridable callback method invoked at the end of processing each directory. 583 * <p> 584 * This implementation does nothing. 585 * </p> 586 * 587 * @param directory the directory being processed 588 * @param depth the current directory level (starting directory = 0) 589 * @param results the collection of result objects, may be updated 590 * @throws IOException if an I/O Error occurs 591 */ 592 @SuppressWarnings("unused") // Possibly thrown from subclasses. 593 protected void handleDirectoryEnd(final File directory, final int depth, final Collection<T> results) throws 594 IOException { 595 // do nothing - overridable by subclass 596 } 597 598 /** 599 * Overridable callback method invoked at the end of processing. 600 * <p> 601 * This implementation does nothing. 602 * </p> 603 * 604 * @param results the collection of result objects, may be updated 605 * @throws IOException if an I/O Error occurs 606 */ 607 @SuppressWarnings("unused") // Possibly thrown from subclasses. 608 protected void handleEnd(final Collection<T> results) throws IOException { 609 // do nothing - overridable by subclass 610 } 611 612 //----------------------------------------------------------------------- 613 /** 614 * CancelException is thrown in DirectoryWalker to cancel the current 615 * processing. 616 */ 617 public static class CancelException extends IOException { 618 619 /** Serialization id. */ 620 private static final long serialVersionUID = 1347339620135041008L; 621 622 /** The file being processed when the exception was thrown. */ 623 private final File file; 624 /** The file depth when the exception was thrown. */ 625 private final int depth; 626 627 /** 628 * Constructs a {@code CancelException} with 629 * the file and depth when cancellation occurred. 630 * 631 * @param file the file when the operation was cancelled, may be null 632 * @param depth the depth when the operation was cancelled, may be null 633 */ 634 public CancelException(final File file, final int depth) { 635 this("Operation Cancelled", file, depth); 636 } 637 638 /** 639 * Constructs a {@code CancelException} with 640 * an appropriate message and the file and depth when 641 * cancellation occurred. 642 * 643 * @param message the detail message 644 * @param file the file when the operation was cancelled 645 * @param depth the depth when the operation was cancelled 646 */ 647 public CancelException(final String message, final File file, final int depth) { 648 super(message); 649 this.file = file; 650 this.depth = depth; 651 } 652 653 /** 654 * Returns the file when the operation was cancelled. 655 * 656 * @return the file when the operation was cancelled 657 */ 658 public File getFile() { 659 return file; 660 } 661 662 /** 663 * Returns the depth when the operation was cancelled. 664 * 665 * @return the depth when the operation was cancelled 666 */ 667 public int getDepth() { 668 return depth; 669 } 670 } 671}