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