Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
DirectoryScanner |
|
| 3.9642857142857144;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 = { "**\\*.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 | 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 | } |