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 // 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 private boolean haveSlowResults = false;
218
219 /**
220 * Whether or not the file system should be treated as a case sensitive one.
221 */
222 private boolean isCaseSensitive = true;
223
224 /**
225 * Whether or not symbolic links should be followed.
226 *
227 *
228 */
229 private boolean followSymlinks = true;
230
231
232 /**
233 * A {@link ScanConductor} an control the scanning process.
234 */
235 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 private ScanConductor.ScanAction scanAction = null;
241
242 /**
243 * Sole constructor.
244 */
245 public DirectoryScanner()
246 {
247 }
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 setBasedir( new File( basedir.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ) ) );
259 }
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 this.basedir = basedir;
269 }
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 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 this.isCaseSensitive = isCaseSensitive;
289 }
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 this.followSymlinks = followSymlinks;
299 }
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 if ( includes == null )
313 {
314 this.includes = null;
315 }
316 else
317 {
318 this.includes = new String[includes.length];
319 for ( int i = 0; i < includes.length; i++ )
320 {
321 String pattern;
322 pattern = includes[i].trim().replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
323 if ( pattern.endsWith( File.separator ) )
324 {
325 pattern += "**";
326 }
327 this.includes[i] = pattern;
328 }
329 }
330 }
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 if ( excludes == null )
344 {
345 this.excludes = null;
346 }
347 else
348 {
349 this.excludes = new String[excludes.length];
350 for ( int i = 0; i < excludes.length; i++ )
351 {
352 String pattern;
353 pattern = excludes[i].trim().replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
354 if ( pattern.endsWith( File.separator ) )
355 {
356 pattern += "**";
357 }
358 this.excludes[i] = pattern;
359 }
360 }
361 }
362
363 public void setScanConductor( final ScanConductor scanConductor )
364 {
365 this.scanConductor = scanConductor;
366 }
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 if ( basedir == null )
379 {
380 throw new IllegalStateException( "No basedir set" );
381 }
382 if ( !basedir.exists() )
383 {
384 throw new IllegalStateException( "basedir " + basedir + " does not exist" );
385 }
386 if ( !basedir.isDirectory() )
387 {
388 throw new IllegalStateException( "basedir " + basedir + " is not a directory" );
389 }
390
391 setupDefaultFilters();
392 setupMatchPatterns();
393
394 filesIncluded = new ArrayList<String>();
395 filesNotIncluded = new ArrayList<String>();
396 filesExcluded = new ArrayList<String>();
397 dirsIncluded = new ArrayList<String>();
398 dirsNotIncluded = new ArrayList<String>();
399 dirsExcluded = new ArrayList<String>();
400 scanAction = ScanConductor.ScanAction.CONTINUE;
401
402 if ( isIncluded( "" ) )
403 {
404 if ( !isExcluded( "" ) )
405 {
406 if ( scanConductor != null )
407 {
408 scanAction = scanConductor.visitDirectory( "", basedir );
409
410 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction )
411 || ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
412 {
413 return;
414 }
415 }
416
417 dirsIncluded.add( "" );
418 }
419 else
420 {
421 dirsExcluded.add( "" );
422 }
423 }
424 else
425 {
426 dirsNotIncluded.add( "" );
427 }
428 scandir( basedir, "", true );
429 }
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 if ( filesIncluded == null )
449 {
450 // perform a scan if the directory didn't got scanned yet
451 scan();
452 }
453
454 return diffFiles( oldFiles, filesIncluded.toArray( new String[filesIncluded.size()] ) );
455 }
456
457 public static DirectoryScanResult diffFiles( @Nullable String[] oldFiles, @Nullable String[] newFiles )
458 {
459 Set<String> oldFileSet = arrayAsHashSet( oldFiles );
460 Set<String> newFileSet = arrayAsHashSet( newFiles );
461
462 List<String> added = new ArrayList<String>();
463 List<String> removed = new ArrayList<String>();
464
465 for ( String oldFile : oldFileSet )
466 {
467 if ( !newFileSet.contains( oldFile ) )
468 {
469 removed.add( oldFile );
470 }
471 }
472
473 for ( String newFile : newFileSet )
474 {
475 if ( !oldFileSet.contains( newFile ) )
476 {
477 added.add( newFile );
478 }
479 }
480
481 String[] filesAdded = added.toArray( new String[added.size()] );
482 String[] filesRemoved = removed.toArray( new String[removed.size()] );
483
484 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 if ( array == null || array.length == 0 )
498 {
499 return Collections.emptySet();
500 }
501
502 Set<T> set = new HashSet<T>( array.length );
503 Collections.addAll( set, array );
504
505 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 if ( haveSlowResults )
518 {
519 return;
520 }
521
522 final String[] excl = dirsExcluded.toArray( new String[dirsExcluded.size()] );
523
524 final String[] notIncl = dirsNotIncluded.toArray( new String[dirsNotIncluded.size()] );
525
526 for ( String anExcl : excl )
527 {
528 if ( !couldHoldIncluded( anExcl ) )
529 {
530 scandir( new File( basedir, anExcl ), anExcl + File.separator, false );
531 }
532 }
533
534 for ( String aNotIncl : notIncl )
535 {
536 if ( !couldHoldIncluded( aNotIncl ) )
537 {
538 scandir( new File( basedir, aNotIncl ), aNotIncl + File.separator, false );
539 }
540 }
541
542 haveSlowResults = true;
543 }
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 String[] newfiles = dir.list();
565
566 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 newfiles = new String[0];
579
580 // throw new IOException( "IO error scanning directory " + dir.getAbsolutePath() );
581 }
582
583 if ( !followSymlinks )
584 {
585 final List<String> noLinks = new ArrayList<String>();
586 for ( final String newfile : newfiles )
587 {
588 try
589 {
590 if ( isSymbolicLink( dir, newfile ) )
591 {
592 final String name = vpath + newfile;
593 final File file = new File( dir, newfile );
594 if ( file.isDirectory() )
595 {
596 dirsExcluded.add( name );
597 }
598 else
599 {
600 filesExcluded.add( name );
601 }
602 }
603 else
604 {
605 noLinks.add( newfile );
606 }
607 }
608 catch ( final IOException ioe )
609 {
610 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 System.err.println( msg );
614 noLinks.add( newfile );
615 }
616 }
617 newfiles = noLinks.toArray( new String[noLinks.size()] );
618 }
619
620 for ( final String newfile : newfiles )
621 {
622 final String name = vpath + newfile;
623 final File file = new File( dir, newfile );
624 if ( file.isDirectory() )
625 {
626 if ( isIncluded( name ) )
627 {
628 if ( !isExcluded( name ) )
629 {
630 if ( scanConductor != null )
631 {
632 scanAction = scanConductor.visitDirectory( name, file );
633
634 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
635 {
636 return;
637 }
638 }
639
640 if ( !ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
641 {
642 dirsIncluded.add( name );
643 if ( fast )
644 {
645 scandir( file, name + File.separator, fast );
646
647 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
648 {
649 return;
650 }
651 }
652 }
653 scanAction = null;
654
655 }
656 else
657 {
658 dirsExcluded.add( name );
659 if ( fast && couldHoldIncluded( name ) )
660 {
661 scandir( file, name + File.separator, fast );
662 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
663 {
664 return;
665 }
666 scanAction = null;
667 }
668 }
669 }
670 else
671 {
672 if ( fast && couldHoldIncluded( name ) )
673 {
674 if ( scanConductor != null )
675 {
676 scanAction = scanConductor.visitDirectory( name, file );
677
678 if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
679 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
680 {
681 return;
682 }
683 }
684 if ( !ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
685 {
686 dirsNotIncluded.add( name );
687
688 scandir( file, name + File.separator, fast );
689 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
690 {
691 return;
692 }
693 }
694 scanAction = null;
695 }
696 }
697 if ( !fast )
698 {
699 scandir( file, name + File.separator, fast );
700 if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
701 {
702 return;
703 }
704 scanAction = null;
705 }
706 }
707 else if ( file.isFile() )
708 {
709 if ( isIncluded( name ) )
710 {
711 if ( !isExcluded( name ) )
712 {
713 if ( scanConductor != null )
714 {
715 scanAction = scanConductor.visitFile( name, file );
716 }
717
718 if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
719 || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
720 {
721 return;
722 }
723
724 filesIncluded.add( name );
725 }
726 else
727 {
728 filesExcluded.add( name );
729 }
730 }
731 else
732 {
733 filesNotIncluded.add( name );
734 }
735 }
736 }
737 }
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 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 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 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 if ( filesIncluded == null )
785 {
786 return new String[0];
787 }
788 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 slowScan();
801 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 slowScan();
816 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 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 slowScan();
841 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 slowScan();
856 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 final int excludesLength = excludes == null ? 0 : excludes.length;
865 String[] newExcludes;
866 newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length];
867 if ( excludesLength > 0 )
868 {
869 System.arraycopy( excludes, 0, newExcludes, 0, excludesLength );
870 }
871 for ( int i = 0; i < DEFAULTEXCLUDES.length; i++ )
872 {
873 newExcludes[i + excludesLength] =
874 DEFAULTEXCLUDES[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
875 }
876 excludes = newExcludes;
877 }
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 if ( Java7Support.isJava7())
894 {
895 return Java7Support.isSymLink( parent );
896 }
897 final File resolvedParent = new File( parent.getCanonicalPath() );
898 final File toTest = new File( resolvedParent, name );
899 return !toTest.getAbsolutePath().equals( toTest.getCanonicalPath() );
900 }
901
902 private void setupDefaultFilters()
903 {
904 if ( includes == null )
905 {
906 // No includes supplied, so set it to 'matches all'
907 includes = new String[1];
908 includes[0] = "**";
909 }
910 if ( excludes == null )
911 {
912 excludes = new String[0];
913 }
914 }
915
916
917 private void setupMatchPatterns()
918 {
919 includesPatterns = MatchPatterns.from( includes );
920 excludesPatterns = MatchPatterns.from( excludes );
921 }
922
923 }