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