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