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