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 * https://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 {@link FileCleaner} implementation.</li> 044 * <li><a href="#filter">2. Filter Example</a> - using {@link FileFilter}(s) with {@link 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 {@link 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 {@link DirectoryWalker} does provide for cancellation is: 158 * </p> 159 * <ul> 160 * <li>{@link CancelException} which can be thrown in any of the <em>lifecycle</em> 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.</li> 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 is a cancel button on a GUI. Calling this method sets a 190 * <a href='https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#d5e12277'>(@code volatile}</a> 191 * flag to ensure it works properly in a multi-threaded environment. 192 * The flag is returned by the {@code handleIsCancelled()} method, which causes the walk to stop 193 * immediately. The {@code handleCancelled()} method will be the next, 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 canceled = false; 200 * 201 * public void cancel() { 202 * canceled = true; 203 * } 204 * 205 * protected boolean handleIsCancelled(File file, int depth, Collection results) { 206 * return canceled; 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. <strong>Note</strong> the decision logic 219 * and throwing a {@link CancelException} could be implemented in any of the <em>lifecycle</em> 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 * CancelException is thrown in DirectoryWalker to cancel the current 259 * processing. 260 */ 261 public static class CancelException extends IOException { 262 263 /** Serialization id. */ 264 private static final long serialVersionUID = 1347339620135041008L; 265 266 /** The file being processed when the exception was thrown. */ 267 private final File file; 268 269 /** The file depth when the exception was thrown. */ 270 private final int depth; 271 272 /** 273 * Constructs a {@link CancelException} with 274 * the file and depth when cancellation occurred. 275 * 276 * @param file the file when the operation was canceled, may be null. 277 * @param depth the depth when the operation was canceled, may be null. 278 */ 279 public CancelException(final File file, final int depth) { 280 this("Operation Cancelled", file, depth); 281 } 282 283 /** 284 * Constructs a {@link CancelException} with 285 * an appropriate message and the file and depth when 286 * cancellation occurred. 287 * 288 * @param message the detail message. 289 * @param file the file when the operation was canceled. 290 * @param depth the depth when the operation was canceled. 291 */ 292 public CancelException(final String message, final File file, final int depth) { 293 super(message); 294 this.file = file; 295 this.depth = depth; 296 } 297 298 /** 299 * Returns the depth when the operation was canceled. 300 * 301 * @return the depth when the operation was canceled. 302 */ 303 public int getDepth() { 304 return depth; 305 } 306 307 /** 308 * Returns the file when the operation was canceled. 309 * 310 * @return the file when the operation was canceled. 311 */ 312 public File getFile() { 313 return file; 314 } 315 } 316 317 /** 318 * The file filter to use to filter files and directories. 319 */ 320 private final FileFilter filter; 321 322 /** 323 * The limit on the directory depth to walk. 324 */ 325 private final int depthLimit; 326 327 /** 328 * Constructs an instance with no filtering and unlimited <em>depth</em>. 329 */ 330 protected DirectoryWalker() { 331 this(null, -1); 332 } 333 334 /** 335 * Constructs an instance with a filter and limit the <em>depth</em> navigated to. 336 * <p> 337 * The filter controls which files and directories will be navigated to as 338 * part of the walk. The {@link FileFilterUtils} class is useful for combining 339 * various filters together. A {@code null} filter means that no 340 * filtering should occur and all files and directories will be visited. 341 * </p> 342 * 343 * @param filter the filter to apply, null means visit all files. 344 * @param depthLimit controls how <em>deep</em> the hierarchy is 345 * navigated to (less than 0 means unlimited). 346 */ 347 protected DirectoryWalker(final FileFilter filter, final int depthLimit) { 348 this.filter = filter; 349 this.depthLimit = depthLimit; 350 } 351 352 /** 353 * Constructs an instance with a directory and a file filter and an optional 354 * limit on the <em>depth</em> navigated to. 355 * <p> 356 * The filters control which files and directories will be navigated to as part 357 * of the walk. This constructor uses {@link FileFilterUtils#makeDirectoryOnly(IOFileFilter)} 358 * and {@link FileFilterUtils#makeFileOnly(IOFileFilter)} internally to combine the filters. 359 * A {@code null} filter means that no filtering should occur. 360 * </p> 361 * 362 * @param directoryFilter the filter to apply to directories, null means visit all directories. 363 * @param fileFilter the filter to apply to files, null means visit all files. 364 * @param depthLimit controls how <em>deep</em> the hierarchy is 365 * navigated to (less than 0 means unlimited). 366 */ 367 protected DirectoryWalker(IOFileFilter directoryFilter, IOFileFilter fileFilter, final int depthLimit) { 368 if (directoryFilter == null && fileFilter == null) { 369 this.filter = null; 370 } else { 371 directoryFilter = directoryFilter != null ? directoryFilter : TrueFileFilter.TRUE; 372 fileFilter = fileFilter != null ? fileFilter : TrueFileFilter.TRUE; 373 directoryFilter = FileFilterUtils.makeDirectoryOnly(directoryFilter); 374 fileFilter = FileFilterUtils.makeFileOnly(fileFilter); 375 this.filter = directoryFilter.or(fileFilter); 376 } 377 this.depthLimit = depthLimit; 378 } 379 380 /** 381 * Checks whether the walk has been canceled by calling {@link #handleIsCancelled}, 382 * throwing a {@link CancelException} if it has. 383 * <p> 384 * Writers of subclasses should not normally call this method as it is called 385 * automatically by the walk of the tree. However, sometimes a single method, 386 * typically {@link #handleFile}, may take a long time to run. In that case, 387 * you may wish to check for cancellation by calling this method. 388 * </p> 389 * 390 * @param file the current file being processed. 391 * @param depth the current file level (starting directory = 0). 392 * @param results the collection of result objects, may be updated. 393 * @throws IOException if an I/O Error occurs. 394 */ 395 protected final void checkIfCancelled(final File file, final int depth, final Collection<T> results) throws 396 IOException { 397 if (handleIsCancelled(file, depth, results)) { 398 throw new CancelException(file, depth); 399 } 400 } 401 402 /** 403 * Overridable callback method invoked with the contents of each directory. 404 * <p> 405 * This implementation returns the files unchanged 406 * </p> 407 * 408 * @param directory the current directory being processed. 409 * @param depth the current directory level (starting directory = 0). 410 * @param files the files (possibly filtered) in the directory, may be {@code null} 411 * @return the filtered list of files. 412 * @throws IOException if an I/O Error occurs. 413 * @since 2.0 414 */ 415 @SuppressWarnings("unused") // Possibly thrown from subclasses. 416 protected File[] filterDirectoryContents(final File directory, final int depth, final File... files) throws 417 IOException { 418 return files; 419 } 420 421 /** 422 * Overridable callback method invoked when the operation is canceled. 423 * The file being processed when the cancellation occurred can be 424 * obtained from the exception. 425 * <p> 426 * This implementation just re-throws the {@link CancelException}. 427 * </p> 428 * 429 * @param startDirectory the directory that the walk started from. 430 * @param results the collection of result objects, may be updated. 431 * @param cancel the exception throw to cancel further processing 432 * containing details at the point of cancellation. 433 * @throws IOException if an I/O Error occurs. 434 */ 435 protected void handleCancelled(final File startDirectory, final Collection<T> results, 436 final CancelException cancel) throws IOException { 437 // re-throw exception - overridable by subclass 438 throw cancel; 439 } 440 441 /** 442 * Overridable callback method invoked to determine if a directory should be processed. 443 * <p> 444 * This method returns a boolean to indicate if the directory should be examined or not. 445 * If you return false, the entire directory and any subdirectories will be skipped. 446 * Note that this functionality is in addition to the filtering by file filter. 447 * </p> 448 * <p> 449 * This implementation does nothing and returns true. 450 * </p> 451 * 452 * @param directory the current directory being processed. 453 * @param depth the current directory level (starting directory = 0). 454 * @param results the collection of result objects, may be updated. 455 * @return true to process this directory, false to skip this directory. 456 * @throws IOException if an I/O Error occurs. 457 */ 458 @SuppressWarnings("unused") // Possibly thrown from subclasses. 459 protected boolean handleDirectory(final File directory, final int depth, final Collection<T> results) throws 460 IOException { 461 // do nothing - overridable by subclass 462 return true; // process directory 463 } 464 465 /** 466 * Overridable callback method invoked at the end of processing each directory. 467 * <p> 468 * This implementation does nothing. 469 * </p> 470 * 471 * @param directory the directory being processed. 472 * @param depth the current directory level (starting directory = 0). 473 * @param results the collection of result objects, may be updated. 474 * @throws IOException if an I/O Error occurs. 475 */ 476 @SuppressWarnings("unused") // Possibly thrown from subclasses. 477 protected void handleDirectoryEnd(final File directory, final int depth, final Collection<T> results) throws 478 IOException { 479 // do nothing - overridable by subclass 480 } 481 482 /** 483 * Overridable callback method invoked at the start of processing each directory. 484 * <p> 485 * This implementation does nothing. 486 * </p> 487 * 488 * @param directory the current directory being processed. 489 * @param depth the current directory level (starting directory = 0). 490 * @param results the collection of result objects, may be updated. 491 * @throws IOException if an I/O Error occurs. 492 */ 493 @SuppressWarnings("unused") // Possibly thrown from subclasses. 494 protected void handleDirectoryStart(final File directory, final int depth, final Collection<T> results) throws 495 IOException { 496 // do nothing - overridable by subclass 497 } 498 499 /** 500 * Overridable callback method invoked at the end of processing. 501 * <p> 502 * This implementation does nothing. 503 * </p> 504 * 505 * @param results the collection of result objects, may be updated. 506 * @throws IOException if an I/O Error occurs. 507 */ 508 @SuppressWarnings("unused") // Possibly thrown from subclasses. 509 protected void handleEnd(final Collection<T> results) throws IOException { 510 // do nothing - overridable by subclass 511 } 512 513 /** 514 * Overridable callback method invoked for each (non-directory) file. 515 * <p> 516 * This implementation does nothing. 517 * </p> 518 * 519 * @param file the current file being processed. 520 * @param depth the current directory level (starting directory = 0). 521 * @param results the collection of result objects, may be updated. 522 * @throws IOException if an I/O Error occurs. 523 */ 524 @SuppressWarnings("unused") // Possibly thrown from subclasses. 525 protected void handleFile(final File file, final int depth, final Collection<T> results) throws IOException { 526 // do nothing - overridable by subclass 527 } 528 529 /** 530 * Overridable callback method invoked to determine if the entire walk 531 * operation should be immediately canceled. 532 * <p> 533 * This method should be implemented by those subclasses that want to 534 * provide a public {@code cancel()} method available from another 535 * thread. The design pattern for the subclass should be as follows: 536 * </p> 537 * <pre> 538 * public class FooDirectoryWalker extends DirectoryWalker { 539 * private volatile boolean canceled = false; 540 * 541 * public void cancel() { 542 * canceled = true; 543 * } 544 * private void handleIsCancelled(File file, int depth, Collection results) { 545 * return canceled; 546 * } 547 * protected void handleCancelled(File startDirectory, 548 * Collection results, CancelException cancel) { 549 * // implement processing required when a cancellation occurs 550 * } 551 * } 552 * </pre> 553 * <p> 554 * If this method returns true, then the directory walk is immediately 555 * canceled. The next callback method will be {@link #handleCancelled}. 556 * </p> 557 * <p> 558 * This implementation returns false. 559 * </p> 560 * 561 * @param file the file or directory being processed. 562 * @param depth the current directory level (starting directory = 0). 563 * @param results the collection of result objects, may be updated. 564 * @return true if the walk has been canceled. 565 * @throws IOException if an I/O Error occurs. 566 */ 567 @SuppressWarnings("unused") // Possibly thrown from subclasses. 568 protected boolean handleIsCancelled( 569 final File file, final int depth, final Collection<T> results) throws IOException { 570 // do nothing - overridable by subclass 571 return false; // not canceled 572 } 573 574 /** 575 * Overridable callback method invoked for each restricted directory. 576 * <p> 577 * This implementation does nothing. 578 * </p> 579 * 580 * @param directory the restricted directory. 581 * @param depth the current directory level (starting directory = 0). 582 * @param results the collection of result objects, may be updated. 583 * @throws IOException if an I/O Error occurs. 584 */ 585 @SuppressWarnings("unused") // Possibly thrown from subclasses. 586 protected void handleRestricted(final File directory, final int depth, final Collection<T> results) throws 587 IOException { 588 // do nothing - overridable by subclass 589 } 590 591 /** 592 * Overridable callback method invoked at the start of processing. 593 * <p> 594 * This implementation does nothing. 595 * </p> 596 * 597 * @param startDirectory the directory to start from. 598 * @param results the collection of result objects, may be updated. 599 * @throws IOException if an I/O Error occurs. 600 */ 601 @SuppressWarnings("unused") // Possibly thrown from subclasses. 602 protected void handleStart(final File startDirectory, final Collection<T> results) throws IOException { 603 // do nothing - overridable by subclass 604 } 605 606 /** 607 * Internal method that walks the directory hierarchy in a depth-first manner. 608 * <p> 609 * Users of this class do not need to call this method. This method will 610 * be called automatically by another (public) method on the specific subclass. 611 * </p> 612 * <p> 613 * Writers of subclasses should call this method to start the directory walk. 614 * Once called, this method will emit events as it walks the hierarchy. 615 * The event methods have the prefix {@code handle}. 616 * </p> 617 * 618 * @param startDirectory the directory to start from, not null. 619 * @param results the collection of result objects, may be updated. 620 * @throws NullPointerException if the start directory is null. 621 * @throws IOException if an I/O Error occurs. 622 */ 623 protected final void walk(final File startDirectory, final Collection<T> results) throws IOException { 624 Objects.requireNonNull(startDirectory, "startDirectory"); 625 try { 626 handleStart(startDirectory, results); 627 walk(startDirectory, 0, results); 628 handleEnd(results); 629 } catch (final CancelException cancel) { 630 handleCancelled(startDirectory, results, cancel); 631 } 632 } 633 634 /** 635 * Main recursive method to examine the directory hierarchy. 636 * 637 * @param directory the directory to examine, not null. 638 * @param depth the directory level (starting directory = 0). 639 * @param results the collection of result objects, may be updated. 640 * @throws IOException if an I/O Error occurs. 641 */ 642 private void walk(final File directory, final int depth, final Collection<T> results) throws IOException { 643 checkIfCancelled(directory, depth, results); 644 if (handleDirectory(directory, depth, results)) { 645 handleDirectoryStart(directory, depth, results); 646 final int childDepth = depth + 1; 647 if (depthLimit < 0 || childDepth <= depthLimit) { 648 checkIfCancelled(directory, depth, results); 649 File[] childFiles = directory.listFiles(filter); 650 childFiles = filterDirectoryContents(directory, depth, childFiles); 651 if (childFiles == null) { 652 handleRestricted(directory, childDepth, results); 653 } else { 654 for (final File childFile : childFiles) { 655 if (childFile.isDirectory()) { 656 walk(childFile, childDepth, results); 657 } else { 658 checkIfCancelled(childFile, childDepth, results); 659 handleFile(childFile, childDepth, results); 660 checkIfCancelled(childFile, childDepth, results); 661 } 662 } 663 } 664 } 665 handleDirectoryEnd(directory, depth, results); 666 } 667 checkIfCancelled(directory, depth, results); 668 } 669}