View Javadoc
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 = { &quot;**\\*.class&quot; };
92   * String[] excludes = { &quot;modules\\*\\**&quot; };
93   * ds.setIncludes( includes );
94   * ds.setExcludes( excludes );
95   * ds.setBasedir( new File( &quot;test&quot; ) );
96   * ds.setCaseSensitive( true );
97   * ds.scan();
98   *
99   * System.out.println( &quot;FILES:&quot; );
100  * String[] files = ds.getIncludedFiles();
101  * for ( int i = 0; i &lt; 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 }