View Javadoc
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&ouml;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 }