View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.io;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  import static org.junit.jupiter.api.Assertions.fail;
24  
25  import java.io.File;
26  import java.io.FileFilter;
27  import java.io.IOException;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.List;
31  
32  import org.apache.commons.io.filefilter.FileFilterUtils;
33  import org.apache.commons.io.filefilter.IOFileFilter;
34  import org.apache.commons.io.filefilter.NameFileFilter;
35  import org.junit.jupiter.api.Test;
36  
37  /**
38   * Tests {@link DirectoryWalker}.
39   */
40  public class DirectoryWalkerTest {
41  
42      /**
43       * Test DirectoryWalker implementation that finds files in a directory hierarchy
44       * applying a file filter.
45       */
46      static class TestCancelWalker extends DirectoryWalker<File> {
47          private final String cancelFileName;
48          private final boolean suppressCancel;
49  
50          TestCancelWalker(final String cancelFileName, final boolean suppressCancel) {
51              this.cancelFileName = cancelFileName;
52              this.suppressCancel = suppressCancel;
53          }
54  
55          /** Find files. */
56          protected List<File> find(final File startDirectory) throws IOException {
57              final List<File> results = new ArrayList<>();
58              walk(startDirectory, results);
59              return results;
60          }
61  
62          /** Handles Cancel. */
63          @Override
64          protected void handleCancelled(final File startDirectory, final Collection<File> results, final CancelException cancel) throws IOException {
65              if (!suppressCancel) {
66                  super.handleCancelled(startDirectory, results, cancel);
67              }
68          }
69  
70          /** Handles a directory end by adding the File to the result set. */
71          @Override
72          protected void handleDirectoryEnd(final File directory, final int depth, final Collection<File> results) throws IOException {
73              results.add(directory);
74              if (cancelFileName.equals(directory.getName())) {
75                  throw new CancelException(directory, depth);
76              }
77          }
78  
79          /** Handles a file by adding the File to the result set. */
80          @Override
81          protected void handleFile(final File file, final int depth, final Collection<File> results) throws IOException {
82              results.add(file);
83              if (cancelFileName.equals(file.getName())) {
84                  throw new CancelException(file, depth);
85              }
86          }
87      }
88      /**
89       * Test DirectoryWalker implementation that always returns false
90       * from handleDirectoryStart()
91       */
92      private static final class TestFalseFileFinder extends TestFileFinder {
93  
94          protected TestFalseFileFinder(final FileFilter filter, final int depthLimit) {
95              super(filter, depthLimit);
96          }
97  
98          /** Always returns false. */
99          @Override
100         protected boolean handleDirectory(final File directory, final int depth, final Collection<File> results) {
101             return false;
102         }
103     }
104     /**
105      * Test DirectoryWalker implementation that finds files in a directory hierarchy
106      * applying a file filter.
107      */
108     private static class TestFileFinder extends DirectoryWalker<File> {
109 
110         protected TestFileFinder(final FileFilter filter, final int depthLimit) {
111             super(filter, depthLimit);
112         }
113 
114         protected TestFileFinder(final IOFileFilter dirFilter, final IOFileFilter fileFilter, final int depthLimit) {
115             super(dirFilter, fileFilter, depthLimit);
116         }
117 
118         /** Find files. */
119         protected List<File> find(final File startDirectory) {
120             final List<File> results = new ArrayList<>();
121             try {
122                 walk(startDirectory, results);
123             } catch (final IOException ex) {
124                 fail(ex.toString());
125             }
126             return results;
127         }
128 
129         /** Handles a directory end by adding the File to the result set. */
130         @Override
131         protected void handleDirectoryEnd(final File directory, final int depth, final Collection<File> results) {
132             results.add(directory);
133         }
134 
135         /** Handles a file by adding the File to the result set. */
136         @Override
137         protected void handleFile(final File file, final int depth, final Collection<File> results) {
138             results.add(file);
139         }
140     }
141     /**
142      * Test DirectoryWalker implementation that finds files in a directory hierarchy
143      * applying a file filter.
144      */
145     private static final class TestFileFinderString extends DirectoryWalker<String> {
146 
147         protected TestFileFinderString(final FileFilter filter, final int depthLimit) {
148             super(filter, depthLimit);
149         }
150 
151         /** Find files. */
152         protected List<String> find(final File startDirectory) {
153             final List<String> results = new ArrayList<>();
154             try {
155                 walk(startDirectory, results);
156             } catch (final IOException ex) {
157                 fail(ex.toString());
158             }
159             return results;
160         }
161 
162         /** Handles a file by adding the File to the result set. */
163         @Override
164         protected void handleFile(final File file, final int depth, final Collection<String> results) {
165             results.add(file.toString());
166         }
167     }
168     /**
169      * Test DirectoryWalker implementation that finds files in a directory hierarchy
170      * applying a file filter.
171      */
172     static class TestMultiThreadCancelWalker extends DirectoryWalker<File> {
173         private final String cancelFileName;
174         private final boolean suppressCancel;
175         private boolean cancelled;
176         public List<File> results;
177 
178         TestMultiThreadCancelWalker(final String cancelFileName, final boolean suppressCancel) {
179             this.cancelFileName = cancelFileName;
180             this.suppressCancel = suppressCancel;
181         }
182 
183         /** Find files. */
184         protected List<File> find(final File startDirectory) throws IOException {
185             results = new ArrayList<>();
186             walk(startDirectory, results);
187             return results;
188         }
189 
190         /** Handles Cancel. */
191         @Override
192         protected void handleCancelled(final File startDirectory, final Collection<File> results, final CancelException cancel) throws IOException {
193             if (!suppressCancel) {
194                 super.handleCancelled(startDirectory, results, cancel);
195             }
196         }
197 
198         /** Handles a directory end by adding the File to the result set. */
199         @Override
200         protected void handleDirectoryEnd(final File directory, final int depth, final Collection<File> results) throws IOException {
201             results.add(directory);
202             assertFalse(cancelled);
203             if (cancelFileName.equals(directory.getName())) {
204                 cancelled = true;
205             }
206         }
207 
208         /** Handles a file by adding the File to the result set. */
209         @Override
210         protected void handleFile(final File file, final int depth, final Collection<File> results) throws IOException {
211             results.add(file);
212             assertFalse(cancelled);
213             if (cancelFileName.equals(file.getName())) {
214                 cancelled = true;
215             }
216         }
217 
218         /** Handles Cancelled. */
219         @Override
220         protected boolean handleIsCancelled(final File file, final int depth, final Collection<File> results) throws IOException {
221             return cancelled;
222         }
223     }
224     // Directories
225     private static final File current      = FileUtils.current();
226     private static final File javaDir      = new File("src/main/java");
227     private static final File orgDir       = new File(javaDir, "org");
228 
229     private static final File apacheDir    = new File(orgDir, "apache");
230     private static final File commonsDir   = new File(apacheDir, "commons");
231     private static final File ioDir        = new File(commonsDir, "io");
232     private static final File outputDir    = new File(ioDir, "output");
233     private static final File[] dirs       = {orgDir, apacheDir, commonsDir, ioDir, outputDir};
234     // Files
235     private static final File fileNameUtils = new File(ioDir, "FilenameUtils.java");
236 
237     private static final File ioUtils       = new File(ioDir, "IOUtils.java");
238     private static final File proxyWriter   = new File(outputDir, "ProxyWriter.java");
239     private static final File nullStream    = new File(outputDir, "NullOutputStream.java");
240     private static final File[] ioFiles     = {fileNameUtils, ioUtils};
241     private static final File[] outputFiles = {proxyWriter, nullStream};
242 
243     // Filters
244     private static final IOFileFilter dirsFilter        = createNameFilter(dirs);
245 
246     private static final IOFileFilter ioFilesFilter = createNameFilter(ioFiles);
247 
248     private static final IOFileFilter outputFilesFilter = createNameFilter(outputFiles);
249 
250     private static final IOFileFilter ioDirAndFilesFilter = dirsFilter.or(ioFilesFilter);
251 
252     private static final IOFileFilter dirsAndFilesFilter = ioDirAndFilesFilter.or(outputFilesFilter);
253 
254     // Filter to exclude SVN files
255     private static final IOFileFilter NOT_SVN = FileFilterUtils.makeSVNAware(null);
256 
257     /**
258      * Create a name filter containing the names of the files
259      * in the array.
260      */
261     private static IOFileFilter createNameFilter(final File[] files) {
262         final String[] names = new String[files.length];
263         for (int i = 0; i < files.length; i++) {
264             names[i] = files[i].getName();
265         }
266         return new NameFileFilter(names);
267     }
268 
269     /**
270      * Check the files in the array are in the results list.
271      */
272     private void checkContainsFiles(final String prefix, final File[] files, final Collection<File> results) {
273         for (int i = 0; i < files.length; i++) {
274             assertTrue(results.contains(files[i]), prefix + "[" + i + "] " + files[i]);
275         }
276     }
277 
278     private void checkContainsString(final String prefix, final File[] files, final Collection<String> results) {
279         for (int i = 0; i < files.length; i++) {
280             assertTrue(results.contains(files[i].toString()), prefix + "[" + i + "] " + files[i]);
281         }
282     }
283 
284     /**
285      * Extract the directories.
286      */
287     private List<File> directoriesOnly(final Collection<File> results) {
288         final List<File> list = new ArrayList<>(results.size());
289         for (final File file : results) {
290             if (file.isDirectory()) {
291                 list.add(file);
292             }
293         }
294         return list;
295     }
296 
297     /**
298      * Extract the files.
299      */
300     private List<File> filesOnly(final Collection<File> results) {
301         final List<File> list = new ArrayList<>(results.size());
302         for (final File file : results) {
303             if (file.isFile()) {
304                 list.add(file);
305             }
306         }
307         return list;
308     }
309 
310     /**
311      * Test Cancel
312      */
313     @Test
314     public void testCancel() {
315         String cancelName = null;
316 
317         // Cancel on a file
318         try {
319             cancelName = "DirectoryWalker.java";
320             new TestCancelWalker(cancelName, false).find(javaDir);
321             fail("CancelException not thrown for '" + cancelName + "'");
322         } catch (final DirectoryWalker.CancelException cancel) {
323             assertEquals(cancelName, cancel.getFile().getName(), "File:  " + cancelName);
324             assertEquals(5, cancel.getDepth(), "Depth: " + cancelName);
325         } catch (final IOException ex) {
326             fail("IOException: " + cancelName + " " + ex);
327         }
328 
329         // Cancel on a directory
330         try {
331             cancelName = "commons";
332             new TestCancelWalker(cancelName, false).find(javaDir);
333             fail("CancelException not thrown for '" + cancelName + "'");
334         } catch (final DirectoryWalker.CancelException cancel) {
335             assertEquals(cancelName, cancel.getFile().getName(), "File:  " + cancelName);
336             assertEquals(3, cancel.getDepth(), "Depth: " + cancelName);
337         } catch (final IOException ex) {
338             fail("IOException: " + cancelName + " " + ex);
339         }
340 
341         // Suppress CancelException (use same file name as preceding test)
342         try {
343             final List<File> results = new TestCancelWalker(cancelName, true).find(javaDir);
344             final File lastFile = results.get(results.size() - 1);
345             assertEquals(cancelName, lastFile.getName(), "Suppress:  " + cancelName);
346         } catch (final IOException ex) {
347             fail("Suppress threw " + ex);
348         }
349 
350     }
351 
352     /**
353      * Test Filtering
354      */
355     @Test
356     public void testFilter() {
357         final List<File> results = new TestFileFinder(dirsAndFilesFilter, -1).find(javaDir);
358         assertEquals(1 + dirs.length + ioFiles.length + outputFiles.length, results.size(), "Result Size");
359         assertTrue(results.contains(javaDir), "Start Dir");
360         checkContainsFiles("Dir", dirs, results);
361         checkContainsFiles("IO File", ioFiles, results);
362         checkContainsFiles("Output File", outputFiles, results);
363     }
364 
365     /**
366      * Test Filtering and limit to depth 0
367      */
368     @Test
369     public void testFilterAndLimitA() {
370         final List<File> results = new TestFileFinder(NOT_SVN, 0).find(javaDir);
371         assertEquals(1, results.size(), "[A] Result Size");
372         assertTrue(results.contains(javaDir), "[A] Start Dir");
373     }
374 
375     /**
376      * Test Filtering and limit to depth 1
377      */
378     @Test
379     public void testFilterAndLimitB() {
380         final List<File> results = new TestFileFinder(NOT_SVN, 1).find(javaDir);
381         assertEquals(2, results.size(), "[B] Result Size");
382         assertTrue(results.contains(javaDir), "[B] Start Dir");
383         assertTrue(results.contains(orgDir), "[B] Org Dir");
384     }
385 
386     /**
387      * Test Filtering and limit to depth 3
388      */
389     @Test
390     public void testFilterAndLimitC() {
391         final List<File> results = new TestFileFinder(NOT_SVN, 3).find(javaDir);
392         assertEquals(4, results.size(), "[C] Result Size");
393         assertTrue(results.contains(javaDir), "[C] Start Dir");
394         assertTrue(results.contains(orgDir), "[C] Org Dir");
395         assertTrue(results.contains(apacheDir), "[C] Apache Dir");
396         assertTrue(results.contains(commonsDir), "[C] Commons Dir");
397     }
398 
399     /**
400      * Test Filtering and limit to depth 5
401      */
402     @Test
403     public void testFilterAndLimitD() {
404         final List<File> results = new TestFileFinder(dirsAndFilesFilter, 5).find(javaDir);
405         assertEquals(1 + dirs.length + ioFiles.length, results.size(), "[D] Result Size");
406         assertTrue(results.contains(javaDir), "[D] Start Dir");
407         checkContainsFiles("[D] Dir", dirs, results);
408         checkContainsFiles("[D] File", ioFiles, results);
409     }
410 
411     /**
412      * Test separate dir and file filters
413      */
414     @Test
415     public void testFilterDirAndFile1() {
416         final List<File> results = new TestFileFinder(dirsFilter, ioFilesFilter, -1).find(javaDir);
417         assertEquals(1 + dirs.length + ioFiles.length, results.size(), "[DirAndFile1] Result Size");
418         assertTrue(results.contains(javaDir), "[DirAndFile1] Start Dir");
419         checkContainsFiles("[DirAndFile1] Dir", dirs, results);
420         checkContainsFiles("[DirAndFile1] File", ioFiles, results);
421     }
422 
423     /**
424      * Test separate dir and file filters
425      */
426     @Test
427     public void testFilterDirAndFile2() {
428         final List<File> results = new TestFileFinder(null, null, -1).find(javaDir);
429         assertTrue(results.size() > 1 + dirs.length + ioFiles.length, "[DirAndFile2] Result Size");
430         assertTrue(results.contains(javaDir), "[DirAndFile2] Start Dir");
431         checkContainsFiles("[DirAndFile2] Dir", dirs, results);
432         checkContainsFiles("[DirAndFile2] File", ioFiles, results);
433     }
434 
435     /**
436      * Test separate dir and file filters
437      */
438     @Test
439     public void testFilterDirAndFile3() {
440         final List<File> results = new TestFileFinder(dirsFilter, null, -1).find(javaDir);
441         final List<File> resultDirs = directoriesOnly(results);
442         assertEquals(1 + dirs.length, resultDirs.size(), "[DirAndFile3] Result Size");
443         assertTrue(results.contains(javaDir), "[DirAndFile3] Start Dir");
444         checkContainsFiles("[DirAndFile3] Dir", dirs, resultDirs);
445     }
446 
447     /**
448      * Test separate dir and file filters
449      */
450     @Test
451     public void testFilterDirAndFile4() {
452         final List<File> results = new TestFileFinder(null, ioFilesFilter, -1).find(javaDir);
453         final List<File> resultFiles = filesOnly(results);
454         assertEquals(ioFiles.length, resultFiles.size(), "[DirAndFile4] Result Size");
455         assertTrue(results.contains(javaDir), "[DirAndFile4] Start Dir");
456         checkContainsFiles("[DirAndFile4] File", ioFiles, resultFiles);
457     }
458 
459     /**
460      * Test Filtering
461      */
462     @Test
463     public void testFilterString() {
464         final List<String> results = new TestFileFinderString(dirsAndFilesFilter, -1).find(javaDir);
465         assertEquals(results.size(), outputFiles.length + ioFiles.length, "Result Size");
466         checkContainsString("IO File", ioFiles, results);
467         checkContainsString("Output File", outputFiles, results);
468     }
469 
470     /**
471      * test an invalid start directory
472      */
473     @Test
474     public void testHandleStartDirectoryFalse() {
475 
476         final List<File> results = new TestFalseFileFinder(null, -1).find(current);
477         assertEquals(0, results.size(), "Result Size");
478 
479     }
480 
481     /**
482      * Test Limiting to current directory
483      */
484     @Test
485     public void testLimitToCurrent() {
486         final List<File> results = new TestFileFinder(null, 0).find(current);
487         assertEquals(1, results.size(), "Result Size");
488         assertTrue(results.contains(FileUtils.current()), "Current Dir");
489     }
490 
491     /**
492      * test an invalid start directory
493      */
494     @Test
495     public void testMissingStartDirectory() {
496 
497         // TODO is this what we want with invalid directory?
498         final File invalidDir = new File("invalid-dir");
499         final List<File> results = new TestFileFinder(null, -1).find(invalidDir);
500         assertEquals(1, results.size(), "Result Size");
501         assertTrue(results.contains(invalidDir), "Current Dir");
502 
503         assertThrows(NullPointerException.class, () -> new TestFileFinder(null, -1).find(null));
504     }
505 
506     /**
507      * Test Cancel
508      */
509     @Test
510     public void testMultiThreadCancel() {
511         String cancelName = "DirectoryWalker.java";
512         TestMultiThreadCancelWalker walker = new TestMultiThreadCancelWalker(cancelName, false);
513         // Cancel on a file
514         try {
515             walker.find(javaDir);
516             fail("CancelException not thrown for '" + cancelName + "'");
517         } catch (final DirectoryWalker.CancelException cancel) {
518             final File last = walker.results.get(walker.results.size() - 1);
519             assertEquals(cancelName, last.getName());
520             assertEquals(5, cancel.getDepth(), "Depth: " + cancelName);
521         } catch (final IOException ex) {
522             fail("IOException: " + cancelName + " " + ex);
523         }
524 
525         // Cancel on a directory
526         try {
527             cancelName = "commons";
528             walker = new TestMultiThreadCancelWalker(cancelName, false);
529             walker.find(javaDir);
530             fail("CancelException not thrown for '" + cancelName + "'");
531         } catch (final DirectoryWalker.CancelException cancel) {
532             assertEquals(cancelName, cancel.getFile().getName(), "File:  " + cancelName);
533             assertEquals(3, cancel.getDepth(), "Depth: " + cancelName);
534         } catch (final IOException ex) {
535             fail("IOException: " + cancelName + " " + ex);
536         }
537 
538         // Suppress CancelException (use same file name as preceding test)
539         try {
540             walker = new TestMultiThreadCancelWalker(cancelName, true);
541             final List<File> results = walker.find(javaDir);
542             final File lastFile = results.get(results.size() - 1);
543             assertEquals(cancelName, lastFile.getName(), "Suppress:  " + cancelName);
544         } catch (final IOException ex) {
545             fail("Suppress threw " + ex);
546         }
547 
548     }
549 
550 }