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