View Javadoc

1   /*
2    * The Apache Software License, Version 1.1
3    *
4    * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
5    * reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without
8    * modification, are permitted provided that the following conditions
9    * are met:
10   *
11   * 1. Redistributions of source code must retain the above copyright
12   *    notice, this list of conditions and the following disclaimer.
13   *
14   * 2. Redistributions in binary form must reproduce the above copyright
15   *    notice, this list of conditions and the following disclaimer in
16   *    the documentation and/or other materials provided with the
17   *    distribution.
18   *
19   * 3. The end-user documentation included with the redistribution, if
20   *    any, must include the following acknowlegement:
21   *       "This product includes software developed by the
22   *        Apache Software Foundation (http://www.apache.org/)."
23   *    Alternately, this acknowlegement may appear in the software itself,
24   *    if and wherever such third-party acknowlegements normally appear.
25   *
26   * 4. The names "Ant" and "Apache Software
27   *    Foundation" must not be used to endorse or promote products derived
28   *    from this software without prior written permission. For written
29   *    permission, please contact apache@apache.org.
30   *
31   * 5. Products derived from this software may not be called "Apache"
32   *    nor may "Apache" appear in their names without prior written
33   *    permission of the Apache Group.
34   *
35   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46   * SUCH DAMAGE.
47   * ====================================================================
48   *
49   * This software consists of voluntary contributions made by many
50   * individuals on behalf of the Apache Software Foundation.  For more
51   * information on the Apache Software Foundation, please see
52   * <http://www.apache.org/>.
53   */
54  
55  package org.apache.maven.it.util;
56  
57  import java.io.File;
58  import java.io.IOException;
59  import java.util.Vector;
60  
61  /**
62   * Class for scanning a directory for files/directories which match certain
63   * criteria.
64   * <p>
65   * These criteria consist of selectors and patterns which have been specified.
66   * With the selectors you can select which files you want to have included.
67   * Files which are not selected are excluded. With patterns you can include
68   * or exclude files based on their filename.
69   * <p>
70   * The idea is simple. A given directory is recursively scanned for all files
71   * and directories. Each file/directory is matched against a set of selectors,
72   * including special support for matching against filenames with include and
73   * and exclude patterns. Only files/directories which match at least one
74   * pattern of the include pattern list or other file selector, and don't match
75   * any pattern of the exclude pattern list or fail to match against a required
76   * selector will be placed in the list of files/directories found.
77   * <p>
78   * When no list of include patterns is supplied, "**" will be used, which
79   * means that everything will be matched. When no list of exclude patterns is
80   * supplied, an empty list is used, such that nothing will be excluded. When
81   * no selectors are supplied, none are applied.
82   * <p>
83   * The filename pattern matching is done as follows:
84   * The name to be matched is split up in path segments. A path segment is the
85   * name of a directory or file, which is bounded by
86   * <code>File.separator</code> ('/' under UNIX, '\' under Windows).
87   * For example, "abc/def/ghi/xyz.java" is split up in the segments "abc",
88   * "def","ghi" and "xyz.java".
89   * The same is done for the pattern against which should be matched.
90   * <p>
91   * The segments of the name and the pattern are then matched against each
92   * other. When '**' is used for a path segment in the pattern, it matches
93   * zero or more path segments of the name.
94   * <p>
95   * There is a special case regarding the use of <code>File.separator</code>s
96   * at the beginning of the pattern and the string to match:<br>
97   * When a pattern starts with a <code>File.separator</code>, the string
98   * to match must also start with a <code>File.separator</code>.
99   * When a pattern does not start with a <code>File.separator</code>, the
100  * string to match may not start with a <code>File.separator</code>.
101  * When one of these rules is not obeyed, the string will not
102  * match.
103  * <p>
104  * When a name path segment is matched against a pattern path segment, the
105  * following special characters can be used:<br>
106  * '*' matches zero or more characters<br>
107  * '?' matches one character.
108  * <p>
109  * Examples:
110  * <p>
111  * "**\*.class" matches all .class files/dirs in a directory tree.
112  * <p>
113  * "test\a??.java" matches all files/dirs which start with an 'a', then two
114  * more characters and then ".java", in a directory called test.
115  * <p>
116  * "**" matches everything in a directory tree.
117  * <p>
118  * "**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where
119  * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").
120  * <p>
121  * Case sensitivity may be turned off if necessary. By default, it is
122  * turned on.
123  * <p>
124  * Example of usage:
125  * <pre>
126  *   String[] includes = {"**\\*.class"};
127  *   String[] excludes = {"modules\\*\\**"};
128  *   ds.setIncludes(includes);
129  *   ds.setExcludes(excludes);
130  *   ds.setBasedir(new File("test"));
131  *   ds.setCaseSensitive(true);
132  *   ds.scan();
133  *
134  *   System.out.println("FILES:");
135  *   String[] files = ds.getIncludedFiles();
136  *   for (int i = 0; i < files.length; i++) {
137  *     System.out.println(files[i]);
138  *   }
139  * </pre>
140  * This will scan a directory called test for .class files, but excludes all
141  * files in all proper subdirectories of a directory called "modules"
142  *
143  * @author Arnout J. Kuiper
144  * <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
145  * @author Magesh Umasankar
146  * @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
147  * @author <a href="mailto:levylambert@tiscali-dsl.de">Antoine Levy-Lambert</a>
148  */
149 public class DirectoryScanner
150 {
151     /**
152      * Patterns which should be excluded by default.
153      *
154      * @see #addDefaultExcludes()
155      */
156     public static final String[] DEFAULTEXCLUDES = {
157         // Miscellaneous typical temporary files
158         "**/*~",
159         "**/#*#",
160         "**/.#*",
161         "**/%*%",
162         "**/._*",
163 
164         // CVS
165         "**/CVS",
166         "**/CVS/**",
167         "**/.cvsignore",
168 
169         // SCCS
170         "**/SCCS",
171         "**/SCCS/**",
172 
173         // Visual SourceSafe
174         "**/vssver.scc",
175 
176         // Subversion
177         "**/.svn",
178         "**/.svn/**",
179 
180         // Arch
181         "**/.arch-ids",
182         "**/.arch-ids/**",
183 
184         //Bazaar
185         "**/.bzr",
186         "**/.bzr/**",
187 
188         //SurroundSCM
189         "**/.MySCMServerInfo",
190 
191         // Mac
192         "**/.DS_Store"
193     };
194 
195     /** The base directory to be scanned. */
196     protected File basedir;
197 
198     /** The patterns for the files to be included. */
199     protected String[] includes;
200 
201     /** The patterns for the files to be excluded. */
202     protected String[] excludes;
203 
204     /** The files which matched at least one include and no excludes
205      *  and were selected.
206      */
207     protected Vector filesIncluded;
208 
209     /** The files which did not match any includes or selectors. */
210     protected Vector filesNotIncluded;
211 
212     /**
213      * The files which matched at least one include and at least
214      * one exclude.
215      */
216     protected Vector filesExcluded;
217 
218     /** The directories which matched at least one include and no excludes
219      *  and were selected.
220      */
221     protected Vector dirsIncluded;
222 
223     /** The directories which were found and did not match any includes. */
224     protected Vector dirsNotIncluded;
225 
226     /**
227      * The directories which matched at least one include and at least one
228      * exclude.
229      */
230     protected Vector dirsExcluded;
231 
232     /** The files which matched at least one include and no excludes and
233      *  which a selector discarded.
234      */
235     protected Vector filesDeselected;
236 
237     /** The directories which matched at least one include and no excludes
238      *  but which a selector discarded.
239      */
240     protected Vector dirsDeselected;
241 
242     /** Whether or not our results were built by a slow scan. */
243     protected boolean haveSlowResults = false;
244 
245     /**
246      * Whether or not the file system should be treated as a case sensitive
247      * one.
248      */
249     protected boolean isCaseSensitive = true;
250 
251     /**
252      * Whether or not symbolic links should be followed.
253      *
254      * @since Ant 1.5
255      */
256     private boolean followSymlinks = true;
257 
258     /** Whether or not everything tested so far has been included. */
259     protected boolean everythingIncluded = true;
260 
261     /**
262      * Sole constructor.
263      */
264     public DirectoryScanner()
265     {
266     }
267 
268     /**
269      * Tests whether or not a given path matches the start of a given
270      * pattern up to the first "**".
271      * <p>
272      * This is not a general purpose test and should only be used if you
273      * can live with false positives. For example, <code>pattern=**\a</code>
274      * and <code>str=b</code> will yield <code>true</code>.
275      *
276      * @param pattern The pattern to match against. Must not be
277      *                <code>null</code>.
278      * @param str     The path to match, as a String. Must not be
279      *                <code>null</code>.
280      *
281      * @return whether or not a given path matches the start of a given
282      * pattern up to the first "**".
283      */
284     protected static boolean matchPatternStart( String pattern, String str )
285     {
286         return SelectorUtils.matchPatternStart( pattern, str );
287     }
288 
289     /**
290      * Tests whether or not a given path matches the start of a given
291      * pattern up to the first "**".
292      * <p>
293      * This is not a general purpose test and should only be used if you
294      * can live with false positives. For example, <code>pattern=**\a</code>
295      * and <code>str=b</code> will yield <code>true</code>.
296      *
297      * @param pattern The pattern to match against. Must not be
298      *                <code>null</code>.
299      * @param str     The path to match, as a String. Must not be
300      *                <code>null</code>.
301      * @param isCaseSensitive Whether or not matching should be performed
302      *                        case sensitively.
303      *
304      * @return whether or not a given path matches the start of a given
305      * pattern up to the first "**".
306      */
307     protected static boolean matchPatternStart( String pattern, String str,
308                                                 boolean isCaseSensitive )
309     {
310         return SelectorUtils.matchPatternStart( pattern, str, isCaseSensitive );
311     }
312 
313     /**
314      * Tests whether or not a given path matches a given pattern.
315      *
316      * @param pattern The pattern to match against. Must not be
317      *                <code>null</code>.
318      * @param str     The path to match, as a String. Must not be
319      *                <code>null</code>.
320      *
321      * @return <code>true</code> if the pattern matches against the string,
322      *         or <code>false</code> otherwise.
323      */
324     protected static boolean matchPath( String pattern, String str )
325     {
326         return SelectorUtils.matchPath( pattern, str );
327     }
328 
329     /**
330      * Tests whether or not a given path matches a given pattern.
331      *
332      * @param pattern The pattern to match against. Must not be
333      *                <code>null</code>.
334      * @param str     The path to match, as a String. Must not be
335      *                <code>null</code>.
336      * @param isCaseSensitive Whether or not matching should be performed
337      *                        case sensitively.
338      *
339      * @return <code>true</code> if the pattern matches against the string,
340      *         or <code>false</code> otherwise.
341      */
342     protected static boolean matchPath( String pattern, String str,
343                                         boolean isCaseSensitive )
344     {
345         return SelectorUtils.matchPath( pattern, str, isCaseSensitive );
346     }
347 
348     /**
349      * Tests whether or not a string matches against a pattern.
350      * The pattern may contain two special characters:<br>
351      * '*' means zero or more characters<br>
352      * '?' means one and only one character
353      *
354      * @param pattern The pattern to match against.
355      *                Must not be <code>null</code>.
356      * @param str     The string which must be matched against the pattern.
357      *                Must not be <code>null</code>.
358      *
359      * @return <code>true</code> if the string matches against the pattern,
360      *         or <code>false</code> otherwise.
361      */
362     public static boolean match( String pattern, String str )
363     {
364         return SelectorUtils.match( pattern, str );
365     }
366 
367     /**
368      * Tests whether or not a string matches against a pattern.
369      * The pattern may contain two special characters:<br>
370      * '*' means zero or more characters<br>
371      * '?' means one and only one character
372      *
373      * @param pattern The pattern to match against.
374      *                Must not be <code>null</code>.
375      * @param str     The string which must be matched against the pattern.
376      *                Must not be <code>null</code>.
377      * @param isCaseSensitive Whether or not matching should be performed
378      *                        case sensitively.
379      *
380      *
381      * @return <code>true</code> if the string matches against the pattern,
382      *         or <code>false</code> otherwise.
383      */
384     protected static boolean match( String pattern, String str,
385                                     boolean isCaseSensitive )
386     {
387         return SelectorUtils.match( pattern, str, isCaseSensitive );
388     }
389 
390     /**
391      * Sets the base directory to be scanned. This is the directory which is
392      * scanned recursively. All '/' and '\' characters are replaced by
393      * <code>File.separatorChar</code>, so the separator used need not match
394      * <code>File.separatorChar</code>.
395      *
396      * @param basedir The base directory to scan.
397      *                Must not be <code>null</code>.
398      */
399     public void setBasedir( String basedir )
400     {
401         setBasedir( new File( basedir.replace( '/', File.separatorChar ).replace(
402             '\\', File.separatorChar ) ) );
403     }
404 
405     /**
406      * Sets the base directory to be scanned. This is the directory which is
407      * scanned recursively.
408      *
409      * @param basedir The base directory for scanning.
410      *                Should not be <code>null</code>.
411      */
412     public void setBasedir( File basedir )
413     {
414         this.basedir = basedir;
415     }
416 
417     /**
418      * Returns the base directory to be scanned.
419      * This is the directory which is scanned recursively.
420      *
421      * @return the base directory to be scanned
422      */
423     public File getBasedir()
424     {
425         return basedir;
426     }
427 
428     /**
429      * Sets whether or not the file system should be regarded as case sensitive.
430      *
431      * @param isCaseSensitive whether or not the file system should be
432      *                        regarded as a case sensitive one
433      */
434     public void setCaseSensitive( boolean isCaseSensitive )
435     {
436         this.isCaseSensitive = isCaseSensitive;
437     }
438 
439     /**
440      * Sets whether or not symbolic links should be followed.
441      *
442      * @param followSymlinks whether or not symbolic links should be followed
443      */
444     public void setFollowSymlinks( boolean followSymlinks )
445     {
446         this.followSymlinks = followSymlinks;
447     }
448 
449     /**
450      * Sets the list of include patterns to use. All '/' and '\' characters
451      * are replaced by <code>File.separatorChar</code>, so the separator used
452      * need not match <code>File.separatorChar</code>.
453      * <p>
454      * When a pattern ends with a '/' or '\', "**" is appended.
455      *
456      * @param includes A list of include patterns.
457      *                 May be <code>null</code>, indicating that all files
458      *                 should be included. If a non-<code>null</code>
459      *                 list is given, all elements must be
460      * non-<code>null</code>.
461      */
462     public void setIncludes( String[] includes )
463     {
464         if ( includes == null )
465         {
466             this.includes = null;
467         }
468         else
469         {
470             this.includes = new String[includes.length];
471             for ( int i = 0; i < includes.length; i++ )
472             {
473                 String pattern;
474                 pattern = includes[i].trim().replace( '/', File.separatorChar ).replace(
475                     '\\', File.separatorChar );
476                 if ( pattern.endsWith( File.separator ) )
477                 {
478                     pattern += "**";
479                 }
480                 this.includes[i] = pattern;
481             }
482         }
483     }
484 
485 
486     /**
487      * Sets the list of exclude patterns to use. All '/' and '\' characters
488      * are replaced by <code>File.separatorChar</code>, so the separator used
489      * need not match <code>File.separatorChar</code>.
490      * <p>
491      * When a pattern ends with a '/' or '\', "**" is appended.
492      *
493      * @param excludes A list of exclude patterns.
494      *                 May be <code>null</code>, indicating that no files
495      *                 should be excluded. If a non-<code>null</code> list is
496      *                 given, all elements must be non-<code>null</code>.
497      */
498     public void setExcludes( String[] excludes )
499     {
500         if ( excludes == null )
501         {
502             this.excludes = null;
503         }
504         else
505         {
506             this.excludes = new String[excludes.length];
507             for ( int i = 0; i < excludes.length; i++ )
508             {
509                 String pattern;
510                 pattern = excludes[i].trim().replace( '/', File.separatorChar ).replace(
511                     '\\', File.separatorChar );
512                 if ( pattern.endsWith( File.separator ) )
513                 {
514                     pattern += "**";
515                 }
516                 this.excludes[i] = pattern;
517             }
518         }
519     }
520 
521     /**
522      * Returns whether or not the scanner has included all the files or
523      * directories it has come across so far.
524      *
525      * @return <code>true</code> if all files and directories which have
526      *         been found so far have been included.
527      */
528     public boolean isEverythingIncluded()
529     {
530         return everythingIncluded;
531     }
532 
533     /**
534      * Scans the base directory for files which match at least one include
535      * pattern and don't match any exclude patterns. If there are selectors
536      * then the files must pass muster there, as well.
537      *
538      * @exception IllegalStateException if the base directory was set
539      *            incorrectly (i.e. if it is <code>null</code>, doesn't exist,
540      *            or isn't a directory).
541      */
542     public void scan() throws IllegalStateException
543     {
544         if ( basedir == null )
545         {
546             throw new IllegalStateException( "No basedir set" );
547         }
548         if ( !basedir.exists() )
549         {
550             throw new IllegalStateException( "basedir " + basedir
551                                              + " does not exist" );
552         }
553         if ( !basedir.isDirectory() )
554         {
555             throw new IllegalStateException( "basedir " + basedir
556                                              + " is not a directory" );
557         }
558 
559         if ( includes == null )
560         {
561             // No includes supplied, so set it to 'matches all'
562             includes = new String[1];
563             includes[0] = "**";
564         }
565         if ( excludes == null )
566         {
567             excludes = new String[0];
568         }
569 
570         filesIncluded = new Vector();
571         filesNotIncluded = new Vector();
572         filesExcluded = new Vector();
573         filesDeselected = new Vector();
574         dirsIncluded = new Vector();
575         dirsNotIncluded = new Vector();
576         dirsExcluded = new Vector();
577         dirsDeselected = new Vector();
578 
579         if ( isIncluded( "" ) )
580         {
581             if ( !isExcluded( "" ) )
582             {
583                 if ( isSelected( "", basedir ) )
584                 {
585                     dirsIncluded.addElement( "" );
586                 }
587                 else
588                 {
589                     dirsDeselected.addElement( "" );
590                 }
591             }
592             else
593             {
594                 dirsExcluded.addElement( "" );
595             }
596         }
597         else
598         {
599             dirsNotIncluded.addElement( "" );
600         }
601         scandir( basedir, "", true );
602     }
603 
604     /**
605      * Top level invocation for a slow scan. A slow scan builds up a full
606      * list of excluded/included files/directories, whereas a fast scan
607      * will only have full results for included files, as it ignores
608      * directories which can't possibly hold any included files/directories.
609      * <p>
610      * Returns immediately if a slow scan has already been completed.
611      */
612     protected void slowScan()
613     {
614         if ( haveSlowResults )
615         {
616             return;
617         }
618 
619         String[] excl = new String[dirsExcluded.size()];
620         dirsExcluded.copyInto( excl );
621 
622         String[] notIncl = new String[dirsNotIncluded.size()];
623         dirsNotIncluded.copyInto( notIncl );
624 
625         for ( int i = 0; i < excl.length; i++ )
626         {
627             if ( !couldHoldIncluded( excl[i] ) )
628             {
629                 scandir( new File( basedir, excl[i] ),
630                          excl[i] + File.separator, false );
631             }
632         }
633 
634         for ( int i = 0; i < notIncl.length; i++ )
635         {
636             if ( !couldHoldIncluded( notIncl[i] ) )
637             {
638                 scandir( new File( basedir, notIncl[i] ),
639                          notIncl[i] + File.separator, false );
640             }
641         }
642 
643         haveSlowResults = true;
644     }
645 
646     /**
647      * Scans the given directory for files and directories. Found files and
648      * directories are placed in their respective collections, based on the
649      * matching of includes, excludes, and the selectors.  When a directory
650      * is found, it is scanned recursively.
651      *
652      * @param dir   The directory to scan. Must not be <code>null</code>.
653      * @param vpath The path relative to the base directory (needed to
654      *              prevent problems with an absolute path when using
655      *              dir). Must not be <code>null</code>.
656      * @param fast  Whether or not this call is part of a fast scan.
657      * @throws IOException 
658      *
659      * @see #filesIncluded
660      * @see #filesNotIncluded
661      * @see #filesExcluded
662      * @see #dirsIncluded
663      * @see #dirsNotIncluded
664      * @see #dirsExcluded
665      * @see #slowScan
666      */
667     protected void scandir( File dir, String vpath, boolean fast )
668     {
669         String[] newfiles = dir.list();
670 
671         if ( newfiles == null )
672         {
673             /*
674              * two reasons are mentioned in the API docs for File.list
675              * (1) dir is not a directory. This is impossible as
676              *     we wouldn't get here in this case.
677              * (2) an IO error occurred (why doesn't it throw an exception
678              *     then???)
679              */
680             
681 
682             /*
683              * [jdcasey] (2) is apparently happening to me, as this is killing one of my tests... 
684              * this is affecting the assembly plugin, fwiw. I will initialize the newfiles array as 
685              * zero-length for now.
686              * 
687              * NOTE: I can't find the problematic code, as it appears to come from a native method 
688              * in UnixFileSystem...
689              */
690             newfiles = new String[0];
691             
692             // throw new IOException( "IO error scanning directory " + dir.getAbsolutePath() );
693         }
694 
695         if ( !followSymlinks )
696         {
697             Vector noLinks = new Vector();
698             for ( int i = 0; i < newfiles.length; i++ )
699             {
700                 try
701                 {
702                     if ( isSymbolicLink( dir, newfiles[i] ) )
703                     {
704                         String name = vpath + newfiles[i];
705                         File file = new File( dir, newfiles[i] );
706                         if ( file.isDirectory() )
707                         {
708                             dirsExcluded.addElement( name );
709                         }
710                         else
711                         {
712                             filesExcluded.addElement( name );
713                         }
714                     }
715                     else
716                     {
717                         noLinks.addElement( newfiles[i] );
718                     }
719                 }
720                 catch ( IOException ioe )
721                 {
722                     String msg = "IOException caught while checking "
723                         + "for links, couldn't get cannonical path!";
724                     // will be caught and redirected to Ant's logging system
725                     System.err.println( msg );
726                     noLinks.addElement( newfiles[i] );
727                 }
728             }
729             newfiles = new String[noLinks.size()];
730             noLinks.copyInto( newfiles );
731         }
732 
733         for ( int i = 0; i < newfiles.length; i++ )
734         {
735             String name = vpath + newfiles[i];
736             File file = new File( dir, newfiles[i] );
737             if ( file.isDirectory() )
738             {
739                 if ( isIncluded( name ) )
740                 {
741                     if ( !isExcluded( name ) )
742                     {
743                         if ( isSelected( name, file ) )
744                         {
745                             dirsIncluded.addElement( name );
746                             if ( fast )
747                             {
748                                 scandir( file, name + File.separator, fast );
749                             }
750                         }
751                         else
752                         {
753                             everythingIncluded = false;
754                             dirsDeselected.addElement( name );
755                             if ( fast && couldHoldIncluded( name ) )
756                             {
757                                 scandir( file, name + File.separator, fast );
758                             }
759                         }
760 
761                     }
762                     else
763                     {
764                         everythingIncluded = false;
765                         dirsExcluded.addElement( name );
766                         if ( fast && couldHoldIncluded( name ) )
767                         {
768                             scandir( file, name + File.separator, fast );
769                         }
770                     }
771                 }
772                 else
773                 {
774                     everythingIncluded = false;
775                     dirsNotIncluded.addElement( name );
776                     if ( fast && couldHoldIncluded( name ) )
777                     {
778                         scandir( file, name + File.separator, fast );
779                     }
780                 }
781                 if ( !fast )
782                 {
783                     scandir( file, name + File.separator, fast );
784                 }
785             }
786             else if ( file.isFile() )
787             {
788                 if ( isIncluded( name ) )
789                 {
790                     if ( !isExcluded( name ) )
791                     {
792                         if ( isSelected( name, file ) )
793                         {
794                             filesIncluded.addElement( name );
795                         }
796                         else
797                         {
798                             everythingIncluded = false;
799                             filesDeselected.addElement( name );
800                         }
801                     }
802                     else
803                     {
804                         everythingIncluded = false;
805                         filesExcluded.addElement( name );
806                     }
807                 }
808                 else
809                 {
810                     everythingIncluded = false;
811                     filesNotIncluded.addElement( name );
812                 }
813             }
814         }
815     }
816 
817     /**
818      * Tests whether or not a name matches against at least one include
819      * pattern.
820      *
821      * @param name The name to match. Must not be <code>null</code>.
822      * @return <code>true</code> when the name matches against at least one
823      *         include pattern, or <code>false</code> otherwise.
824      */
825     protected boolean isIncluded( String name )
826     {
827         for ( int i = 0; i < includes.length; i++ )
828         {
829             if ( matchPath( includes[i], name, isCaseSensitive ) )
830             {
831                 return true;
832             }
833         }
834         return false;
835     }
836 
837     /**
838      * Tests whether or not a name matches the start of at least one include
839      * pattern.
840      *
841      * @param name The name to match. Must not be <code>null</code>.
842      * @return <code>true</code> when the name matches against the start of at
843      *         least one include pattern, or <code>false</code> otherwise.
844      */
845     protected boolean couldHoldIncluded( String name )
846     {
847         for ( int i = 0; i < includes.length; i++ )
848         {
849             if ( matchPatternStart( includes[i], name, isCaseSensitive ) )
850             {
851                 return true;
852             }
853         }
854         return false;
855     }
856 
857     /**
858      * Tests whether or not a name matches against at least one exclude
859      * pattern.
860      *
861      * @param name The name to match. Must not be <code>null</code>.
862      * @return <code>true</code> when the name matches against at least one
863      *         exclude pattern, or <code>false</code> otherwise.
864      */
865     protected boolean isExcluded( String name )
866     {
867         for ( int i = 0; i < excludes.length; i++ )
868         {
869             if ( matchPath( excludes[i], name, isCaseSensitive ) )
870             {
871                 return true;
872             }
873         }
874         return false;
875     }
876 
877     /**
878      * Tests whether a name should be selected.
879      *
880      * @param name the filename to check for selecting
881      * @param file the java.io.File object for this filename
882      * @return <code>false</code> when the selectors says that the file
883      *         should not be selected, <code>true</code> otherwise.
884      */
885     protected boolean isSelected( String name, File file )
886     {
887         return true;
888     }
889 
890     /**
891      * Returns the names of the files which matched at least one of the
892      * include patterns and none of the exclude patterns.
893      * The names are relative to the base directory.
894      *
895      * @return the names of the files which matched at least one of the
896      *         include patterns and none of the exclude patterns.
897      */
898     public String[] getIncludedFiles()
899     {
900         String[] files = new String[filesIncluded.size()];
901         filesIncluded.copyInto( files );
902         return files;
903     }
904 
905     /**
906      * Returns the names of the files which matched none of the include
907      * patterns. The names are relative to the base directory. This involves
908      * performing a slow scan if one has not already been completed.
909      *
910      * @return the names of the files which matched none of the include
911      *         patterns.
912      *
913      * @see #slowScan
914      */
915     public String[] getNotIncludedFiles()
916     {
917         slowScan();
918         String[] files = new String[filesNotIncluded.size()];
919         filesNotIncluded.copyInto( files );
920         return files;
921     }
922 
923     /**
924      * Returns the names of the files which matched at least one of the
925      * include patterns and at least one of the exclude patterns.
926      * The names are relative to the base directory. This involves
927      * performing a slow scan if one has not already been completed.
928      *
929      * @return the names of the files which matched at least one of the
930      *         include patterns and at at least one of the exclude patterns.
931      *
932      * @see #slowScan
933      */
934     public String[] getExcludedFiles()
935     {
936         slowScan();
937         String[] files = new String[filesExcluded.size()];
938         filesExcluded.copyInto( files );
939         return files;
940     }
941 
942     /**
943      * <p>Returns the names of the files which were selected out and
944      * therefore not ultimately included.</p>
945      *
946      * <p>The names are relative to the base directory. This involves
947      * performing a slow scan if one has not already been completed.</p>
948      *
949      * @return the names of the files which were deselected.
950      *
951      * @see #slowScan
952      */
953     public String[] getDeselectedFiles()
954     {
955         slowScan();
956         String[] files = new String[filesDeselected.size()];
957         filesDeselected.copyInto( files );
958         return files;
959     }
960 
961     /**
962      * Returns the names of the directories which matched at least one of the
963      * include patterns and none of the exclude patterns.
964      * The names are relative to the base directory.
965      *
966      * @return the names of the directories which matched at least one of the
967      * include patterns and none of the exclude patterns.
968      */
969     public String[] getIncludedDirectories()
970     {
971         String[] directories = new String[dirsIncluded.size()];
972         dirsIncluded.copyInto( directories );
973         return directories;
974     }
975 
976     /**
977      * Returns the names of the directories which matched none of the include
978      * patterns. The names are relative to the base directory. This involves
979      * performing a slow scan if one has not already been completed.
980      *
981      * @return the names of the directories which matched none of the include
982      * patterns.
983      *
984      * @see #slowScan
985      */
986     public String[] getNotIncludedDirectories()
987     {
988         slowScan();
989         String[] directories = new String[dirsNotIncluded.size()];
990         dirsNotIncluded.copyInto( directories );
991         return directories;
992     }
993 
994     /**
995      * Returns the names of the directories which matched at least one of the
996      * include patterns and at least one of the exclude patterns.
997      * The names are relative to the base directory. This involves
998      * performing a slow scan if one has not already been completed.
999      *
1000      * @return the names of the directories which matched at least one of the
1001      * include patterns and at least one of the exclude patterns.
1002      *
1003      * @see #slowScan
1004      */
1005     public String[] getExcludedDirectories()
1006     {
1007         slowScan();
1008         String[] directories = new String[dirsExcluded.size()];
1009         dirsExcluded.copyInto( directories );
1010         return directories;
1011     }
1012 
1013     /**
1014      * <p>Returns the names of the directories which were selected out and
1015      * therefore not ultimately included.</p>
1016      *
1017      * <p>The names are relative to the base directory. This involves
1018      * performing a slow scan if one has not already been completed.</p>
1019      *
1020      * @return the names of the directories which were deselected.
1021      *
1022      * @see #slowScan
1023      */
1024     public String[] getDeselectedDirectories()
1025     {
1026         slowScan();
1027         String[] directories = new String[dirsDeselected.size()];
1028         dirsDeselected.copyInto( directories );
1029         return directories;
1030     }
1031 
1032     /**
1033      * Adds default exclusions to the current exclusions set.
1034      */
1035     public void addDefaultExcludes()
1036     {
1037         int excludesLength = excludes == null ? 0 : excludes.length;
1038         String[] newExcludes;
1039         newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length];
1040         if ( excludesLength > 0 )
1041         {
1042             System.arraycopy( excludes, 0, newExcludes, 0, excludesLength );
1043         }
1044         for ( int i = 0; i < DEFAULTEXCLUDES.length; i++ )
1045         {
1046             newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i].replace( '/',
1047                                                                           File.separatorChar ).replace( '\\', File.separatorChar );
1048         }
1049         excludes = newExcludes;
1050     }
1051 
1052     /**
1053      * Checks whether a given file is a symbolic link.
1054      *
1055      * <p>It doesn't really test for symbolic links but whether the
1056      * canonical and absolute paths of the file are identical - this
1057      * may lead to false positives on some platforms.</p>
1058      *
1059      * @param parent the parent directory of the file to test
1060      * @param name the name of the file to test.
1061      *
1062      * @since Ant 1.5
1063      */
1064     public boolean isSymbolicLink( File parent, String name )
1065         throws IOException
1066     {
1067         File resolvedParent = new File( parent.getCanonicalPath() );
1068         File toTest = new File( resolvedParent, name );
1069         return !toTest.getAbsolutePath().equals( toTest.getCanonicalPath() );
1070     }
1071 
1072 }