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