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