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