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