1 package org.codehaus.mojo.jaxb2.shared;
2
3 import org.apache.maven.plugin.MojoExecutionException;
4 import org.apache.maven.plugin.logging.Log;
5 import org.codehaus.mojo.jaxb2.AbstractJaxbMojo;
6 import org.codehaus.mojo.jaxb2.shared.filters.Filter;
7 import org.codehaus.mojo.jaxb2.shared.filters.Filters;
8 import org.codehaus.plexus.util.FileUtils;
9 import org.codehaus.plexus.util.Os;
10 import org.codehaus.plexus.util.StringUtils;
11
12 import java.io.File;
13 import java.io.FileFilter;
14 import java.io.IOException;
15 import java.io.UnsupportedEncodingException;
16 import java.net.MalformedURLException;
17 import java.net.URL;
18 import java.net.URLDecoder;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.SortedMap;
24 import java.util.TreeMap;
25
26 /**
27 * The Jaxb2 Maven Plugin needs to fiddle with the filesystem a great deal, to create and optionally prune
28 * directories or detect/create various files. This utility class contains all such algorithms, and serves as
29 * an entry point to any Plexus Utils methods.
30 *
31 * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>
32 * @since 2.0
33 */
34 public final class FileSystemUtilities {
35
36 /*
37 * Hide the constructor for utility classes.
38 */
39 private FileSystemUtilities() {
40 // Do nothing
41 }
42
43 /**
44 * FileFilter which accepts Files that exist and for which {@code File.isFile() } is {@code true}.
45 */
46 public static final FileFilter EXISTING_FILE = new FileFilter() {
47 @Override
48 public boolean accept(final File candidate) {
49 return candidate != null && candidate.exists() && candidate.isFile();
50 }
51 };
52
53 /**
54 * FileFilter which accepts Files that exist and for which {@code File.isDirectory() } is {@code true}.
55 */
56 public static final FileFilter EXISTING_DIRECTORY = new FileFilter() {
57 @Override
58 public boolean accept(final File candidate) {
59 return candidate != null && candidate.exists() && candidate.isDirectory();
60 }
61 };
62
63 /**
64 * Acquires the canonical path for the supplied file.
65 *
66 * @param file A non-null File for which the canonical path should be retrieved.
67 * @return The canonical path of the supplied file.
68 */
69 public static String getCanonicalPath(final File file) {
70 return getCanonicalFile(file).getPath();
71 }
72
73 /**
74 * Non-valid Characters for naming files, folders under Windows: <code>":", "*", "?", "\"", "<", ">", "|"</code>
75 *
76 * @see <a href="http://support.microsoft.com/?scid=kb%3Ben-us%3B177506&x=12&y=13">
77 * http://support.microsoft.com/?scid=kb%3Ben-us%3B177506&x=12&y=13</a>
78 * @see {@code org.codehaus.plexus.util.FileUtils}
79 */
80 private static final String[] INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME = {":", "*", "?", "\"", "<", ">", "|"};
81
82 /**
83 * Acquires the canonical File for the supplied file.
84 *
85 * @param file A non-null File for which the canonical File should be retrieved.
86 * @return The canonical File of the supplied file.
87 */
88 public static File getCanonicalFile(final File file) {
89
90 // Check sanity
91 Validate.notNull(file, "file");
92
93 // All done
94 try {
95 return file.getCanonicalFile();
96 } catch (IOException e) {
97 throw new IllegalArgumentException("Could not acquire the canonical file for ["
98 + file.getAbsolutePath() + "]", e);
99 }
100 }
101
102 /**
103 * <p>Retrieves the canonical File matching the supplied path in the following order or priority:</p>
104 * <ol>
105 * <li><strong>Absolute path:</strong> The path is used by itself (i.e. {@code new File(path);}). If an
106 * existing file or directory matches the provided path argument, its canonical path will be returned.</li>
107 * <li><strong>Relative path:</strong> The path is appended to the baseDir (i.e.
108 * {@code new File(baseDir, path);}). If an existing file or directory matches the provided path argument,
109 * its canonical path will be returned. Only in this case will be baseDir argument be considered.</li>
110 * </ol>
111 * <p>If no file or directory could be derived from the supplied path and baseDir, {@code null} is returned.</p>
112 *
113 * @param path A non-null path which will be used to find an existing file or directory.
114 * @param baseDir A directory to which the path will be appended to search for the existing file or directory in
115 * case the file was nonexistent when interpreted as an absolute path.
116 * @return either a canonical File for the path, or {@code null} if no file or directory matched
117 * the supplied path and baseDir.
118 */
119 public static File getExistingFile(final String path, final File baseDir) {
120
121 // Check sanity
122 Validate.notEmpty(path, "path");
123 final File theFile = new File(path);
124 File toReturn = null;
125
126 // Is 'path' absolute?
127 if (theFile.isAbsolute() && (EXISTING_FILE.accept(theFile) || EXISTING_DIRECTORY.accept(theFile))) {
128 toReturn = getCanonicalFile(theFile);
129 }
130
131 // Is 'path' relative?
132 if (!theFile.isAbsolute()) {
133
134 // In this case, baseDir cannot be null.
135 Validate.notNull(baseDir, "baseDir");
136 final File relativeFile = new File(baseDir, path);
137
138 if (EXISTING_FILE.accept(relativeFile) || EXISTING_DIRECTORY.accept(relativeFile)) {
139 toReturn = getCanonicalFile(relativeFile);
140 }
141 }
142
143 // The path provided did not point to an existing File or Directory.
144 return toReturn;
145 }
146
147 /**
148 * Retrieves the URL for the supplied File. Convenience method which hides exception handling
149 * for the operation in question.
150 *
151 * @param aFile A File for which the URL should be retrieved.
152 * @return The URL for the supplied aFile.
153 * @throws java.lang.IllegalArgumentException if getting the URL yielded a MalformedURLException.
154 */
155 public static URL getUrlFor(final File aFile) throws IllegalArgumentException {
156
157 // Check sanity
158 Validate.notNull(aFile, "aFile");
159
160 try {
161 return aFile.toURI().normalize().toURL();
162 } catch (MalformedURLException e) {
163 throw new IllegalArgumentException("Could not retrieve the URL from file ["
164 + getCanonicalPath(aFile) + "]", e);
165 }
166 }
167
168 /**
169 * Acquires the file for a supplied URL, provided that its protocol is is either a file or a jar.
170 *
171 * @param anURL a non-null URL.
172 * @param encoding The encoding to be used by the URLDecoder to decode the path found.
173 * @return The File pointing to the supplied URL, for file or jar protocol URLs and null otherwise.
174 */
175 public static File getFileFor(final URL anURL, final String encoding) {
176
177 // Check sanity
178 Validate.notNull(anURL, "anURL");
179 Validate.notNull(encoding, "encoding");
180
181 final String protocol = anURL.getProtocol();
182 File toReturn = null;
183 if ("file".equalsIgnoreCase(protocol)) {
184 try {
185 final String decodedPath = URLDecoder.decode(anURL.getPath(), encoding);
186 toReturn = new File(decodedPath);
187 } catch (Exception e) {
188 throw new IllegalArgumentException("Could not get the File for [" + anURL + "]", e);
189 }
190 } else if ("jar".equalsIgnoreCase(protocol)) {
191
192 try {
193
194 // Decode the JAR
195 final String tmp = URLDecoder.decode(anURL.getFile(), encoding);
196
197 // JAR URLs generally contain layered protocols, such as:
198 // jar:file:/some/path/to/nazgul-tools-validation-aspect-4.0.2.jar!/the/package/ValidationAspect.class
199 final URL innerURL = new URL(tmp);
200
201 // We can handle File protocol URLs here.
202 if ("file".equalsIgnoreCase(innerURL.getProtocol())) {
203
204 // Peel off the inner protocol
205 final String innerUrlPath = innerURL.getPath();
206 final String filePath = innerUrlPath.contains("!")
207 ? innerUrlPath.substring(0, innerUrlPath.indexOf("!"))
208 : innerUrlPath;
209 toReturn = new File(URLDecoder.decode(filePath, encoding));
210 }
211 } catch (Exception e) {
212 throw new IllegalArgumentException("Could not get the File for [" + anURL + "]", e);
213 }
214 }
215
216 // All done.
217 return toReturn;
218 }
219
220
221 /**
222 * Filters files found either in the sources paths (or in the standardDirectory if no explicit sources are given),
223 * and retrieves a List holding those files that do not match any of the supplied Java Regular Expression
224 * excludePatterns.
225 *
226 * @param baseDir The non-null basedir Directory.
227 * @param sources The sources which should be either absolute or relative (to the given baseDir)
228 * paths to files or to directories that should be searched recursively for files.
229 * @param standardDirectories If no sources are given, revert to searching all files under these standard
230 * directories. Each path is {@code relativize()}-d to the supplied baseDir to
231 * reach a directory path.
232 * @param log A non-null Maven Log for logging any operations performed.
233 * @param fileTypeDescription A human-readable short description of what kind of files are searched for, such as
234 * "xsdSources" or "xjbSources".
235 * @param excludePatterns An optional List of patterns used to construct an ExclusionRegExpFileFilter used to
236 * identify files which should be excluded from the result.
237 * @return URLs to all Files under the supplied sources (or standardDirectories, if no explicit sources
238 * are given) which do not match the supplied Java Regular excludePatterns.
239 */
240 public static List<URL> filterFiles(final File baseDir,
241 final List<String> sources,
242 final List<String> standardDirectories,
243 final Log log,
244 final String fileTypeDescription,
245 final List<Filter<File>> excludePatterns) {
246
247 final SortedMap<String, File> pathToResolvedSourceMap = new TreeMap<String, File>();
248
249 for (String current : standardDirectories) {
250 for (File currentResolvedSource : FileSystemUtilities.filterFiles(
251 baseDir,
252 sources,
253 FileSystemUtilities.relativize(current, baseDir),
254 log,
255 fileTypeDescription,
256 excludePatterns)) {
257
258 // Add the source
259 pathToResolvedSourceMap.put(
260 FileSystemUtilities.getCanonicalPath(currentResolvedSource),
261 currentResolvedSource);
262 }
263 }
264
265 final List<URL> toReturn = new ArrayList<URL>();
266
267 // Extract the URLs for all resolved Java sources.
268 for (Map.Entry<String, File> current : pathToResolvedSourceMap.entrySet()) {
269 toReturn.add(FileSystemUtilities.getUrlFor(current.getValue()));
270 }
271
272 if (log.isDebugEnabled()) {
273
274 final StringBuilder builder = new StringBuilder();
275 builder.append("\n+=================== [Filtered " + fileTypeDescription + "]\n");
276
277 builder.append("|\n");
278 builder.append("| " + excludePatterns.size() + " Exclude patterns:\n");
279 for (int i = 0; i < excludePatterns.size(); i++) {
280 builder.append("| [" + (i + 1) + "/" + excludePatterns.size() + "]: " + excludePatterns.get(i) + "\n");
281 }
282
283 builder.append("|\n");
284 builder.append("| " + standardDirectories.size() + " Standard Directories:\n");
285 for (int i = 0; i < standardDirectories.size(); i++) {
286 builder.append("| [" + (i + 1) + "/" + standardDirectories.size() + "]: "
287 + standardDirectories.get(i) + "\n");
288 }
289
290 builder.append("|\n");
291 builder.append("| " + toReturn.size() + " Results:\n");
292 for (int i = 0; i < toReturn.size(); i++) {
293 builder.append("| [" + (i + 1) + "/" + toReturn.size() + "]: " + toReturn.get(i) + "\n");
294 }
295 builder.append("|\n");
296 builder.append("+=================== [End Filtered " + fileTypeDescription + "]\n\n");
297
298 // Log all.
299 log.debug(builder.toString().replace("\n", AbstractJaxbMojo.NEWLINE));
300 }
301
302 // All done.
303 return toReturn;
304 }
305
306 /**
307 * Filters files found either in the sources paths (or in the standardDirectory if no explicit sources are given),
308 * and retrieves a List holding those files that do not match any of the supplied Java Regular Expression
309 * excludePatterns.
310 *
311 * @param baseDir The non-null basedir Directory.
312 * @param sources The sources which should be either absolute or relative (to the given baseDir)
313 * paths to files or to directories that should be searched recursively for files.
314 * @param standardDirectory If no sources are given, revert to searching all files under this standard directory.
315 * This is the path appended to the baseDir to reach a directory.
316 * @param log A non-null Maven Log for logging any operations performed.
317 * @param fileTypeDescription A human-readable short description of what kind of files are searched for, such as
318 * "xsdSources" or "xjbSources".
319 * @param excludeFilters An optional List of Filters used to identify files which should be excluded from
320 * the result.
321 * @return All files under the supplied sources (or standardDirectory, if no explicit sources are given) which
322 * do not match the supplied Java Regular excludePatterns.
323 */
324 @SuppressWarnings("CheckStyle")
325 public static List<File> filterFiles(final File baseDir,
326 final List<String> sources,
327 final String standardDirectory,
328 final Log log,
329 final String fileTypeDescription,
330 final List<Filter<File>> excludeFilters) {
331
332 // Check sanity
333 Validate.notNull(baseDir, "baseDir");
334 Validate.notNull(log, "log");
335 Validate.notEmpty(standardDirectory, "standardDirectory");
336 Validate.notEmpty(fileTypeDescription, "fileTypeDescription");
337
338 // No sources provided? Fallback to the standard (which should be a relative path).
339 List<String> effectiveSources = sources;
340 if (sources == null || sources.isEmpty()) {
341 effectiveSources = new ArrayList<String>();
342
343 final File tmp = new File(standardDirectory);
344 final File rootDirectory = tmp.isAbsolute() ? tmp : new File(baseDir, standardDirectory);
345 effectiveSources.add(FileSystemUtilities.getCanonicalPath(rootDirectory));
346 }
347
348 // First, remove the non-existent sources.
349 List<File> existingSources = new ArrayList<File>();
350 for (String current : effectiveSources) {
351
352 final File existingFile = FileSystemUtilities.getExistingFile(current, baseDir);
353 if (existingFile != null) {
354 existingSources.add(existingFile);
355
356 if (log.isDebugEnabled()) {
357 log.debug("Accepted configured " + fileTypeDescription + " ["
358 + FileSystemUtilities.getCanonicalFile(existingFile) + "]");
359 }
360 } else {
361 if (log.isInfoEnabled()) {
362 log.info("Ignored given or default " + fileTypeDescription + " [" + current
363 + "], since it is not an existent file or directory.");
364 }
365 }
366 }
367
368 if (log.isDebugEnabled() && existingSources.size() > 0) {
369
370 final int size = existingSources.size();
371
372 log.debug(" [" + size + " existing " + fileTypeDescription + "] ...");
373 for (int i = 0; i < size; i++) {
374 log.debug(" " + (i + 1) + "/" + size + ": " + existingSources.get(i));
375 }
376 log.debug(" ... End [" + size + " existing " + fileTypeDescription + "]");
377 }
378
379 // All Done.
380 return FileSystemUtilities.resolveRecursively(existingSources, excludeFilters, log);
381 }
382
383 /**
384 * Filters all supplied files using the
385 *
386 * @param files The list of files to resolve, filter and return. If the {@code files} List
387 * contains directories, they are searched for Files recursively. Any found Files in such
388 * a search are included in the resulting File List if they match the acceptFilter supplied.
389 * @param acceptFilter A filter matched to all files in the given List. If the acceptFilter matches a file, it is
390 * included in the result.
391 * @param log The active Maven Log.
392 * @return All files in (or files in subdirectories of directories provided in) the files List, provided that each
393 * file is accepted by an ExclusionRegExpFileFilter.
394 */
395 public static List<File> filterFiles(final List<File> files, final Filter<File> acceptFilter, final Log log) {
396
397 // Check sanity
398 Validate.notNull(files, "files");
399
400 final List<File> toReturn = new ArrayList<File>();
401
402 if (files.size() > 0) {
403 for (File current : files) {
404
405 final boolean isAcceptedFile = EXISTING_FILE.accept(current) && acceptFilter.accept(current);
406 final boolean isAcceptedDirectory = EXISTING_DIRECTORY.accept(current) && acceptFilter.accept(current);
407
408 if (isAcceptedFile) {
409 toReturn.add(current);
410 } else if (isAcceptedDirectory) {
411 recurseAndPopulate(toReturn, Collections.singletonList(acceptFilter), current, false, log);
412 }
413 }
414 }
415
416 // All done
417 return toReturn;
418 }
419
420 /**
421 * Retrieves a List of Files containing all the existing files within the supplied files List, including all
422 * files found in directories recursive to any directories provided in the files list. Each file included in the
423 * result must pass an ExclusionRegExpFileFilter synthesized from the supplied exclusions pattern(s).
424 *
425 * @param files The list of files to resolve, filter and return. If the {@code files} List
426 * contains directories, they are searched for Files recursively. Any found Files in such
427 * a search are included in the resulting File List if they do not match any of the
428 * exclusionFilters supplied.
429 * @param exclusionFilters A List of Filters which identify files to remove from the result - implying that any
430 * File matched by any of these exclusionFilters will not be included in the result.
431 * @param log The active Maven Log.
432 * @return All files in (or files in subdirectories of directories provided in) the files List, provided that each
433 * file is accepted by an ExclusionRegExpFileFilter.
434 */
435 public static List<File> resolveRecursively(final List<File> files,
436 final List<Filter<File>> exclusionFilters,
437 final Log log) {
438
439 // Check sanity
440 Validate.notNull(files, "files");
441
442 final List<Filter<File>> effectiveExclusions = exclusionFilters == null
443 ? new ArrayList<Filter<File>>()
444 : exclusionFilters;
445
446 final List<File> toReturn = new ArrayList<File>();
447
448 if (files.size() > 0) {
449 for (File current : files) {
450
451 final boolean isAcceptedFile = EXISTING_FILE.accept(current)
452 && Filters.noFilterMatches(current, effectiveExclusions);
453 final boolean isAcceptedDirectory = EXISTING_DIRECTORY.accept(current)
454 && Filters.noFilterMatches(current, effectiveExclusions);
455
456 if (isAcceptedFile) {
457 toReturn.add(current);
458 } else if (isAcceptedDirectory) {
459 recurseAndPopulate(toReturn, effectiveExclusions, current, true, log);
460 }
461 }
462 }
463
464 // All done
465 return toReturn;
466 }
467
468 /**
469 * Convenience method to successfully create a directory - or throw an exception if failing to create it.
470 *
471 * @param aDirectory The directory to create.
472 * @param cleanBeforeCreate if {@code true}, the directory and all its content will be deleted before being
473 * re-created. This will ensure that the created directory is really clean.
474 * @throws MojoExecutionException if the aDirectory could not be created (and/or cleaned).
475 */
476 public static void createDirectory(final File aDirectory, final boolean cleanBeforeCreate)
477 throws MojoExecutionException {
478
479 // Check sanity
480 Validate.notNull(aDirectory, "aDirectory");
481 validateFileOrDirectoryName(aDirectory);
482
483 // Clean an existing directory?
484 if (cleanBeforeCreate) {
485 try {
486 FileUtils.deleteDirectory(aDirectory);
487 } catch (IOException e) {
488 throw new MojoExecutionException("Could not clean directory [" + getCanonicalPath(aDirectory) + "]", e);
489 }
490 }
491
492 // Now, make the required directory, if it does not already exist as a directory.
493 final boolean existsAsFile = aDirectory.exists() && aDirectory.isFile();
494 if (existsAsFile) {
495 throw new MojoExecutionException("[" + getCanonicalPath(aDirectory) + "] exists and is a file. "
496 + "Cannot make directory");
497 } else if (!aDirectory.exists() && !aDirectory.mkdirs()) {
498 throw new MojoExecutionException("Could not create directory [" + getCanonicalPath(aDirectory) + "]");
499 }
500 }
501
502 /**
503 * If the supplied path refers to a file or directory below the supplied basedir, the returned
504 * path is identical to the part below the basedir.
505 *
506 * @param path The path to strip off basedir path from, and return.
507 * @param parentDir The maven project basedir.
508 * @return The path relative to basedir, if it is situated below the basedir. Otherwise the supplied path.
509 */
510 public static String relativize(final String path, final File parentDir) {
511
512 // Check sanity
513 Validate.notNull(path, "path");
514 Validate.notNull(parentDir, "parentDir");
515
516 final String basedirPath = FileSystemUtilities.getCanonicalPath(parentDir);
517 String toReturn = path;
518
519 // Compare case insensitive
520 if (path.toLowerCase().startsWith(basedirPath.toLowerCase())) {
521 toReturn = path.substring(basedirPath.length() + 1);
522 }
523
524 // Handle whitespace in the argument.
525 return toReturn;
526 }
527
528 /**
529 * If the supplied fileOrDir is a File, it is added to the returned List if any of the filters Match.
530 * If the supplied fileOrDir is a Directory, it is listed and any of the files immediately within the fileOrDir
531 * directory are returned within the resulting List provided that they match any of the supplied filters.
532 *
533 * @param fileOrDir A File or Directory.
534 * @param fileFilters A List of filter of which at least one must match to add the File
535 * (or child Files, in case of a directory) to the resulting List.
536 * @param log The active Maven Log
537 * @return A List holding the supplied File (or child Files, in case fileOrDir is a Directory) given that at
538 * least one Filter accepts them.
539 */
540 public static List<File> listFiles(final File fileOrDir,
541 final List<Filter<File>> fileFilters,
542 final Log log) {
543 return listFiles(fileOrDir, fileFilters, false, log);
544 }
545
546 /**
547 * If the supplied fileOrDir is a File, it is added to the returned List if any of the filters Match.
548 * If the supplied fileOrDir is a Directory, it is listed and any of the files immediately within the fileOrDir
549 * directory are returned within the resulting List provided that they match any of the supplied filters.
550 *
551 * @param fileOrDir A File or Directory.
552 * @param fileFilters A List of filter of which at least one must match to add the File (or child Files, in case
553 * of a directory) to the resulting List.
554 * @param excludeFilterOperation if {@code true}, all fileFilters are considered exclude filter, i.e. if
555 * any of the Filters match the fileOrDir, that fileOrDir will be excluded from the
556 * result.
557 * @param log The active Maven Log
558 * @return A List holding the supplied File (or child Files, in case fileOrDir is a Directory) given that at
559 * least one Filter accepts them.
560 */
561 public static List<File> listFiles(final File fileOrDir,
562 final List<Filter<File>> fileFilters,
563 final boolean excludeFilterOperation,
564 final Log log) {
565
566 // Check sanity
567 Validate.notNull(log, "log");
568 Validate.notNull(fileFilters, "fileFilters");
569 final List<File> toReturn = new ArrayList<File>();
570
571 if (EXISTING_FILE.accept(fileOrDir)) {
572 checkAndAdd(toReturn, fileOrDir, fileFilters, excludeFilterOperation, log);
573 } else if (EXISTING_DIRECTORY.accept(fileOrDir)) {
574
575 final File[] listedFiles = fileOrDir.listFiles();
576 if (listedFiles != null) {
577 for (File current : listedFiles) {
578 checkAndAdd(toReturn, current, fileFilters, excludeFilterOperation, log);
579 }
580 }
581 }
582
583 // All done.
584 return toReturn;
585 }
586
587 //
588 // Private helpers
589 //
590
591 private static void checkAndAdd(final List<File> toPopulate,
592 final File current,
593 final List<Filter<File>> fileFilters,
594 final boolean excludeFilterOperation,
595 final Log log) {
596
597 //
598 // When no filters are supplied...
599 // [Include Operation]: all files will be rejected
600 // [Exclude Operation]: all files will be included
601 //
602 final boolean noFilters = fileFilters == null || fileFilters.isEmpty();
603 final boolean addFile = excludeFilterOperation
604 ? noFilters || Filters.rejectAtLeastOnce(current, fileFilters)
605 : noFilters || Filters.matchAtLeastOnce(current, fileFilters);
606 final String logPrefix = (addFile ? "Accepted " : "Rejected ")
607 + (current.isDirectory() ? "directory" : "file") + " [";
608
609 if (addFile) {
610 toPopulate.add(current);
611 }
612
613 if (log.isDebugEnabled()) {
614 log.debug(logPrefix + getCanonicalPath(current) + "]");
615 }
616 }
617
618 private static void validateFileOrDirectoryName(final File fileOrDir) {
619
620 if (Os.isFamily(Os.FAMILY_WINDOWS) && !FileUtils.isValidWindowsFileName(fileOrDir)) {
621 throw new IllegalArgumentException(
622 "The file (" + fileOrDir + ") cannot contain any of the following characters: \n"
623 + StringUtils.join(INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME, " "));
624 }
625 }
626
627 private static void recurseAndPopulate(final List<File> toPopulate,
628 final List<Filter<File>> fileFilters,
629 final File aDirectory,
630 final boolean excludeOperation,
631 final Log log) {
632
633 final List<File> files = listFiles(aDirectory, fileFilters, excludeOperation, log);
634 for (File current : files) {
635 if (EXISTING_FILE.accept(current)) {
636 toPopulate.add(current);
637 }
638
639 if (EXISTING_DIRECTORY.accept(current)) {
640 recurseAndPopulate(toPopulate, fileFilters, current, excludeOperation, log);
641 }
642 }
643 }
644 }