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