View Javadoc
1   package org.codehaus.plexus.util;
2   
3   /*
4    * Copyright The Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.net.URI;
22  import java.net.URISyntaxException;
23  import java.net.URL;
24  import java.nio.file.Files;
25  import java.nio.file.Paths;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Set;
31  
32  import org.junit.jupiter.api.BeforeEach;
33  import org.junit.jupiter.api.Test;
34  
35  import static org.junit.jupiter.api.Assertions.assertEquals;
36  import static org.junit.jupiter.api.Assertions.assertFalse;
37  import static org.junit.jupiter.api.Assertions.assertTrue;
38  import static org.junit.jupiter.api.Assertions.fail;
39  import static org.junit.jupiter.api.Assumptions.assumeTrue;
40  
41  /**
42   * Base class for testcases doing tests with files.
43   *
44   * @author Dan T. Tran
45   * @since 3.4.0
46   */
47  class DirectoryScannerTest extends FileBasedTestCase {
48      private static final String testDir = getTestDirectory().getPath();
49  
50      @BeforeEach
51      void setUp() {
52          try {
53              FileUtils.deleteDirectory(testDir);
54          } catch (IOException e) {
55              fail("Could not delete directory " + testDir);
56          }
57      }
58  
59      @Test
60      void crossPlatformIncludesString() throws Exception {
61          DirectoryScanner ds = new DirectoryScanner();
62          ds.setBasedir(new File(getTestResourcesDir() + File.separator + "directory-scanner").getCanonicalFile());
63  
64          String fs;
65          if (File.separatorChar == '/') {
66              fs = "\\";
67          } else {
68              fs = "/";
69          }
70  
71          ds.setIncludes(new String[] {"foo" + fs});
72          ds.addDefaultExcludes();
73          ds.scan();
74  
75          String[] files = ds.getIncludedFiles();
76          assertEquals(1, files.length);
77      }
78  
79      @Test
80      void crossPlatformExcludesString() throws Exception {
81          DirectoryScanner ds = new DirectoryScanner();
82          ds.setBasedir(new File(getTestResourcesDir() + File.separator + "directory-scanner").getCanonicalFile());
83          ds.setIncludes(new String[] {"**"});
84  
85          String fs;
86          if (File.separatorChar == '/') {
87              fs = "\\";
88          } else {
89              fs = "/";
90          }
91  
92          ds.setExcludes(new String[] {"foo" + fs});
93          ds.addDefaultExcludes();
94          ds.scan();
95  
96          String[] files = ds.getIncludedFiles();
97          assertEquals(0, files.length);
98      }
99  
100     private String getTestResourcesDir() throws URISyntaxException {
101         ClassLoader cloader = Thread.currentThread().getContextClassLoader();
102         URL resource = cloader.getResource("test.txt");
103         if (resource == null) {
104             fail("Cannot locate test-resources directory containing 'test.txt' in the classloader.");
105         }
106 
107         File file = new File(new URI(resource.toExternalForm()).normalize().getPath());
108 
109         return file.getParent();
110     }
111 
112     private void createTestFiles() throws IOException {
113         FileUtils.mkdir(testDir);
114         this.createFile(new File(testDir + "/scanner1.dat"), 0);
115         this.createFile(new File(testDir + "/scanner2.dat"), 0);
116         this.createFile(new File(testDir + "/scanner3.dat"), 0);
117         this.createFile(new File(testDir + "/scanner4.dat"), 0);
118         this.createFile(new File(testDir + "/scanner5.dat"), 0);
119     }
120 
121     /**
122      * Check if 'src/test/resources/symlinks/src/sym*' test files (start with 'sym') exist and are symlinks.<br>
123      * On some OS (like Windows 10), the 'git clone' requires to be executed with admin permissions and the
124      * 'core.symlinks=true' git option.
125      *
126      * @return true If files here and symlinks, false otherwise
127      */
128     private boolean checkTestFilesSymlinks() {
129         File symlinksDirectory = new File("src/test/resources/symlinks/src");
130         try {
131             List<String> symlinks =
132                     FileUtils.getFileAndDirectoryNames(symlinksDirectory, "sym*", null, true, true, true, true);
133             if (symlinks.isEmpty()) {
134                 throw new IOException("Symlinks files/directories are not present");
135             }
136             for (String symLink : symlinks) {
137                 if (!Files.isSymbolicLink(Paths.get(symLink))) {
138                     throw new IOException(String.format("Path is not a symlink: %s", symLink));
139                 }
140             }
141             return true;
142         } catch (IOException e) {
143             System.err.printf(
144                     "The unit test '%s.%s' will be skipped, reason: %s%n",
145                     this.getClass().getSimpleName(), getTestMethodName(), e.getMessage());
146             System.out.printf("This test requires symlinks files in '%s' directory.%n", symlinksDirectory.getPath());
147             System.out.println("On some OS (like Windows 10), files are present only if the clone/checkout is done"
148                     + " in administrator mode, and correct (symlinks and not flat file/directory)"
149                     + " if symlinks option are used (for git: git clone -c core.symlinks=true [url])");
150             return false;
151         }
152     }
153 
154     @Test
155     void general() throws Exception {
156         this.createTestFiles();
157 
158         String includes = "scanner1.dat,scanner2.dat,scanner3.dat,scanner4.dat,scanner5.dat";
159         String excludes = "scanner1.dat,scanner2.dat";
160 
161         List<File> fileNames = FileUtils.getFiles(new File(testDir), includes, excludes, false);
162 
163         assertEquals(3, fileNames.size(), "Wrong number of results.");
164         assertTrue(fileNames.contains(new File("scanner3.dat")), "3 not found.");
165         assertTrue(fileNames.contains(new File("scanner4.dat")), "4 not found.");
166         assertTrue(fileNames.contains(new File("scanner5.dat")), "5 not found.");
167     }
168 
169     @Test
170     void includesExcludesWithWhiteSpaces() throws Exception {
171         this.createTestFiles();
172 
173         String includes = "scanner1.dat,\n  \n,scanner2.dat  \n\r, scanner3.dat\n, \tscanner4.dat,scanner5.dat\n,";
174 
175         String excludes = "scanner1.dat,\n  \n,scanner2.dat  \n\r,,";
176 
177         List<File> fileNames = FileUtils.getFiles(new File(testDir), includes, excludes, false);
178 
179         assertEquals(3, fileNames.size(), "Wrong number of results.");
180         assertTrue(fileNames.contains(new File("scanner3.dat")), "3 not found.");
181         assertTrue(fileNames.contains(new File("scanner4.dat")), "4 not found.");
182         assertTrue(fileNames.contains(new File("scanner5.dat")), "5 not found.");
183     }
184 
185     @Test
186     void followSymlinksFalse() {
187         assumeTrue(checkTestFilesSymlinks());
188 
189         DirectoryScanner ds = new DirectoryScanner();
190         ds.setBasedir(new File("src/test/resources/symlinks/src/"));
191         ds.setFollowSymlinks(false);
192         ds.scan();
193         List<String> included = Arrays.asList(ds.getIncludedFiles());
194         assertAlwaysIncluded(included);
195         assertEquals(9, included.size());
196         List<String> includedDirs = Arrays.asList(ds.getIncludedDirectories());
197         assertTrue(includedDirs.contains("")); // w00t !
198         assertTrue(includedDirs.contains("aRegularDir"));
199         assertTrue(includedDirs.contains("symDir"));
200         assertTrue(includedDirs.contains("symLinkToDirOnTheOutside"));
201         assertTrue(includedDirs.contains("targetDir"));
202         assertEquals(5, includedDirs.size());
203     }
204 
205     private void assertAlwaysIncluded(List<String> included) {
206         assertTrue(included.contains("aRegularDir" + File.separator + "aRegularFile.txt"));
207         assertTrue(included.contains("targetDir" + File.separator + "targetFile.txt"));
208         assertTrue(included.contains("fileR.txt"));
209         assertTrue(included.contains("fileW.txt"));
210         assertTrue(included.contains("fileX.txt"));
211         assertTrue(included.contains("symR"));
212         assertTrue(included.contains("symW"));
213         assertTrue(included.contains("symX"));
214         assertTrue(included.contains("symLinkToFileOnTheOutside"));
215     }
216 
217     @Test
218     void followSymlinks() {
219         assumeTrue(checkTestFilesSymlinks());
220 
221         DirectoryScanner ds = new DirectoryScanner();
222         ds.setBasedir(new File("src/test/resources/symlinks/src/"));
223         ds.setFollowSymlinks(true);
224         ds.scan();
225         List<String> included = Arrays.asList(ds.getIncludedFiles());
226         assertAlwaysIncluded(included);
227         assertTrue(included.contains("symDir" + File.separator + "targetFile.txt"));
228         assertTrue(included.contains("symLinkToDirOnTheOutside" + File.separator + "FileInDirOnTheOutside.txt"));
229         assertEquals(11, included.size());
230 
231         List<String> includedDirs = Arrays.asList(ds.getIncludedDirectories());
232         assertTrue(includedDirs.contains("")); // w00t !
233         assertTrue(includedDirs.contains("aRegularDir"));
234         assertTrue(includedDirs.contains("symDir"));
235         assertTrue(includedDirs.contains("symLinkToDirOnTheOutside"));
236         assertTrue(includedDirs.contains("targetDir"));
237         assertEquals(5, includedDirs.size());
238     }
239 
240     private void createTestDirectories() throws IOException {
241         FileUtils.mkdir(testDir + File.separator + "directoryTest");
242         FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "testDir123");
243         FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "test_dir_123");
244         FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "test-dir-123");
245         this.createFile(
246                 new File(testDir + File.separator + "directoryTest" + File.separator + "testDir123" + File.separator
247                         + "file1.dat"),
248                 0);
249         this.createFile(
250                 new File(testDir + File.separator + "directoryTest" + File.separator + "test_dir_123" + File.separator
251                         + "file1.dat"),
252                 0);
253         this.createFile(
254                 new File(testDir + File.separator + "directoryTest" + File.separator + "test-dir-123" + File.separator
255                         + "file1.dat"),
256                 0);
257     }
258 
259     @Test
260     void directoriesWithHyphens() throws Exception {
261         this.createTestDirectories();
262 
263         DirectoryScanner ds = new DirectoryScanner();
264         String[] includes = {"**/*.dat"};
265         String[] excludes = {""};
266         ds.setIncludes(includes);
267         ds.setExcludes(excludes);
268         ds.setBasedir(new File(testDir + File.separator + "directoryTest"));
269         ds.setCaseSensitive(true);
270         ds.scan();
271 
272         String[] files = ds.getIncludedFiles();
273         assertEquals(3, files.length, "Wrong number of results.");
274     }
275 
276     @Test
277     void antExcludesOverrideIncludes() throws Exception {
278         printTestHeader();
279 
280         File dir = new File(testDir, "regex-dir");
281         dir.mkdirs();
282 
283         String[] excludedPaths = {"target/foo.txt"};
284 
285         createFiles(dir, excludedPaths);
286 
287         String[] includedPaths = {"src/main/resources/project/target/foo.txt"};
288 
289         createFiles(dir, includedPaths);
290 
291         DirectoryScanner ds = new DirectoryScanner();
292 
293         String[] includes = {"**/target/*"};
294         String[] excludes = {"target/*"};
295 
296         // This doesn't work, since excluded patterns refine included ones, meaning they operate on
297         // the list of paths that passed the included patterns, and can override them.
298         // String[] includes = {"**src/**/target/**/*" };
299         // String[] excludes = { "**/target/**/*" };
300 
301         ds.setIncludes(includes);
302         ds.setExcludes(excludes);
303         ds.setBasedir(dir);
304         ds.scan();
305 
306         assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
307     }
308 
309     @Test
310     void antExcludesOverrideIncludesWithExplicitAntPrefix() throws Exception {
311         printTestHeader();
312 
313         File dir = new File(testDir, "regex-dir");
314         dir.mkdirs();
315 
316         String[] excludedPaths = {"target/foo.txt"};
317 
318         createFiles(dir, excludedPaths);
319 
320         String[] includedPaths = {"src/main/resources/project/target/foo.txt"};
321 
322         createFiles(dir, includedPaths);
323 
324         DirectoryScanner ds = new DirectoryScanner();
325 
326         String[] includes = {SelectorUtils.ANT_HANDLER_PREFIX + "**/target/**/*" + SelectorUtils.PATTERN_HANDLER_SUFFIX
327         };
328         String[] excludes = {SelectorUtils.ANT_HANDLER_PREFIX + "target/**/*" + SelectorUtils.PATTERN_HANDLER_SUFFIX};
329 
330         // This doesn't work, since excluded patterns refine included ones, meaning they operate on
331         // the list of paths that passed the included patterns, and can override them.
332         // String[] includes = {"**src/**/target/**/*" };
333         // String[] excludes = { "**/target/**/*" };
334 
335         ds.setIncludes(includes);
336         ds.setExcludes(excludes);
337         ds.setBasedir(dir);
338         ds.scan();
339 
340         assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
341     }
342 
343     @Test
344     void regexIncludeWithExcludedPrefixDirs() throws Exception {
345         printTestHeader();
346 
347         File dir = new File(testDir, "regex-dir");
348         dir.mkdirs();
349 
350         String[] excludedPaths = {"src/main/foo.txt"};
351 
352         createFiles(dir, excludedPaths);
353 
354         String[] includedPaths = {"src/main/resources/project/target/foo.txt"};
355 
356         createFiles(dir, includedPaths);
357 
358         String regex = ".+/target.*";
359 
360         DirectoryScanner ds = new DirectoryScanner();
361 
362         String includeExpr = SelectorUtils.REGEX_HANDLER_PREFIX + regex + SelectorUtils.PATTERN_HANDLER_SUFFIX;
363 
364         String[] includes = {includeExpr};
365         ds.setIncludes(includes);
366         ds.setBasedir(dir);
367         ds.scan();
368 
369         assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
370     }
371 
372     @Test
373     void regexExcludeWithNegativeLookahead() throws Exception {
374         printTestHeader();
375 
376         File dir = new File(testDir, "regex-dir");
377         try {
378             FileUtils.deleteDirectory(dir);
379         } catch (IOException ignored) {
380         }
381 
382         dir.mkdirs();
383 
384         String[] excludedPaths = {"target/foo.txt"};
385 
386         createFiles(dir, excludedPaths);
387 
388         String[] includedPaths = {"src/main/resources/project/target/foo.txt"};
389 
390         createFiles(dir, includedPaths);
391 
392         String regex = "(?!.*src/).*target.*";
393 
394         DirectoryScanner ds = new DirectoryScanner();
395 
396         String excludeExpr = SelectorUtils.REGEX_HANDLER_PREFIX + regex + SelectorUtils.PATTERN_HANDLER_SUFFIX;
397 
398         String[] excludes = {excludeExpr};
399         ds.setExcludes(excludes);
400         ds.setBasedir(dir);
401         ds.scan();
402 
403         assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
404     }
405 
406     @Test
407     void regexWithSlashInsideCharacterClass() throws Exception {
408         printTestHeader();
409 
410         File dir = new File(testDir, "regex-dir");
411         try {
412             FileUtils.deleteDirectory(dir);
413         } catch (IOException ignored) {
414         }
415 
416         dir.mkdirs();
417 
418         String[] excludedPaths = {"target/foo.txt", "target/src/main/target/foo.txt"};
419 
420         createFiles(dir, excludedPaths);
421 
422         String[] includedPaths = {"module/src/main/target/foo.txt"};
423 
424         createFiles(dir, includedPaths);
425 
426         // NOTE: The portion "[^/]" is the interesting part of this pattern.
427         String regex = "(?!((?!target/)[^/]+/)*src/).*target.*";
428 
429         DirectoryScanner ds = new DirectoryScanner();
430 
431         String excludeExpr = SelectorUtils.REGEX_HANDLER_PREFIX + regex + SelectorUtils.PATTERN_HANDLER_SUFFIX;
432 
433         String[] excludes = {excludeExpr};
434         ds.setExcludes(excludes);
435         ds.setBasedir(dir);
436         ds.scan();
437 
438         assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
439     }
440 
441     /**
442      * Test that the directory scanning does not enter into not matching directories.
443      *
444      * @see <a href="https://github.com/codehaus-plexus/plexus-utils/issues/63">Issue #63</a>
445      * @throws java.io.IOException if occurs an I/O error.
446      */
447     @Test
448     void doNotScanUnnecesaryDirectories() throws Exception {
449         createTestDirectories();
450 
451         // create additional directories 'anotherDir1', 'anotherDir2' and 'anotherDir3' with a 'file1.dat' file
452         FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "testDir123" + File.separator
453                 + "anotherDir1");
454         FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "test_dir_123" + File.separator
455                 + "anotherDir2");
456         FileUtils.mkdir(testDir + File.separator + "directoryTest" + File.separator + "test-dir-123" + File.separator
457                 + "anotherDir3");
458 
459         this.createFile(
460                 new File(testDir + File.separator + "directoryTest" + File.separator + "testDir123" + File.separator
461                         + "anotherDir1" + File.separator + "file1.dat"),
462                 0);
463         this.createFile(
464                 new File(testDir + File.separator + "directoryTest" + File.separator + "test_dir_123" + File.separator
465                         + "anotherDir2" + File.separator + "file1.dat"),
466                 0);
467         this.createFile(
468                 new File(testDir + File.separator + "directoryTest" + File.separator + "test-dir-123" + File.separator
469                         + "anotherDir3" + File.separator + "file1.dat"),
470                 0);
471 
472         String[] excludedPaths = {
473             "directoryTest" + File.separator + "testDir123" + File.separator + "anotherDir1" + File.separator
474                     + "file1.dat",
475             "directoryTest" + File.separator + "test_dir_123" + File.separator + "anotherDir2" + File.separator
476                     + "file1.dat",
477             "directoryTest" + File.separator + "test-dir-123" + File.separator + "anotherDir3" + File.separator
478                     + "file1.dat"
479         };
480 
481         String[] includedPaths = {
482             "directoryTest" + File.separator + "testDir123" + File.separator + "file1.dat",
483             "directoryTest" + File.separator + "test_dir_123" + File.separator + "file1.dat",
484             "directoryTest" + File.separator + "test-dir-123" + File.separator + "file1.dat"
485         };
486 
487         final Set<String> scannedDirSet = new HashSet<>();
488 
489         DirectoryScanner ds = new DirectoryScanner() {
490             @Override
491             protected void scandir(File dir, String vpath, boolean fast) {
492                 scannedDirSet.add(dir.getName());
493                 super.scandir(dir, vpath, fast);
494             }
495         };
496 
497         // one '*' matches only ONE directory level
498         String[] includes = {"directoryTest" + File.separator + "*" + File.separator + "file1.dat"};
499         ds.setIncludes(includes);
500         ds.setBasedir(new File(testDir));
501         ds.scan();
502 
503         assertInclusionsAndExclusions(ds.getIncludedFiles(), excludedPaths, includedPaths);
504 
505         Set<String> expectedScannedDirSet =
506                 new HashSet<>(Arrays.asList("io", "directoryTest", "testDir123", "test_dir_123", "test-dir-123"));
507 
508         assertEquals(expectedScannedDirSet, scannedDirSet);
509     }
510 
511     @Test
512     void isSymbolicLink() throws Exception {
513         assumeTrue(checkTestFilesSymlinks());
514 
515         final File directory = new File("src/test/resources/symlinks/src");
516         DirectoryScanner ds = new DirectoryScanner();
517         assertTrue(ds.isSymbolicLink(directory, "symR"));
518         assertTrue(ds.isSymbolicLink(directory, "symDir"));
519         assertFalse(ds.isSymbolicLink(directory, "fileR.txt"));
520         assertFalse(ds.isSymbolicLink(directory, "aRegularDir"));
521     }
522 
523     @Test
524     void isParentSymbolicLink() throws Exception {
525         assumeTrue(checkTestFilesSymlinks());
526 
527         final File directory = new File("src/test/resources/symlinks/src");
528         DirectoryScanner ds = new DirectoryScanner();
529         assertFalse(ds.isParentSymbolicLink(directory, "symR"));
530         assertFalse(ds.isParentSymbolicLink(directory, "symDir"));
531         assertFalse(ds.isParentSymbolicLink(directory, "fileR.txt"));
532         assertFalse(ds.isParentSymbolicLink(directory, "aRegularDir"));
533         assertFalse(ds.isParentSymbolicLink(new File(directory, "aRegularDir"), "aRegulatFile.txt"));
534         assertTrue(ds.isParentSymbolicLink(new File(directory, "symDir"), "targetFile.txt"));
535         assertTrue(
536                 ds.isParentSymbolicLink(new File(directory, "symLinkToDirOnTheOutside"), "FileInDirOnTheOutside.txt"));
537     }
538 
539     @Test
540     void defaultExcludes() throws Exception {
541         DirectoryScanner ds = new DirectoryScanner();
542         // work in src directory as target has filtering already applied with outdated default excludes
543         // (https://maven.apache.org/plugins/maven-resources-plugin/resources-mojo.html#addDefaultExcludes)
544         ds.setBasedir(new File("src/test/resources/directory-scanner-default-excludes").getCanonicalFile());
545 
546         ds.addDefaultExcludes();
547         ds.scan();
548 
549         assertInclusionsAndExclusions(
550                 ds.getIncludedFiles(), new String[] {}, ".gitignore", ".gitattributes", ".cvsignore");
551     }
552 
553     private void printTestHeader() {
554         StackTraceElement ste = new Throwable().getStackTrace()[1];
555         System.out.println("Test: " + ste.getMethodName());
556     }
557 
558     private void assertInclusionsAndExclusions(String[] files, String[] excludedPaths, String... includedPaths) {
559         Arrays.sort(files);
560 
561         System.out.println("Included files: ");
562         for (String file : files) {
563             System.out.println(file);
564         }
565 
566         List<String> failedToExclude = new ArrayList<>();
567         for (String excludedPath : excludedPaths) {
568             String alt = excludedPath.replace('/', '\\');
569             System.out.println("Searching for exclusion as: " + excludedPath + "\nor: " + alt);
570             if (Arrays.binarySearch(files, excludedPath) > -1 || Arrays.binarySearch(files, alt) > -1) {
571                 failedToExclude.add(excludedPath);
572             }
573         }
574 
575         List<String> failedToInclude = new ArrayList<>();
576         for (String includedPath : includedPaths) {
577             String alt = includedPath.replace('/', '\\');
578             System.out.println("Searching for inclusion as: " + includedPath + "\nor: " + alt);
579             if (Arrays.binarySearch(files, includedPath) < 0 && Arrays.binarySearch(files, alt) < 0) {
580                 failedToInclude.add(includedPath);
581             }
582         }
583 
584         StringBuilder buffer = new StringBuilder();
585         if (!failedToExclude.isEmpty()) {
586             buffer.append("Should NOT have included:\n").append(StringUtils.join(failedToExclude.iterator(), "\n\t- "));
587         }
588 
589         if (!failedToInclude.isEmpty()) {
590             if (buffer.length() > 0) {
591                 buffer.append("\n\n");
592             }
593 
594             buffer.append("Should have included:\n").append(StringUtils.join(failedToInclude.iterator(), "\n\t- "));
595         }
596 
597         if (buffer.length() > 0) {
598             fail(buffer.toString());
599         }
600     }
601 
602     private void createFiles(File dir, String... paths) throws IOException {
603         for (String path1 : paths) {
604             String path = path1.replace('/', File.separatorChar).replace('\\', File.separatorChar);
605             File file = new File(dir, path);
606 
607             if (path.endsWith(File.separator)) {
608                 file.mkdirs();
609             } else {
610                 if (file.getParentFile() != null) {
611                     file.getParentFile().mkdirs();
612                 }
613 
614                 createFile(file, 0);
615             }
616         }
617     }
618 }