1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. 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,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.maven.shared.utils.io;
20
21 import javax.annotation.Nonnull;
22 import javax.annotation.Nullable;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.nio.file.Files;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Set;
32
33 /**
34 * <p>Class for scanning a directory for files/directories which match certain criteria.</p>
35 * <p>
36 * These criteria consist of selectors and patterns which have been specified. With the selectors you can select which
37 * files you want to have included. Files which are not selected are excluded. With patterns you can include or exclude
38 * files based on their filename.
39 * </p>
40 * <p>
41 * The idea is simple. A given directory is recursively scanned for all files and directories. Each file/directory is
42 * matched against a set of selectors, including special support for matching against filenames with include and and
43 * exclude patterns. Only files/directories which match at least one pattern of the include pattern list or other file
44 * selector, and don't match any pattern of the exclude pattern list or fail to match against a required selector will
45 * be placed in the list of files/directories found.
46 * </p>
47 * <p>
48 * When no list of include patterns is supplied, "**" will be used, which means that everything will be matched. When no
49 * list of exclude patterns is supplied, an empty list is used, such that nothing will be excluded. When no selectors
50 * are supplied, none are applied.
51 * </p>
52 * <p>
53 * The filename pattern matching is done as follows: The name to be matched is split up in path segments. A path segment
54 * is the name of a directory or file, which is bounded by <code>File.separator</code> ('/' under UNIX, '\' under
55 * Windows). For example, "abc/def/ghi/xyz.java" is split up in the segments "abc", "def","ghi" and "xyz.java". The same
56 * is done for the pattern against which should be matched.
57 * </p>
58 * <p>
59 * The segments of the name and the pattern are then matched against each other. When '**' is used for a path segment in
60 * the pattern, it matches zero or more path segments of the name.
61 * </p>
62 * <p>
63 * There is a special case regarding the use of <code>File.separator</code>s at the beginning of the pattern and the
64 * string to match:<br>
65 * When a pattern starts with a <code>File.separator</code>, the string to match must also start with a
66 * <code>File.separator</code>. When a pattern does not start with a <code>File.separator</code>, the string to match
67 * may not start with a <code>File.separator</code>. When one of these rules is not obeyed, the string will not match.
68 * </p>
69 * <p>
70 * When a name path segment is matched against a pattern path segment, the following special characters can be used:<br>
71 * '*' matches zero or more characters<br>
72 * '?' matches one character.
73 * </p>
74 * <p>
75 * Examples:
76 * <ul>
77 * <li>"**\*.class" matches all .class files/dirs in a directory tree.</li>
78 * <li>"test\a??.java" matches all files/dirs which start with an 'a', then two more characters and then ".java", in a
79 * directory called test.</li>
80 * <li>"**" matches everything in a directory tree.</li>
81 * <li>"**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where there is a parent directory called test
82 * (e.g. "abc\test\def\ghi\XYZ123").</li>
83 * </ul>
84 * <p>
85 * Case sensitivity may be turned off if necessary. By default, it is turned on.
86 * </p>
87 * <p>
88 * Example of usage:
89 * </p>
90 * <pre>
91 * String[] includes = { "**\\*.class" };
92 * String[] excludes = { "modules\\*\\**" };
93 * ds.setIncludes( includes );
94 * ds.setExcludes( excludes );
95 * ds.setBasedir( new File( "test" ) );
96 * ds.setCaseSensitive( true );
97 * ds.scan();
98 *
99 * System.out.println( "FILES:" );
100 * String[] files = ds.getIncludedFiles();
101 * for ( int i = 0; i < files.length; i++ )
102 * {
103 * System.out.println( files[i] );
104 * }
105 * </pre>
106 * <p>
107 * This will scan a directory called test for .class files, but excludes all files in all proper subdirectories of a
108 * directory called "modules"
109 * </p>
110 * <p>
111 * This class must not be used from multiple Threads concurrently!
112 * </p>
113 *
114 * @author Arnout J. Kuiper <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
115 * @author Magesh Umasankar
116 * @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
117 * @author <a href="mailto:levylambert@tiscali-dsl.de">Antoine Levy-Lambert</a>
118 * @deprecated use {@code java.nio.file.DirectoryStream} or {@code java.nio.Files.walkFileTree()}
119 * and related classes
120 */
121 @Deprecated
122 public class DirectoryScanner {
123 /**
124 * Patterns which should be excluded by default.
125 *
126 * @see #addDefaultExcludes()
127 */
128 public static final String[] DEFAULTEXCLUDES = {
129 // Miscellaneous typical temporary files
130 "**/*~",
131 "**/#*#",
132 "**/.#*",
133 "**/%*%",
134 "**/._*",
135
136 // CVS
137 "**/CVS",
138 "**/CVS/**",
139 "**/.cvsignore",
140
141 // Subversion
142 "**/.svn",
143 "**/.svn/**",
144
145 // Arch
146 "**/.arch-ids",
147 "**/.arch-ids/**",
148
149 // Bazaar
150 "**/.bzr",
151 "**/.bzr/**",
152
153 // SurroundSCM
154 "**/.MySCMServerInfo",
155
156 // Mac
157 "**/.DS_Store",
158
159 // Serena Dimensions Version 10
160 "**/.metadata",
161 "**/.metadata/**",
162
163 // Mercurial
164 "**/.hg",
165 "**/.hg/**",
166
167 // git
168 "**/.git",
169 "**/.git/**",
170
171 // BitKeeper
172 "**/BitKeeper",
173 "**/BitKeeper/**",
174 "**/ChangeSet",
175 "**/ChangeSet/**",
176
177 // darcs
178 "**/_darcs",
179 "**/_darcs/**",
180 "**/.darcsrepo",
181 "**/.darcsrepo/**",
182 "**/-darcs-backup*",
183 "**/.darcs-temp-mail"
184 };
185
186 /**
187 * The base directory to be scanned.
188 */
189 private File basedir;
190
191 /**
192 * The patterns for the files to be included.
193 */
194 private String[] includes;
195
196 /**
197 * The patterns for the files to be excluded.
198 */
199 private String[] excludes;
200
201 private MatchPatterns excludesPatterns;
202
203 private MatchPatterns includesPatterns;
204
205 /**
206 * The files which matched at least one include and no excludes and were selected.
207 */
208 private List<String> filesIncluded;
209
210 /**
211 * The files which did not match any includes or selectors.
212 */
213 private List<String> filesNotIncluded;
214
215 /**
216 * The files which matched at least one include and at least one exclude.
217 */
218 private List<String> filesExcluded;
219
220 /**
221 * The directories which matched at least one include and no excludes and were selected.
222 */
223 private List<String> dirsIncluded;
224
225 /**
226 * The directories which were found and did not match any includes.
227 */
228 private List<String> dirsNotIncluded;
229
230 /**
231 * The directories which matched at least one include and at least one exclude.
232 */
233 private List<String> dirsExcluded;
234
235 /**
236 * Whether or not our results were built by a slow scan.
237 */
238 private boolean haveSlowResults = false;
239
240 /**
241 * Whether or not the file system should be treated as a case sensitive one.
242 */
243 private boolean isCaseSensitive = true;
244
245 /**
246 * Whether or not symbolic links should be followed.
247 *
248 *
249 */
250 private boolean followSymlinks = true;
251
252 /**
253 * A {@link ScanConductor} an control the scanning process.
254 */
255 private ScanConductor scanConductor = null;
256
257 /**
258 * The last ScanAction. We need to store this in the instance as the scan() method doesn't return
259 */
260 private ScanConductor.ScanAction scanAction = null;
261
262 /**
263 * Sole constructor.
264 */
265 public DirectoryScanner() {}
266
267 /**
268 * Sets the base directory to be scanned. This is the directory which is scanned recursively. All '/' and '\'
269 * characters are replaced by <code>File.separatorChar</code>, so the separator used need not match
270 * <code>File.separatorChar</code>.
271 *
272 * @param basedir The base directory to scan. Must not be <code>null</code>.
273 */
274 public void setBasedir(final String basedir) {
275 setBasedir(new File(basedir.replace('/', File.separatorChar).replace('\\', File.separatorChar)));
276 }
277
278 /**
279 * Sets the base directory to be scanned. This is the directory which is scanned recursively.
280 *
281 * @param basedir The base directory for scanning. Should not be <code>null</code>.
282 */
283 public void setBasedir(@Nonnull final File basedir) {
284 this.basedir = basedir;
285 }
286
287 /**
288 * Returns the base directory to be scanned. This is the directory which is scanned recursively.
289 *
290 * @return the base directory to be scanned
291 */
292 public File getBasedir() {
293 return basedir;
294 }
295
296 /**
297 * Sets whether or not the file system should be regarded as case sensitive.
298 *
299 * @param isCaseSensitiveParameter whether or not the file system should be regarded as a case sensitive one
300 */
301 public void setCaseSensitive(final boolean isCaseSensitiveParameter) {
302 this.isCaseSensitive = isCaseSensitiveParameter;
303 }
304
305 /**
306 * Sets whether or not symbolic links should be followed.
307 *
308 * @param followSymlinks whether or not symbolic links should be followed
309 */
310 public void setFollowSymlinks(final boolean followSymlinks) {
311 this.followSymlinks = followSymlinks;
312 }
313
314 /**
315 * Sets the list of include patterns to use. All '/' and '\' characters are replaced by
316 * <code>File.separatorChar</code>, so the separator used need not match <code>File.separatorChar</code>.
317 * <p>
318 * When a pattern ends with a '/' or '\', "**" is appended.
319 * </p>
320 *
321 * @param includes A list of include patterns. May be <code>null</code>, indicating that all files should be
322 * included. If a non-<code>null</code> list is given, all elements must be non-<code>null</code>.
323 */
324 public void setIncludes(final String... includes) {
325 if (includes == null) {
326 this.includes = null;
327 } else {
328 this.includes = new String[includes.length];
329 for (int i = 0; i < includes.length; i++) {
330 if (includes[i] == null) {
331 throw new NullPointerException(messageForNullListElement("includes"));
332 }
333 String pattern;
334 pattern = includes[i].trim().replace('/', File.separatorChar).replace('\\', File.separatorChar);
335 if (pattern.endsWith(File.separator)) {
336 pattern += "**";
337 }
338 this.includes[i] = pattern;
339 }
340 }
341 }
342
343 /**
344 * Sets the list of exclude patterns to use. All '/' and '\' characters are replaced by
345 * <code>File.separatorChar</code>, so the separator used need not match <code>File.separatorChar</code>.
346 * <p>
347 * When a pattern ends with a '/' or '\', "**" is appended.
348 * </p>
349 *
350 * @param excludes A list of exclude patterns. May be <code>null</code>, indicating that no files should be
351 * excluded. If a non-<code>null</code> list is given, all elements must be non-<code>null</code>.
352 */
353 public void setExcludes(final String... excludes) {
354 if (excludes == null) {
355 this.excludes = null;
356 } else {
357 this.excludes = new String[excludes.length];
358 for (int i = 0; i < excludes.length; i++) {
359 if (excludes[i] == null) {
360 throw new NullPointerException(messageForNullListElement("excludes"));
361 }
362 String pattern;
363 pattern = excludes[i].trim().replace('/', File.separatorChar).replace('\\', File.separatorChar);
364 if (pattern.endsWith(File.separator)) {
365 pattern += "**";
366 }
367 this.excludes[i] = pattern;
368 }
369 }
370 }
371
372 private static String messageForNullListElement(String listName) {
373 return "If a non-null " + listName + " list is given, all elements must be non-null";
374 }
375
376 /**
377 * @param scanConductor {@link #scanConductor}
378 */
379 public void setScanConductor(final ScanConductor scanConductor) {
380 this.scanConductor = scanConductor;
381 }
382
383 /**
384 * Scans the base directory for files which match at least one include pattern and don't match any exclude patterns.
385 * If there are selectors then the files must pass muster there, as well.
386 *
387 * @throws IllegalStateException if the base directory was set incorrectly (i.e. if it is <code>null</code>,
388 * doesn't exist, or isn't a directory).
389 */
390 public void scan() throws IllegalStateException {
391 if (basedir == null) {
392 throw new IllegalStateException("No basedir set");
393 }
394 if (!basedir.exists()) {
395 throw new IllegalStateException("basedir " + basedir + " does not exist");
396 }
397 if (!basedir.isDirectory()) {
398 throw new IllegalStateException("basedir " + basedir + " is not a directory");
399 }
400
401 setupDefaultFilters();
402 setupMatchPatterns();
403
404 filesIncluded = new ArrayList<String>();
405 filesNotIncluded = new ArrayList<String>();
406 filesExcluded = new ArrayList<String>();
407 dirsIncluded = new ArrayList<String>();
408 dirsNotIncluded = new ArrayList<String>();
409 dirsExcluded = new ArrayList<String>();
410 scanAction = ScanConductor.ScanAction.CONTINUE;
411
412 if (isIncluded("")) {
413 if (!isExcluded("")) {
414 if (scanConductor != null) {
415 scanAction = scanConductor.visitDirectory("", basedir);
416
417 if (ScanConductor.ScanAction.ABORT.equals(scanAction)
418 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals(scanAction)
419 || ScanConductor.ScanAction.NO_RECURSE.equals(scanAction)) {
420 return;
421 }
422 }
423
424 dirsIncluded.add("");
425 } else {
426 dirsExcluded.add("");
427 }
428 } else {
429 dirsNotIncluded.add("");
430 }
431 scandir(basedir, "", true);
432 }
433
434 /**
435 * Determine the file differences between the currently included files and
436 * a previously captured list of files.
437 * This method will not look for a changed in content but sole in the
438 * list of files given.
439 * <p>
440 * The method will compare the given array of file Strings with the result
441 * of the last directory scan. It will execute a {@link #scan()} if no
442 * result of a previous scan could be found.
443 * </p>
444 * <p>
445 * The result of the diff can be queried by the methods
446 * {@link DirectoryScanResult#getFilesAdded()} and {@link DirectoryScanResult#getFilesRemoved()}
447 * </p>
448 *
449 * @param oldFiles the list of previously captured files names.
450 * @return the result of the directory scan.
451 */
452 public DirectoryScanResult diffIncludedFiles(String... oldFiles) {
453 if (filesIncluded == null) {
454 // perform a scan if the directory didn't got scanned yet
455 scan();
456 }
457
458 return diffFiles(oldFiles, filesIncluded.toArray(new String[filesIncluded.size()]));
459 }
460
461 /**
462 * @param oldFiles array of old files
463 * @param newFiles array of new files
464 * @return calculated difference
465 */
466 public static DirectoryScanResult diffFiles(@Nullable String[] oldFiles, @Nullable String[] newFiles) {
467 Set<String> oldFileSet = arrayAsHashSet(oldFiles);
468 Set<String> newFileSet = arrayAsHashSet(newFiles);
469
470 List<String> added = new ArrayList<String>();
471 List<String> removed = new ArrayList<String>();
472
473 for (String oldFile : oldFileSet) {
474 if (!newFileSet.contains(oldFile)) {
475 removed.add(oldFile);
476 }
477 }
478
479 for (String newFile : newFileSet) {
480 if (!oldFileSet.contains(newFile)) {
481 added.add(newFile);
482 }
483 }
484
485 String[] filesAdded = added.toArray(new String[added.size()]);
486 String[] filesRemoved = removed.toArray(new String[removed.size()]);
487
488 return new DirectoryScanResult(filesAdded, filesRemoved);
489 }
490
491 /**
492 * Take an array of type T and convert it into a HashSet of type T.
493 * If <code>null</code> or an empty array gets passed, an empty Set will be returned.
494 *
495 * @param array The array
496 * @return the filled HashSet of type T
497 */
498 private static <T> Set<T> arrayAsHashSet(@Nullable T[] array) {
499 if (array == null || array.length == 0) {
500 return Collections.emptySet();
501 }
502
503 Set<T> set = new HashSet<T>(array.length);
504 Collections.addAll(set, array);
505
506 return set;
507 }
508
509 /**
510 * Top level invocation for a slow scan. A slow scan builds up a full list of excluded/included files/directories,
511 * whereas a fast scan will only have full results for included files, as it ignores directories which can't
512 * possibly hold any included files/directories.
513 * <p/>
514 * Returns immediately if a slow scan has already been completed.
515 */
516 void slowScan() {
517 if (haveSlowResults) {
518 return;
519 }
520
521 final String[] excl = dirsExcluded.toArray(new String[dirsExcluded.size()]);
522
523 final String[] notIncl = dirsNotIncluded.toArray(new String[dirsNotIncluded.size()]);
524
525 for (String anExcl : excl) {
526 if (!couldHoldIncluded(anExcl)) {
527 scandir(new File(basedir, anExcl), anExcl + File.separator, false);
528 }
529 }
530
531 for (String aNotIncl : notIncl) {
532 if (!couldHoldIncluded(aNotIncl)) {
533 scandir(new File(basedir, aNotIncl), aNotIncl + File.separator, false);
534 }
535 }
536
537 haveSlowResults = true;
538 }
539
540 /**
541 * Scans the given directory for files and directories. Found files and directories are placed in their respective
542 * collections, based on the matching of includes, excludes, and the selectors. When a directory is found, it is
543 * scanned recursively.
544 *
545 * @param dir The directory to scan. Must not be <code>null</code>.
546 * @param vpath The path relative to the base directory (needed to prevent problems with an absolute path when using
547 * dir). Must not be <code>null</code>.
548 * @param fast Whether or not this call is part of a fast scan.
549 * @see #filesIncluded
550 * @see #filesNotIncluded
551 * @see #filesExcluded
552 * @see #dirsIncluded
553 * @see #dirsNotIncluded
554 * @see #dirsExcluded
555 * @see #slowScan
556 */
557 void scandir(@Nonnull final File dir, @Nonnull final String vpath, final boolean fast) {
558 String[] newfiles = dir.list();
559
560 if (newfiles == null) {
561 /*
562 * two reasons are mentioned in the API docs for File.list (1) dir is not a directory. This is impossible as
563 * we wouldn't get here in this case. (2) an IO error occurred (why doesn't it throw an exception then???)
564 */
565
566 /*
567 * [jdcasey] (2) is apparently happening to me, as this is killing one of my tests... this is affecting the
568 * assembly plugin, fwiw. I will initialize the newfiles array as zero-length for now. NOTE: I can't find
569 * the problematic code, as it appears to come from a native method in UnixFileSystem...
570 */
571 newfiles = new String[0];
572
573 // throw new IOException( "IO error scanning directory " + dir.getAbsolutePath() );
574 }
575
576 if (!followSymlinks) {
577 newfiles = doNotFollowSymbolicLinks(dir, vpath, newfiles);
578 }
579
580 for (final String newfile : newfiles) {
581 final String name = vpath + newfile;
582 final File file = new File(dir, newfile);
583 if (file.isDirectory()) {
584 if (isIncluded(name)) {
585 if (!isExcluded(name)) {
586 if (scanConductor != null) {
587 scanAction = scanConductor.visitDirectory(name, file);
588
589 if (ScanConductor.ScanAction.ABORT.equals(scanAction)
590 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals(scanAction)) {
591 return;
592 }
593 }
594
595 if (!ScanConductor.ScanAction.NO_RECURSE.equals(scanAction)) {
596 dirsIncluded.add(name);
597 if (fast) {
598 scandir(file, name + File.separator, fast);
599
600 if (ScanConductor.ScanAction.ABORT.equals(scanAction)) {
601 return;
602 }
603 }
604 }
605 scanAction = null;
606
607 } else {
608 dirsExcluded.add(name);
609 if (fast && couldHoldIncluded(name)) {
610 scandir(file, name + File.separator, fast);
611 if (ScanConductor.ScanAction.ABORT.equals(scanAction)) {
612 return;
613 }
614 scanAction = null;
615 }
616 }
617 } else {
618 if (fast && couldHoldIncluded(name)) {
619 if (scanConductor != null) {
620 scanAction = scanConductor.visitDirectory(name, file);
621
622 if (ScanConductor.ScanAction.ABORT.equals(scanAction)
623 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals(scanAction)) {
624 return;
625 }
626 }
627 if (!ScanConductor.ScanAction.NO_RECURSE.equals(scanAction)) {
628 dirsNotIncluded.add(name);
629
630 scandir(file, name + File.separator, fast);
631 if (ScanConductor.ScanAction.ABORT.equals(scanAction)) {
632 return;
633 }
634 }
635 scanAction = null;
636 }
637 }
638 if (!fast) {
639 scandir(file, name + File.separator, fast);
640 if (ScanConductor.ScanAction.ABORT.equals(scanAction)) {
641 return;
642 }
643 scanAction = null;
644 }
645 } else if (file.isFile()) {
646 if (isIncluded(name)) {
647 if (!isExcluded(name)) {
648 if (scanConductor != null) {
649 scanAction = scanConductor.visitFile(name, file);
650 }
651
652 if (ScanConductor.ScanAction.ABORT.equals(scanAction)
653 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals(scanAction)) {
654 return;
655 }
656
657 filesIncluded.add(name);
658 } else {
659 filesExcluded.add(name);
660 }
661 } else {
662 filesNotIncluded.add(name);
663 }
664 }
665 }
666 }
667
668 private String[] doNotFollowSymbolicLinks(final File dir, final String vpath, String[] newfiles) {
669 final List<String> noLinks = new ArrayList<String>();
670 for (final String newfile : newfiles) {
671 try {
672 if (isSymbolicLink(dir, newfile)) {
673 final String name = vpath + newfile;
674 final File file = new File(dir, newfile);
675 if (file.isDirectory()) {
676 dirsExcluded.add(name);
677 } else {
678 filesExcluded.add(name);
679 }
680 } else {
681 noLinks.add(newfile);
682 }
683 } catch (final IOException ioe) {
684 final String msg = "IOException caught while checking " + "for links, couldn't get canonical path!";
685 // will be caught and redirected to Ant's logging system
686 System.err.println(msg);
687 noLinks.add(newfile);
688 }
689 }
690 newfiles = noLinks.toArray(new String[noLinks.size()]);
691 return newfiles;
692 }
693
694 /**
695 * Tests whether or not a name matches against at least one include pattern.
696 *
697 * @param name The name to match. Must not be <code>null</code>.
698 * @return <code>true</code> when the name matches against at least one include pattern, or <code>false</code>
699 * otherwise.
700 */
701 boolean isIncluded(final String name) {
702 return includesPatterns.matches(name, isCaseSensitive);
703 }
704
705 /**
706 * Tests whether or not a name matches the start of at least one include pattern.
707 *
708 * @param name The name to match. Must not be <code>null</code>.
709 * @return <code>true</code> when the name matches against the start of at least one include pattern, or
710 * <code>false</code> otherwise.
711 */
712 boolean couldHoldIncluded(@Nonnull final String name) {
713 return includesPatterns.matchesPatternStart(name, isCaseSensitive);
714 }
715
716 /**
717 * Tests whether or not a name matches against at least one exclude pattern.
718 *
719 * @param name The name to match. Must not be <code>null</code>.
720 * @return <code>true</code> when the name matches against at least one exclude pattern, or <code>false</code>
721 * otherwise.
722 */
723 boolean isExcluded(@Nonnull final String name) {
724 return excludesPatterns.matches(name, isCaseSensitive);
725 }
726
727 /**
728 * Returns the names of the files which matched at least one of the include patterns and none of the exclude
729 * patterns. The names are relative to the base directory.
730 *
731 * @deprecated this method does not work correctly on Windows.
732 * @return the names of the files which matched at least one of the include patterns and none of the exclude
733 * patterns. May also contain symbolic links to files.
734 */
735 @Deprecated
736 public String[] getIncludedFiles() {
737 if (filesIncluded == null) {
738 return new String[0];
739 }
740 return filesIncluded.toArray(new String[filesIncluded.size()]);
741 }
742
743 /**
744 * Returns the names of the files which matched none of the include patterns. The names are relative to the base
745 * directory. This involves performing a slow scan if one has not already been completed.
746 *
747 * @return the names of the files which matched none of the include patterns.
748 * @see #slowScan
749 */
750 public String[] getNotIncludedFiles() {
751 slowScan();
752 return filesNotIncluded.toArray(new String[filesNotIncluded.size()]);
753 }
754
755 /**
756 * Returns the names of the files which matched at least one of the include patterns and at least one of the exclude
757 * patterns. The names are relative to the base directory. This involves performing a slow scan if one has not
758 * already been completed.
759 *
760 * @return the names of the files which matched at least one of the include patterns and at at least one of the
761 * exclude patterns.
762 * @see #slowScan
763 */
764 public String[] getExcludedFiles() {
765 slowScan();
766 return filesExcluded.toArray(new String[filesExcluded.size()]);
767 }
768
769 /**
770 * Returns the names of the directories which matched at least one of the include patterns and none of the exclude
771 * patterns. The names are relative to the base directory.
772 *
773 * @deprecated this method is buggy. Do not depend on it.
774 * @return the names of the directories which matched at least one of the include patterns and none of the exclude
775 * patterns. May also contain symbolic links to directories.
776 */
777 @Deprecated
778 public String[] getIncludedDirectories() {
779 return dirsIncluded.toArray(new String[dirsIncluded.size()]);
780 }
781
782 /**
783 * Returns the names of the directories which matched none of the include patterns. The names are relative to the
784 * base directory. This involves performing a slow scan if one has not already been completed.
785 *
786 * @return the names of the directories which matched none of the include patterns.
787 * @see #slowScan
788 */
789 public String[] getNotIncludedDirectories() {
790 slowScan();
791 return dirsNotIncluded.toArray(new String[dirsNotIncluded.size()]);
792 }
793
794 /**
795 * Returns the names of the directories which matched at least one of the include patterns and at least one of the
796 * exclude patterns. The names are relative to the base directory. This involves performing a slow scan if one has
797 * not already been completed.
798 *
799 * @return the names of the directories which matched at least one of the include patterns and at least one of the
800 * exclude patterns.
801 * @see #slowScan
802 */
803 public String[] getExcludedDirectories() {
804 slowScan();
805 return dirsExcluded.toArray(new String[dirsExcluded.size()]);
806 }
807
808 /**
809 * Adds default exclusions to the current exclusions set.
810 */
811 public void addDefaultExcludes() {
812 final int excludesLength = excludes == null ? 0 : excludes.length;
813 String[] newExcludes;
814 newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length];
815 if (excludesLength > 0) {
816 System.arraycopy(excludes, 0, newExcludes, 0, excludesLength);
817 }
818 for (int i = 0; i < DEFAULTEXCLUDES.length; i++) {
819 newExcludes[i + excludesLength] =
820 DEFAULTEXCLUDES[i].replace('/', File.separatorChar).replace('\\', File.separatorChar);
821 }
822 excludes = newExcludes;
823 }
824
825 /**
826 * Checks whether a given file is a symbolic link.
827 * <p>
828 * It doesn't really test for symbolic links but whether the canonical and absolute paths of the file are identical
829 * - this may lead to false positives on some platforms.
830 * </p>
831 *
832 * @param parent the parent directory of the file to test
833 * @param name the name of the file to test.
834 *
835 */
836 boolean isSymbolicLink(final File parent, final String name) throws IOException {
837 return Files.isSymbolicLink(parent.toPath());
838 }
839
840 private void setupDefaultFilters() {
841 if (includes == null) {
842 // No includes supplied, so set it to 'matches all'
843 includes = new String[1];
844 includes[0] = "**";
845 }
846 if (excludes == null) {
847 excludes = new String[0];
848 }
849 }
850
851 private void setupMatchPatterns() {
852 includesPatterns = MatchPatterns.from(includes);
853 excludesPatterns = MatchPatterns.from(excludes);
854 }
855 }