View Javadoc
1   package org.codehaus.plexus.util;
2   
3   /* ====================================================================
4    * The Apache Software License, Version 1.1
5    *
6    * Copyright (c) 2001 The Apache Software Foundation.  All rights
7    * reserved.
8    *
9    * Redistribution and use in source and binary forms, with or without
10   * modification, are permitted provided that the following conditions
11   * are met:
12   *
13   * 1. Redistributions of source code must retain the above copyright
14   *    notice, this list of conditions and the following disclaimer.
15   *
16   * 2. Redistributions in binary form must reproduce the above copyright
17   *    notice, this list of conditions and the following disclaimer in
18   *    the documentation and/or other materials provided with the
19   *    distribution.
20   *
21   * 3. The end-user documentation included with the redistribution,
22   *    if any, must include the following acknowledgment:
23   *       "This product includes software developed by the
24   *        Apache Software Foundation (http://www.codehaus.org/)."
25   *    Alternately, this acknowledgment may appear in the software itself,
26   *    if and wherever such third-party acknowledgments normally appear.
27   *
28   * 4. The names "Apache" and "Apache Software Foundation" and
29   *    "Apache Turbine" must not be used to endorse or promote products
30   *    derived from this software without prior written permission. For
31   *    written permission, please contact codehaus@codehaus.org.
32   *
33   * 5. Products derived from this software may not be called "Apache",
34   *    "Apache Turbine", nor may "Apache" appear in their name, without
35   *    prior written permission of the Apache Software Foundation.
36   *
37   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48   * SUCH DAMAGE.
49   * ====================================================================
50   *
51   * This software consists of voluntary contributions made by many
52   * individuals on behalf of the Apache Software Foundation.  For more
53   * information on the Apache Software Foundation, please see
54   * <http://www.codehaus.org/>.
55   *
56   */
57  
58  import java.io.BufferedReader;
59  import java.io.File;
60  import java.io.IOException;
61  import java.io.InputStream;
62  import java.io.OutputStream;
63  import java.io.OutputStreamWriter;
64  import java.io.Reader;
65  import java.io.Writer;
66  import java.net.URL;
67  import java.nio.charset.Charset;
68  import java.nio.file.Files;
69  import java.nio.file.Path;
70  import java.nio.file.Paths;
71  import java.nio.file.StandardOpenOption;
72  import java.security.SecureRandom;
73  import java.text.DecimalFormat;
74  import java.util.ArrayList;
75  import java.util.Arrays;
76  import java.util.List;
77  import java.util.Random;
78  
79  import org.codehaus.plexus.util.io.InputStreamFacade;
80  import org.codehaus.plexus.util.io.URLInputStreamFacade;
81  
82  /**
83   * <p>This class provides basic facilities for manipulating files and file paths.</p>
84   *
85   * <b>Path-related methods</b>
86   *
87   * <p>Methods exist to retrieve the components of a typical file path. For example
88   * <code>/www/hosted/mysite/index.html</code>, can be broken into:
89   * <ul>
90   * <li><code>/www/hosted/mysite/</code> -- retrievable through {@link #getPath}</li>
91   * <li><code>index.html</code> -- retrievable through {@link #removePath}</li>
92   * <li><code>/www/hosted/mysite/index</code> -- retrievable through {@link #removeExtension}</li>
93   * <li><code>html</code> -- retrievable through {@link #getExtension}</li>
94   * </ul>
95   * <p>There are also methods to {@link #catPath concatenate two paths}, {@link #resolveFile resolve a path relative to a
96   * File} and {@link #normalize} a path.</p>
97   *
98   * <b>File-related methods</b>
99   *
100  * <p>There are methods to create a {@link #toFile File from a URL}, copy a {@link #copyFileToDirectory File to a
101  * directory}, copy a {@link #copyFile File to another File}, copy a {@link #copyURLToFile URL's contents to a File}, as
102  * well as methods to {@link #deleteDirectory(File) delete} and {@link #cleanDirectory(File) clean} a directory.</p>
103  *
104  * <p>Common {@link java.io.File} manipulation routines.</p>
105  *
106  * <p>Taken from the commons-utils repo. Also code from Alexandria's FileUtils. And from Avalon Excalibur's IO. And from
107  * Ant.</p>
108  *
109  * @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A>
110  * @author <a href="mailto:sanders@codehaus.org">Scott Sanders</a>
111  * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
112  * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph.Reck</a>
113  * @author <a href="mailto:peter@codehaus.org">Peter Donald</a>
114  * @author <a href="mailto:jefft@codehaus.org">Jeff Turner</a>
115  *
116  */
117 public class FileUtils extends BaseFileUtils {
118     /**
119      * The number of bytes in a kilobyte.
120      */
121     public static final int ONE_KB = 1024;
122 
123     /**
124      * The number of bytes in a megabyte.
125      */
126     public static final int ONE_MB = ONE_KB * ONE_KB;
127 
128     /**
129      * The number of bytes in a gigabyte.
130      */
131     public static final int ONE_GB = ONE_KB * ONE_MB;
132 
133     /**
134      * The vm file separator
135      */
136     public static String FS = File.separator;
137 
138     /**
139      * Non-valid Characters for naming files, folders under Windows: <code>":", "*", "?", "\"", "<", ">", "|"</code>
140      *
141      * @see <a href="http://support.microsoft.com/?scid=kb%3Ben-us%3B177506&x=12&y=13">
142      *      http://support.microsoft.com/?scid=kb%3Ben-us%3B177506&x=12&y=13</a>
143      */
144     private static final String[] INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME = {":", "*", "?", "\"", "<", ">", "|"};
145 
146     /**
147      * @return the default excludes pattern
148      * @see DirectoryScanner#DEFAULTEXCLUDES
149      */
150     public static String[] getDefaultExcludes() {
151         return DirectoryScanner.DEFAULTEXCLUDES;
152     }
153 
154     /**
155      * @return the default excludes pattern as list.
156      * @see #getDefaultExcludes()
157      */
158     public static List<String> getDefaultExcludesAsList() {
159         return Arrays.asList(getDefaultExcludes());
160     }
161 
162     /**
163      * @return the default excludes pattern as comma separated string.
164      * @see DirectoryScanner#DEFAULTEXCLUDES
165      * @see StringUtils#join(Object[], String)
166      */
167     public static String getDefaultExcludesAsString() {
168         return StringUtils.join(DirectoryScanner.DEFAULTEXCLUDES, ",");
169     }
170 
171     /**
172      * Returns a human-readable version of the file size (original is in bytes).
173      *
174      * @param size The number of bytes.
175      * @return A human-readable display value (includes units).
176      */
177     public static String byteCountToDisplaySize(int size) {
178         String displaySize;
179 
180         if (size / ONE_GB > 0) {
181             displaySize = String.valueOf(size / ONE_GB) + " GB";
182         } else if (size / ONE_MB > 0) {
183             displaySize = String.valueOf(size / ONE_MB) + " MB";
184         } else if (size / ONE_KB > 0) {
185             displaySize = String.valueOf(size / ONE_KB) + " KB";
186         } else {
187             displaySize = String.valueOf(size) + " bytes";
188         }
189 
190         return displaySize;
191     }
192 
193     /**
194      * Returns the directory path portion of a file specification string. Matches the equally named unix command.
195      *
196      * @param filename the file path
197      * @return The directory portion excluding the ending file separator.
198      */
199     public static String dirname(String filename) {
200         int i = filename.lastIndexOf(File.separator);
201         return (i >= 0 ? filename.substring(0, i) : "");
202     }
203 
204     /**
205      * Returns the filename portion of a file specification string.
206      *
207      * @param filename the file path
208      * @return The filename string with extension.
209      */
210     public static String filename(String filename) {
211         int i = filename.lastIndexOf(File.separator);
212         return (i >= 0 ? filename.substring(i + 1) : filename);
213     }
214 
215     /**
216      * Returns the filename portion of a file specification string. Matches the equally named unix command.
217      *
218      * @param filename the file path
219      * @return The filename string without extension.
220      */
221     public static String basename(String filename) {
222         return basename(filename, extension(filename));
223     }
224 
225     /**
226      * Returns the filename portion of a file specification string. Matches the equally named unix command.
227      *
228      * @param filename the file path
229      * @param suffix the file suffix
230      * @return the basename of the file
231      */
232     public static String basename(String filename, String suffix) {
233         int i = filename.lastIndexOf(File.separator) + 1;
234         int lastDot = ((suffix != null) && (suffix.length() > 0)) ? filename.lastIndexOf(suffix) : -1;
235 
236         if (lastDot >= 0) {
237             return filename.substring(i, lastDot);
238         } else if (i > 0) {
239             return filename.substring(i);
240         } else {
241             return filename; // else returns all (no path and no extension)
242         }
243     }
244 
245     /**
246      * Returns the extension portion of a file specification string. This everything after the last dot '.' in the
247      * filename (NOT including the dot).
248      *
249      * @param filename the file path
250      * @return the extension of the file
251      */
252     public static String extension(String filename) {
253         // Ensure the last dot is after the last file separator
254         int lastSep = filename.lastIndexOf(File.separatorChar);
255         int lastDot;
256         if (lastSep < 0) {
257             lastDot = filename.lastIndexOf('.');
258         } else {
259             lastDot = filename.substring(lastSep + 1).lastIndexOf('.');
260             if (lastDot >= 0) {
261                 lastDot += lastSep + 1;
262             }
263         }
264 
265         if (lastDot >= 0 && lastDot > lastSep) {
266             return filename.substring(lastDot + 1);
267         }
268 
269         return "";
270     }
271 
272     /**
273      * Check if a file exits.
274      *
275      * @param fileName the file path.
276      * @return true if file exists.
277      */
278     public static boolean fileExists(String fileName) {
279         File file = new File(fileName);
280         return file.exists();
281     }
282 
283     /**
284      * Note: the file content is read with platform encoding.
285      *
286      * @param file the file path
287      * @return the file content using the platform encoding.
288      * @throws IOException if any
289      */
290     public static String fileRead(String file) throws IOException {
291         return fileRead(file, null);
292     }
293 
294     /**
295      * @param file the file path
296      * @param encoding the wanted encoding
297      * @return the file content using the specified encoding.
298      * @throws IOException if any
299      */
300     public static String fileRead(String file, String encoding) throws IOException {
301         return fileRead(Paths.get(file), encoding);
302     }
303 
304     /**
305      * Note: the file content is read with platform encoding
306      *
307      * @param file the file path
308      * @return the file content using the platform encoding.
309      * @throws IOException if any
310      */
311     public static String fileRead(File file) throws IOException {
312         return fileRead(file, null);
313     }
314 
315     /**
316      * @param file the file path
317      * @param encoding the wanted encoding
318      * @return the file content using the specified encoding.
319      * @throws IOException if any
320      */
321     public static String fileRead(File file, String encoding) throws IOException {
322         return fileRead(file.toPath(), encoding);
323     }
324 
325     /**
326      * Appends data to a file. The file will be created if it does not exist. Note: the data is written with platform
327      * encoding
328      *
329      * @param fileName The path of the file to write.
330      * @param data The content to write to the file.
331      * @throws IOException if any
332      * @deprecated use {@code java.nio.files.Files.write(filename, data.getBytes(encoding),
333      *     StandardOpenOption.APPEND, StandardOpenOption.CREATE)}
334      */
335     public static void fileAppend(String fileName, String data) throws IOException {
336         fileAppend(fileName, null, data);
337     }
338 
339     /**
340      * Appends data to a file. The file will be created if it does not exist.
341      *
342      * @param fileName The path of the file to write.
343      * @param encoding The encoding of the file.
344      * @param data The content to write to the file.
345      * @throws IOException if any
346      * @deprecated use {@code java.nio.files.Files.write(filename, data.getBytes(encoding),
347      *     StandardOpenOption.APPEND, StandardOpenOption.CREATE)}
348      */
349     public static void fileAppend(String fileName, String encoding, String data) throws IOException {
350         fileAppend(Paths.get(fileName), encoding, data);
351     }
352 
353     private static void fileAppend(Path path, String encoding, String data) throws IOException {
354         fileWrite(path, encoding, data, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
355     }
356 
357     /**
358      * Writes data to a file. The file will be created if it does not exist. Note: the data is written with platform
359      * encoding
360      *
361      * @param fileName The path of the file to write.
362      * @param data The content to write to the file.
363      * @throws IOException if any
364      */
365     public static void fileWrite(String fileName, String data) throws IOException {
366         fileWrite(fileName, null, data);
367     }
368 
369     /**
370      * Writes data to a file. The file will be created if it does not exist.
371      *
372      * @param fileName The path of the file to write.
373      * @param encoding The encoding of the file.
374      * @param data The content to write to the file.
375      * @throws IOException if any
376      */
377     public static void fileWrite(String fileName, String encoding, String data) throws IOException {
378         Path file = (fileName == null) ? null : Paths.get(fileName);
379         fileWrite(file, encoding, data);
380     }
381 
382     /**
383      * Writes data to a file. The file will be created if it does not exist. Note: the data is written with platform
384      * encoding
385      *
386      * @param file The file to write.
387      * @param data The content to write to the file.
388      * @throws IOException if any
389      * @since 2.0.6
390      */
391     public static void fileWrite(File file, String data) throws IOException {
392         fileWrite(file, null, data);
393     }
394 
395     /**
396      * Writes data to a file. The file will be created if it does not exist.
397      *
398      * @param file The file to write.
399      * @param encoding The encoding of the file.
400      * @param data The content to write to the file.
401      * @throws IOException if any
402      * @since 2.0.6
403      */
404     public static void fileWrite(File file, String encoding, String data) throws IOException {
405         fileWrite(file.toPath(), encoding, data);
406     }
407 
408     /**
409      * Deletes a file.
410      *
411      * @param fileName The path of the file to delete.
412      */
413     public static void fileDelete(String fileName) {
414         File file = new File(fileName);
415         try {
416             NioFiles.deleteIfExists(file);
417         } catch (IOException e) {
418             throw new RuntimeException(e);
419         }
420     }
421 
422     /**
423      * Waits for NFS to propagate a file creation, imposing a timeout.
424      *
425      * @param fileName The path of the file.
426      * @param seconds The maximum time in seconds to wait.
427      * @return True if file exists.
428      */
429     public static boolean waitFor(String fileName, int seconds) {
430         return waitFor(new File(fileName), seconds);
431     }
432 
433     /**
434      * Waits for NFS to propagate a file creation, imposing a timeout.
435      *
436      * @param file The file.
437      * @param seconds The maximum time in seconds to wait.
438      * @return True if file exists.
439      */
440     public static boolean waitFor(File file, int seconds) {
441         int timeout = 0;
442         int tick = 0;
443         while (!file.exists()) {
444             if (tick++ >= 10) {
445                 tick = 0;
446                 if (timeout++ > seconds) {
447                     return false;
448                 }
449             }
450             try {
451                 Thread.sleep(100);
452             } catch (InterruptedException ignore) {
453                 // nop
454             }
455         }
456         return true;
457     }
458 
459     /**
460      * Creates a file handle.
461      *
462      * @param fileName The path of the file.
463      * @return A <code>File</code> manager.
464      */
465     public static File getFile(String fileName) {
466         return new File(fileName);
467     }
468 
469     /**
470      * <p>Given a directory and an array of extensions return an array of compliant files.</p>
471      *
472      * <p>TODO Should an ignore list be passed in? TODO Should a recurse flag be passed in?</p>
473      *
474      * <p>The given extensions should be like "java" and not like ".java"</p>
475      *
476      * @param directory The path of the directory.
477      * @param extensions an array of expected extensions.
478      * @return An array of files for the wanted extensions.
479      */
480     public static String[] getFilesFromExtension(String directory, String[] extensions) {
481         List<String> files = new ArrayList<String>();
482 
483         File currentDir = new File(directory);
484 
485         String[] unknownFiles = currentDir.list();
486 
487         if (unknownFiles == null) {
488             return new String[0];
489         }
490 
491         for (String unknownFile : unknownFiles) {
492             String currentFileName = directory + System.getProperty("file.separator") + unknownFile;
493             File currentFile = new File(currentFileName);
494 
495             if (currentFile.isDirectory()) {
496                 // ignore all CVS directories...
497                 if (currentFile.getName().equals("CVS")) {
498                     continue;
499                 }
500 
501                 // ok... transverse into this directory and get all the files... then combine
502                 // them with the current list.
503 
504                 String[] fetchFiles = getFilesFromExtension(currentFileName, extensions);
505                 files = blendFilesToVector(files, fetchFiles);
506             } else {
507                 // ok... add the file
508 
509                 String add = currentFile.getAbsolutePath();
510                 if (isValidFile(add, extensions)) {
511                     files.add(add);
512                 }
513             }
514         }
515 
516         // ok... move the Vector into the files list...
517         return files.toArray(new String[0]);
518     }
519 
520     /**
521      * Private helper method for getFilesFromExtension()
522      */
523     private static List<String> blendFilesToVector(List<String> v, String[] files) {
524         for (String file : files) {
525             v.add(file);
526         }
527 
528         return v;
529     }
530 
531     /**
532      * Checks to see if a file is of a particular type(s). Note that if the file does not have an extension, an empty
533      * string (&quot;&quot;) is matched for.
534      */
535     private static boolean isValidFile(String file, String[] extensions) {
536         String extension = extension(file);
537         if (extension == null) {
538             extension = "";
539         }
540 
541         // ok.. now that we have the "extension" go through the current know
542         // excepted extensions and determine if this one is OK.
543 
544         for (String extension1 : extensions) {
545             if (extension1.equals(extension)) {
546                 return true;
547             }
548         }
549 
550         return false;
551     }
552 
553     /**
554      * Simple way to make a directory
555      *
556      * @param dir the directory to create
557      * @throws IllegalArgumentException if the dir contains illegal Windows characters under Windows OS.
558      * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
559      */
560     public static void mkdir(String dir) {
561         File file = new File(dir);
562 
563         if (Os.isFamily(Os.FAMILY_WINDOWS) && !isValidWindowsFileName(file)) {
564             throw new IllegalArgumentException("The file (" + dir
565                     + ") cannot contain any of the following characters: \n"
566                     + StringUtils.join(INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME, " "));
567         }
568 
569         if (!file.exists()) {
570             file.mkdirs();
571         }
572     }
573 
574     /**
575      * Compare the contents of two files to determine if they are equal or not.
576      *
577      * @param file1 the first file
578      * @param file2 the second file
579      * @return true if the content of the files are equal or they both don't exist, false otherwise
580      * @throws IOException if any
581      */
582     public static boolean contentEquals(final File file1, final File file2) throws IOException {
583         final boolean file1Exists = file1.exists();
584         if (file1Exists != file2.exists()) {
585             return false;
586         }
587 
588         if (!file1Exists) {
589             // two not existing files are equal
590             return true;
591         }
592 
593         if (file1.isDirectory() || file2.isDirectory()) {
594             // don't want to compare directory contents
595             return false;
596         }
597 
598         try (InputStream input1 = Files.newInputStream(file1.toPath());
599                 InputStream input2 = Files.newInputStream(file2.toPath())) {
600             return IOUtil.contentEquals(input1, input2);
601         }
602     }
603 
604     /**
605      * Convert from a <code>URL</code> to a <code>File</code>.
606      *
607      * @param url File URL.
608      * @return The equivalent <code>File</code> object, or <code>null</code> if the URL's protocol is not
609      *         <code>file</code>
610      */
611     public static File toFile(final URL url) {
612         if (url == null || !url.getProtocol().equalsIgnoreCase("file")) {
613             return null;
614         }
615 
616         String filename = url.getFile().replace('/', File.separatorChar);
617         int pos = -1;
618         while ((pos = filename.indexOf('%', pos + 1)) >= 0) {
619             if (pos + 2 < filename.length()) {
620                 String hexStr = filename.substring(pos + 1, pos + 3);
621                 char ch = (char) Integer.parseInt(hexStr, 16);
622                 filename = filename.substring(0, pos) + ch + filename.substring(pos + 3);
623             }
624         }
625         return new File(filename);
626     }
627 
628     /**
629      * Convert the array of Files into a list of URLs.
630      *
631      * @param files the array of files
632      * @return the array of URLs
633      * @throws IOException if an error occurs
634      */
635     public static URL[] toURLs(final File[] files) throws IOException {
636         final URL[] urls = new URL[files.length];
637 
638         for (int i = 0; i < urls.length; i++) {
639             urls[i] = files[i].toURI().toURL();
640         }
641 
642         return urls;
643     }
644 
645     /**
646      * Remove extension from filename. ie
647      *
648      * <pre>
649      * foo.txt    --&gt; foo
650      * a\b\c.jpg  --&gt; a\b\c
651      * a\b\c      --&gt; a\b\c
652      * </pre>
653      *
654      * @param filename the path of the file
655      * @return the filename minus extension
656      */
657     public static String removeExtension(final String filename) {
658         String ext = extension(filename);
659 
660         if ("".equals(ext)) {
661             return filename;
662         }
663 
664         final int index = filename.lastIndexOf(ext) - 1;
665         return filename.substring(0, index);
666     }
667 
668     /**
669      * Get extension from filename. ie
670      *
671      * <pre>
672      * foo.txt    --&gt; "txt"
673      * a\b\c.jpg  --&gt; "jpg"
674      * a\b\c      --&gt; ""
675      * </pre>
676      *
677      * @param filename the path of the file
678      * @return the extension of filename or "" if none
679      */
680     public static String getExtension(final String filename) {
681         return extension(filename);
682     }
683 
684     /**
685      * Remove path from filename. Equivalent to the unix command <code>basename</code> ie.
686      *
687      * <pre>
688      * a/b/c.txt --&gt; c.txt
689      * a.txt     --&gt; a.txt
690      * </pre>
691      *
692      * @param filepath the path of the file
693      * @return the filename minus path
694      */
695     public static String removePath(final String filepath) {
696         return removePath(filepath, File.separatorChar);
697     }
698 
699     /**
700      * Remove path from filename. ie.
701      *
702      * <pre>
703      * a/b/c.txt --&gt; c.txt
704      * a.txt     --&gt; a.txt
705      * </pre>
706      *
707      * @param filepath the path of the file
708      * @param fileSeparatorChar the file separator character like <b>/</b> on Unix platforms.
709      * @return the filename minus path
710      */
711     public static String removePath(final String filepath, final char fileSeparatorChar) {
712         final int index = filepath.lastIndexOf(fileSeparatorChar);
713 
714         if (-1 == index) {
715             return filepath;
716         }
717 
718         return filepath.substring(index + 1);
719     }
720 
721     /**
722      * Get path from filename. Roughly equivalent to the unix command <code>dirname</code>. ie.
723      *
724      * <pre>
725      * a/b/c.txt --&gt; a/b
726      * a.txt     --&gt; ""
727      * </pre>
728      *
729      * @param filepath the filepath
730      * @return the filename minus path
731      */
732     public static String getPath(final String filepath) {
733         return getPath(filepath, File.separatorChar);
734     }
735 
736     /**
737      * Get path from filename. ie.
738      *
739      * <pre>
740      * a/b/c.txt --&gt; a/b
741      * a.txt     --&gt; ""
742      * </pre>
743      *
744      * @param filepath the filepath
745      * @param fileSeparatorChar the file separator character like <b>/</b> on Unix platforms.
746      * @return the filename minus path
747      */
748     public static String getPath(final String filepath, final char fileSeparatorChar) {
749         final int index = filepath.lastIndexOf(fileSeparatorChar);
750         if (-1 == index) {
751             return "";
752         }
753 
754         return filepath.substring(0, index);
755     }
756 
757     /**
758      * Copy file from source to destination. If <code>destinationDirectory</code> does not exist, it (and any parent
759      * directories) will be created. If a file <code>source</code> in <code>destinationDirectory</code> exists, it will
760      * be overwritten.
761      *
762      * @param source An existing <code>File</code> to copy.
763      * @param destinationDirectory A directory to copy <code>source</code> into.
764      * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
765      * @throws IllegalArgumentException if <code>destinationDirectory</code> isn't a directory.
766      * @throws IOException if <code>source</code> does not exist, the file in <code>destinationDirectory</code> cannot
767      *             be written to, or an IO error occurs during copying.
768      */
769     public static void copyFileToDirectory(final String source, final String destinationDirectory) throws IOException {
770         copyFileToDirectory(new File(source), new File(destinationDirectory));
771     }
772 
773     /**
774      * Copy file from source to destination only if source is newer than the target file. If
775      * <code>destinationDirectory</code> does not exist, it (and any parent directories) will be created. If a file
776      * <code>source</code> in <code>destinationDirectory</code> exists, it will be overwritten.
777      *
778      * @param source An existing <code>File</code> to copy.
779      * @param destinationDirectory A directory to copy <code>source</code> into.
780      * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
781      * @throws IllegalArgumentException if <code>destinationDirectory</code> isn't a directory.
782      * @throws IOException if <code>source</code> does not exist, the file in <code>destinationDirectory</code> cannot
783      *             be written to, or an IO error occurs during copying.
784      */
785     public static void copyFileToDirectoryIfModified(final String source, final String destinationDirectory)
786             throws IOException {
787         copyFileToDirectoryIfModified(new File(source), new File(destinationDirectory));
788     }
789 
790     /**
791      * Copy file from source to destination. If <code>destinationDirectory</code> does not exist, it (and any parent
792      * directories) will be created. If a file <code>source</code> in <code>destinationDirectory</code> exists, it will
793      * be overwritten.
794      *
795      * @param source An existing <code>File</code> to copy.
796      * @param destinationDirectory A directory to copy <code>source</code> into.
797      * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
798      * @throws IllegalArgumentException if <code>destinationDirectory</code> isn't a directory.
799      * @throws IOException if <code>source</code> does not exist, the file in <code>destinationDirectory</code> cannot
800      *             be written to, or an IO error occurs during copying.
801      */
802     public static void copyFileToDirectory(final File source, final File destinationDirectory) throws IOException {
803         if (destinationDirectory.exists() && !destinationDirectory.isDirectory()) {
804             throw new IllegalArgumentException("Destination is not a directory");
805         }
806 
807         copyFile(source, new File(destinationDirectory, source.getName()));
808     }
809 
810     /**
811      * Copy file from source to destination only if source is newer than the target file. If
812      * <code>destinationDirectory</code> does not exist, it (and any parent directories) will be created. If a file
813      * <code>source</code> in <code>destinationDirectory</code> exists, it will be overwritten.
814      *
815      * @param source An existing <code>File</code> to copy.
816      * @param destinationDirectory A directory to copy <code>source</code> into.
817      * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
818      * @throws IllegalArgumentException if <code>destinationDirectory</code> isn't a directory.
819      * @throws IOException if <code>source</code> does not exist, the file in <code>destinationDirectory</code> cannot
820      *             be written to, or an IO error occurs during copying.
821      */
822     public static void copyFileToDirectoryIfModified(final File source, final File destinationDirectory)
823             throws IOException {
824         if (destinationDirectory.exists() && !destinationDirectory.isDirectory()) {
825             throw new IllegalArgumentException("Destination is not a directory");
826         }
827 
828         copyFileIfModified(source, new File(destinationDirectory, source.getName()));
829     }
830 
831     /**
832      * Creates a number of directories, as delivered from DirectoryScanner
833      *
834      * @param sourceBase The basedir used for the directory scan
835      * @param dirs The getIncludedDirs from the dirscanner
836      * @param destination The base dir of the output structure
837      * @throws IOException io issue
838      */
839     public static void mkDirs(final File sourceBase, String[] dirs, final File destination) throws IOException {
840         for (String dir : dirs) {
841             File src = new File(sourceBase, dir);
842             File dst = new File(destination, dir);
843             if (NioFiles.isSymbolicLink(src)) {
844                 File target = NioFiles.readSymbolicLink(src);
845                 NioFiles.createSymbolicLink(dst, target);
846             } else {
847                 dst.mkdirs();
848             }
849         }
850     }
851 
852     /**
853      * Copy file from source to destination. The directories up to <code>destination</code> will be created if they
854      * don't already exist. <code>destination</code> will be overwritten if it already exists.
855      *
856      * @param source An existing non-directory <code>File</code> to copy bytes from.
857      * @param destination A non-directory <code>File</code> to write bytes to (possibly overwriting).
858      * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot be written to, or an
859      *             IO error occurs during copying.
860      * @throws java.io.FileNotFoundException if <code>destination</code> is a directory (use
861      *             {@link #copyFileToDirectory}).
862      */
863     public static void copyFile(final File source, final File destination) throws IOException {
864         // check source exists
865         if (!source.exists()) {
866             final String message = "File " + source + " does not exist";
867             throw new IOException(message);
868         }
869 
870         // check source != destination, see PLXUTILS-10
871         if (source.getCanonicalPath().equals(destination.getCanonicalPath())) {
872             // if they are equal, we can exit the method without doing any work
873             return;
874         }
875         mkdirsFor(destination);
876 
877         doCopyFile(source, destination);
878 
879         if (source.length() != destination.length()) {
880             String message = "Failed to copy full contents from " + source + " to " + destination;
881             throw new IOException(message);
882         }
883     }
884 
885     private static void doCopyFile(File source, File destination) throws IOException {
886         doCopyFileUsingNewIO(source, destination);
887     }
888 
889     private static void doCopyFileUsingNewIO(File source, File destination) throws IOException {
890         NioFiles.copy(source, destination);
891     }
892 
893     /**
894      * Link file from destination to source. The directories up to <code>destination</code> will be created if they
895      * don't already exist. <code>destination</code> will be overwritten if it already exists.
896      *
897      * @param source An existing non-directory <code>File</code> to link to.
898      * @param destination A non-directory <code>File</code> becoming the link (possibly overwriting).
899      * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot be created, or an
900      *             IO error occurs during linking.
901      * @throws java.io.FileNotFoundException if <code>destination</code> is a directory (use
902      *             {@link #copyFileToDirectory}).
903      */
904     public static void linkFile(final File source, final File destination) throws IOException {
905         // check source exists
906         if (!source.exists()) {
907             final String message = "File " + source + " does not exist";
908             throw new IOException(message);
909         }
910 
911         // check source != destination, see PLXUTILS-10
912         if (source.getCanonicalPath().equals(destination.getCanonicalPath())) {
913             // if they are equal, we can exit the method without doing any work
914             return;
915         }
916         mkdirsFor(destination);
917 
918         NioFiles.createSymbolicLink(destination, source);
919     }
920 
921     /**
922      * Copy file from source to destination only if source timestamp is later than the destination timestamp. The
923      * directories up to <code>destination</code> will be created if they don't already exist. <code>destination</code>
924      * will be overwritten if it already exists.
925      *
926      * @param source An existing non-directory <code>File</code> to copy bytes from.
927      * @param destination A non-directory <code>File</code> to write bytes to (possibly overwriting).
928      * @return true if no problem occured
929      * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot be written to, or an
930      *             IO error occurs during copying.
931      */
932     public static boolean copyFileIfModified(final File source, final File destination) throws IOException {
933         if (isSourceNewerThanDestination(source, destination)) {
934             copyFile(source, destination);
935 
936             return true;
937         }
938 
939         return false;
940     }
941 
942     /**
943      * Copies bytes from the URL <code>source</code> to a file <code>destination</code>. The directories up to
944      * <code>destination</code> will be created if they don't already exist. <code>destination</code> will be
945      * overwritten if it already exists.
946      *
947      * @param source A <code>URL</code> to copy bytes from.
948      * @param destination A non-directory <code>File</code> to write bytes to (possibly overwriting).
949      * @throws IOException if
950      *             <ul>
951      *             <li><code>source</code> URL cannot be opened</li>
952      *             <li><code>destination</code> cannot be written to</li>
953      *             <li>an IO error occurs during copying</li>
954      *             </ul>
955      */
956     public static void copyURLToFile(final URL source, final File destination) throws IOException {
957         copyStreamToFile(new URLInputStreamFacade(source), destination);
958     }
959 
960     /**
961      * Copies bytes from the {@link InputStream} <code>source</code> to a file <code>destination</code>. The directories
962      * up to <code>destination</code> will be created if they don't already exist. <code>destination</code> will be
963      * overwritten if it already exists.
964      *
965      * @param source An {@link InputStream} to copy bytes from. This stream is guaranteed to be closed.
966      * @param destination A non-directory <code>File</code> to write bytes to (possibly overwriting).
967      * @throws IOException if
968      *             <ul>
969      *             <li><code>source</code> URL cannot be opened</li>
970      *             <li><code>destination</code> cannot be written to</li>
971      *             <li>an IO error occurs during copying</li>
972      *             </ul>
973      */
974     public static void copyStreamToFile(final InputStreamFacade source, final File destination) throws IOException {
975         mkdirsFor(destination);
976         checkCanWrite(destination);
977 
978         try (InputStream input = source.getInputStream();
979                 OutputStream output = Files.newOutputStream(destination.toPath())) {
980             IOUtil.copy(input, output);
981         }
982     }
983 
984     private static void checkCanWrite(File destination) throws IOException {
985         // make sure we can write to destination
986         if (destination.exists() && !destination.canWrite()) {
987             final String message = "Unable to open file " + destination + " for writing.";
988             throw new IOException(message);
989         }
990     }
991 
992     private static void mkdirsFor(File destination) {
993         // does destination directory exist ?
994         File parentFile = destination.getParentFile();
995         if (parentFile != null && !parentFile.exists()) {
996             parentFile.mkdirs();
997         }
998     }
999 
1000     /**
1001      * Normalize a path. Eliminates "/../" and "/./" in a string. Returns <code>null</code> if the ..'s went past the
1002      * root. Eg:
1003      *
1004      * <pre>
1005      * /foo//               --&gt;     /foo/
1006      * /foo/./              --&gt;     /foo/
1007      * /foo/../bar          --&gt;     /bar
1008      * /foo/../bar/         --&gt;     /bar/
1009      * /foo/../bar/../baz   --&gt;     /baz
1010      * //foo//./bar         --&gt;     /foo/bar
1011      * /../                 --&gt;     null
1012      * </pre>
1013      *
1014      * @param path the path to normalize
1015      * @return the normalized String, or <code>null</code> if too many ..'s.
1016      */
1017     public static String normalize(final String path) {
1018         String normalized = path;
1019         // Resolve occurrences of "//" in the normalized path
1020         while (true) {
1021             int index = normalized.indexOf("//");
1022             if (index < 0) {
1023                 break;
1024             }
1025             normalized = normalized.substring(0, index) + normalized.substring(index + 1);
1026         }
1027 
1028         // Resolve occurrences of "/./" in the normalized path
1029         while (true) {
1030             int index = normalized.indexOf("/./");
1031             if (index < 0) {
1032                 break;
1033             }
1034             normalized = normalized.substring(0, index) + normalized.substring(index + 2);
1035         }
1036 
1037         // Resolve occurrences of "/../" in the normalized path
1038         while (true) {
1039             int index = normalized.indexOf("/../");
1040             if (index < 0) {
1041                 break;
1042             }
1043             if (index == 0) {
1044                 return null; // Trying to go outside our context
1045             }
1046             int index2 = normalized.lastIndexOf('/', index - 1);
1047             normalized = normalized.substring(0, index2) + normalized.substring(index + 3);
1048         }
1049 
1050         // Return the normalized path that we have completed
1051         return normalized;
1052     }
1053 
1054     /**
1055      * <p>Will concatenate 2 paths. Paths with <code>..</code> will be properly handled.</p>
1056      *
1057      * Eg.,
1058      * <pre>
1059      * /a/b/c + d = /a/b/d
1060      * /a/b/c + ../d = /a/d
1061      * </pre>
1062      *
1063      * <p>Thieved from Tomcat sources...</p>
1064      *
1065      * @param lookupPath a path
1066      * @param path the path to concatenate
1067      * @return The concatenated paths, or null if error occurs
1068      */
1069     public static String catPath(final String lookupPath, final String path) {
1070         // Cut off the last slash and everything beyond
1071         int index = lookupPath.lastIndexOf("/");
1072         String lookup = lookupPath.substring(0, index);
1073         String pth = path;
1074 
1075         // Deal with .. by chopping dirs off the lookup path
1076         while (pth.startsWith("../")) {
1077             if (lookup.length() > 0) {
1078                 index = lookup.lastIndexOf("/");
1079                 lookup = lookup.substring(0, index);
1080             } else {
1081                 // More ..'s than dirs, return null
1082                 return null;
1083             }
1084 
1085             index = pth.indexOf("../") + 3;
1086             pth = pth.substring(index);
1087         }
1088 
1089         return new StringBuffer(lookup).append("/").append(pth).toString();
1090     }
1091 
1092     /**
1093      * Resolve a file <code>filename</code> to it's canonical form. If <code>filename</code> is relative (doesn't start
1094      * with <code>/</code>), it will be resolved relative to <code>baseFile</code>, otherwise it is treated as a normal
1095      * root-relative path.
1096      *
1097      * @param baseFile Where to resolve <code>filename</code> from, if <code>filename</code> is relative.
1098      * @param filename Absolute or relative file path to resolve.
1099      * @return The canonical <code>File</code> of <code>filename</code>.
1100      */
1101     public static File resolveFile(final File baseFile, String filename) {
1102         String filenm = filename;
1103         if ('/' != File.separatorChar) {
1104             filenm = filename.replace('/', File.separatorChar);
1105         }
1106 
1107         if ('\\' != File.separatorChar) {
1108             filenm = filename.replace('\\', File.separatorChar);
1109         }
1110 
1111         // deal with absolute files
1112         if (filenm.startsWith(File.separator) || (Os.isFamily(Os.FAMILY_WINDOWS) && filenm.indexOf(":") > 0)) {
1113             File file = new File(filenm);
1114 
1115             try {
1116                 file = file.getCanonicalFile();
1117             } catch (final IOException ioe) {
1118                 // nop
1119             }
1120 
1121             return file;
1122         }
1123         // FIXME: I'm almost certain this // removal is unnecessary, as getAbsoluteFile() strips
1124         // them. However, I'm not sure about this UNC stuff. (JT)
1125         final char[] chars = filename.toCharArray();
1126         final StringBuilder sb = new StringBuilder();
1127 
1128         // remove duplicate file separators in succession - except
1129         // on win32 at start of filename as UNC filenames can
1130         // be \\AComputer\AShare\myfile.txt
1131         int start = 0;
1132         if ('\\' == File.separatorChar) {
1133             sb.append(filenm.charAt(0));
1134             start++;
1135         }
1136 
1137         for (int i = start; i < chars.length; i++) {
1138             final boolean doubleSeparator = File.separatorChar == chars[i] && File.separatorChar == chars[i - 1];
1139 
1140             if (!doubleSeparator) {
1141                 sb.append(chars[i]);
1142             }
1143         }
1144 
1145         filenm = sb.toString();
1146 
1147         // must be relative
1148         File file = (new File(baseFile, filenm)).getAbsoluteFile();
1149 
1150         try {
1151             file = file.getCanonicalFile();
1152         } catch (final IOException ioe) {
1153             // nop
1154         }
1155 
1156         return file;
1157     }
1158 
1159     /**
1160      * Delete a file. If file is directory delete it and all sub-directories.
1161      *
1162      * @param file the file path
1163      * @throws IOException if any
1164      */
1165     public static void forceDelete(final String file) throws IOException {
1166         forceDelete(new File(file));
1167     }
1168 
1169     /**
1170      * Delete a file. If file is directory delete it and all sub-directories.
1171      *
1172      * @param file a file
1173      * @throws IOException if any
1174      */
1175     public static void forceDelete(final File file) throws IOException {
1176         if (file.isDirectory()) {
1177             deleteDirectory(file);
1178         } else {
1179             /*
1180              * NOTE: Always try to delete the file even if it appears to be non-existent. This will ensure that a
1181              * symlink whose target does not exist is deleted, too.
1182              */
1183             boolean filePresent = file.getCanonicalFile().exists();
1184             if (!deleteFile(file) && filePresent) {
1185                 final String message = "File " + file + " unable to be deleted.";
1186                 throw new IOException(message);
1187             }
1188         }
1189     }
1190 
1191     /**
1192      * Accommodate Windows bug encountered in both Sun and IBM JDKs. Others possible. If the delete does not work, call
1193      * System.gc(), wait a little and try again.
1194      *
1195      * @param file a file
1196      * @throws IOException if any
1197      */
1198     private static boolean deleteFile(File file) throws IOException {
1199         if (file.isDirectory()) {
1200             throw new IOException("File " + file + " isn't a file.");
1201         }
1202 
1203         if (!file.delete()) {
1204             if (Os.isFamily(Os.FAMILY_WINDOWS)) {
1205                 file = file.getCanonicalFile();
1206                 System.gc();
1207             }
1208 
1209             try {
1210                 Thread.sleep(10);
1211                 return file.delete();
1212             } catch (InterruptedException ignore) {
1213                 return file.delete();
1214             }
1215         }
1216 
1217         return true;
1218     }
1219 
1220     /**
1221      * Schedule a file to be deleted when JVM exits. If file is directory delete it and all sub-directories.
1222      *
1223      * @param file a file
1224      * @throws IOException if any
1225      */
1226     public static void forceDeleteOnExit(final File file) throws IOException {
1227         if (!file.exists()) {
1228             return;
1229         }
1230 
1231         if (file.isDirectory()) {
1232             deleteDirectoryOnExit(file);
1233         } else {
1234             file.deleteOnExit();
1235         }
1236     }
1237 
1238     /**
1239      * Recursively schedule directory for deletion on JVM exit.
1240      *
1241      * @param directory a directory
1242      * @throws IOException if any
1243      */
1244     private static void deleteDirectoryOnExit(final File directory) throws IOException {
1245         if (!directory.exists()) {
1246             return;
1247         }
1248         directory.deleteOnExit(); // The hook reverses the list
1249 
1250         cleanDirectoryOnExit(directory);
1251     }
1252 
1253     /**
1254      * Clean a directory without deleting it.
1255      *
1256      * @param directory a directory
1257      * @throws IOException if any
1258      */
1259     private static void cleanDirectoryOnExit(final File directory) throws IOException {
1260         if (!directory.exists()) {
1261             final String message = directory + " does not exist";
1262             throw new IllegalArgumentException(message);
1263         }
1264 
1265         if (!directory.isDirectory()) {
1266             final String message = directory + " is not a directory";
1267             throw new IllegalArgumentException(message);
1268         }
1269 
1270         IOException exception = null;
1271 
1272         final File[] files = directory.listFiles();
1273         for (final File file : files) {
1274             try {
1275                 forceDeleteOnExit(file);
1276             } catch (final IOException ioe) {
1277                 exception = ioe;
1278             }
1279         }
1280 
1281         if (null != exception) {
1282             throw exception;
1283         }
1284     }
1285 
1286     /**
1287      * Make a directory.
1288      *
1289      * @param file not null
1290      * @throws IOException If there already exists a file with specified name or the directory is unable to be created
1291      * @throws IllegalArgumentException if the file contains illegal Windows characters under Windows OS.
1292      * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
1293      */
1294     public static void forceMkdir(final File file) throws IOException {
1295         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
1296             if (!isValidWindowsFileName(file)) {
1297                 throw new IllegalArgumentException("The file (" + file.getAbsolutePath()
1298                         + ") cannot contain any of the following characters: \n"
1299                         + StringUtils.join(INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME, " "));
1300             }
1301         }
1302 
1303         if (file.exists()) {
1304             if (file.isFile()) {
1305                 final String message =
1306                         "File " + file + " exists and is " + "not a directory. Unable to create directory.";
1307                 throw new IOException(message);
1308             }
1309         } else {
1310             if (false == file.mkdirs()) {
1311                 final String message = "Unable to create directory " + file;
1312                 throw new IOException(message);
1313             }
1314         }
1315     }
1316 
1317     /**
1318      * Recursively delete a directory.
1319      *
1320      * @param directory a directory
1321      * @throws IOException if any
1322      */
1323     public static void deleteDirectory(final String directory) throws IOException {
1324         deleteDirectory(new File(directory));
1325     }
1326 
1327     /**
1328      * Recursively delete a directory.
1329      *
1330      * @param directory a directory
1331      * @throws IOException if any
1332      */
1333     public static void deleteDirectory(final File directory) throws IOException {
1334         if (!directory.exists()) {
1335             return;
1336         }
1337 
1338         /*
1339          * try delete the directory before its contents, which will take care of any directories that are really
1340          * symbolic links.
1341          */
1342         if (directory.delete()) {
1343             return;
1344         }
1345 
1346         cleanDirectory(directory);
1347         if (!directory.delete()) {
1348             final String message = "Directory " + directory + " unable to be deleted.";
1349             throw new IOException(message);
1350         }
1351     }
1352 
1353     /**
1354      * Clean a directory without deleting it.
1355      *
1356      * @param directory a directory
1357      * @throws IOException if any
1358      */
1359     public static void cleanDirectory(final String directory) throws IOException {
1360         cleanDirectory(new File(directory));
1361     }
1362 
1363     /**
1364      * Clean a directory without deleting it.
1365      *
1366      * @param directory a directory
1367      * @throws IOException if any
1368      */
1369     public static void cleanDirectory(final File directory) throws IOException {
1370         if (!directory.exists()) {
1371             final String message = directory + " does not exist";
1372             throw new IllegalArgumentException(message);
1373         }
1374 
1375         if (!directory.isDirectory()) {
1376             final String message = directory + " is not a directory";
1377             throw new IllegalArgumentException(message);
1378         }
1379 
1380         IOException exception = null;
1381 
1382         final File[] files = directory.listFiles();
1383 
1384         if (files == null) {
1385             return;
1386         }
1387 
1388         for (final File file : files) {
1389             try {
1390                 forceDelete(file);
1391             } catch (final IOException ioe) {
1392                 exception = ioe;
1393             }
1394         }
1395 
1396         if (null != exception) {
1397             throw exception;
1398         }
1399     }
1400 
1401     /**
1402      * Recursively count size of a directory.
1403      *
1404      * @param directory a directory
1405      * @return size of directory in bytes.
1406      */
1407     public static long sizeOfDirectory(final String directory) {
1408         return sizeOfDirectory(new File(directory));
1409     }
1410 
1411     /**
1412      * Recursively count size of a directory.
1413      *
1414      * @param directory a directory
1415      * @return size of directory in bytes.
1416      */
1417     public static long sizeOfDirectory(final File directory) {
1418         if (!directory.exists()) {
1419             final String message = directory + " does not exist";
1420             throw new IllegalArgumentException(message);
1421         }
1422 
1423         if (!directory.isDirectory()) {
1424             final String message = directory + " is not a directory";
1425             throw new IllegalArgumentException(message);
1426         }
1427 
1428         long size = 0;
1429 
1430         final File[] files = directory.listFiles();
1431         for (final File file : files) {
1432             if (file.isDirectory()) {
1433                 size += sizeOfDirectory(file);
1434             } else {
1435                 size += file.length();
1436             }
1437         }
1438 
1439         return size;
1440     }
1441 
1442     /**
1443      * Return the files contained in the directory, using inclusion and exclusion Ant patterns, including the directory
1444      * name in each of the files
1445      *
1446      * @param directory the directory to scan
1447      * @param includes the includes pattern, comma separated
1448      * @param excludes the excludes pattern, comma separated
1449      * @return a list of File objects
1450      * @throws IOException io issue
1451      * @see #getFileNames(File, String, String, boolean)
1452      */
1453     public static List<File> getFiles(File directory, String includes, String excludes) throws IOException {
1454         return getFiles(directory, includes, excludes, true);
1455     }
1456 
1457     /**
1458      * Return the files contained in the directory, using inclusion and exclusion Ant patterns
1459      *
1460      * @param directory the directory to scan
1461      * @param includes the includes pattern, comma separated
1462      * @param excludes the excludes pattern, comma separated
1463      * @param includeBasedir true to include the base dir in each file
1464      * @return a list of File objects
1465      * @throws IOException io issue
1466      * @see #getFileNames(File, String, String, boolean)
1467      */
1468     public static List<File> getFiles(File directory, String includes, String excludes, boolean includeBasedir)
1469             throws IOException {
1470         List<String> fileNames = getFileNames(directory, includes, excludes, includeBasedir);
1471 
1472         List<File> files = new ArrayList<File>();
1473 
1474         for (String filename : fileNames) {
1475             files.add(new File(filename));
1476         }
1477 
1478         return files;
1479     }
1480 
1481     /**
1482      * Return a list of files as String depending options. This method use case sensitive file name.
1483      *
1484      * @param directory the directory to scan
1485      * @param includes the includes pattern, comma separated
1486      * @param excludes the excludes pattern, comma separated
1487      * @param includeBasedir true to include the base dir in each String of file
1488      * @return a list of files as String
1489      * @throws IOException io issue
1490      */
1491     public static List<String> getFileNames(File directory, String includes, String excludes, boolean includeBasedir)
1492             throws IOException {
1493         return getFileNames(directory, includes, excludes, includeBasedir, true);
1494     }
1495 
1496     /**
1497      * Return a list of files as String depending options.
1498      *
1499      * @param directory the directory to scan
1500      * @param includes the includes pattern, comma separated
1501      * @param excludes the excludes pattern, comma separated
1502      * @param includeBasedir true to include the base dir in each String of file
1503      * @param isCaseSensitive true if case sensitive
1504      * @return a list of files as String
1505      * @throws IOException io issue
1506      */
1507     public static List<String> getFileNames(
1508             File directory, String includes, String excludes, boolean includeBasedir, boolean isCaseSensitive)
1509             throws IOException {
1510         return getFileAndDirectoryNames(directory, includes, excludes, includeBasedir, isCaseSensitive, true, false);
1511     }
1512 
1513     /**
1514      * Return a list of directories as String depending options. This method use case sensitive file name.
1515      *
1516      * @param directory the directory to scan
1517      * @param includes the includes pattern, comma separated
1518      * @param excludes the excludes pattern, comma separated
1519      * @param includeBasedir true to include the base dir in each String of file
1520      * @return a list of directories as String
1521      * @throws IOException io issue
1522      */
1523     public static List<String> getDirectoryNames(
1524             File directory, String includes, String excludes, boolean includeBasedir) throws IOException {
1525         return getDirectoryNames(directory, includes, excludes, includeBasedir, true);
1526     }
1527 
1528     /**
1529      * Return a list of directories as String depending options.
1530      *
1531      * @param directory the directory to scan
1532      * @param includes the includes pattern, comma separated
1533      * @param excludes the excludes pattern, comma separated
1534      * @param includeBasedir true to include the base dir in each String of file
1535      * @param isCaseSensitive true if case sensitive
1536      * @return a list of directories as String
1537      * @throws IOException io issue
1538      */
1539     public static List<String> getDirectoryNames(
1540             File directory, String includes, String excludes, boolean includeBasedir, boolean isCaseSensitive)
1541             throws IOException {
1542         return getFileAndDirectoryNames(directory, includes, excludes, includeBasedir, isCaseSensitive, false, true);
1543     }
1544 
1545     /**
1546      * Return a list of files as String depending options.
1547      *
1548      * @param directory the directory to scan
1549      * @param includes the includes pattern, comma separated
1550      * @param excludes the excludes pattern, comma separated
1551      * @param includeBasedir true to include the base dir in each String of file
1552      * @param isCaseSensitive true if case sensitive
1553      * @param getFiles true if get files
1554      * @param getDirectories true if get directories
1555      * @return a list of files as String
1556      * @throws IOException io issue
1557      */
1558     public static List<String> getFileAndDirectoryNames(
1559             File directory,
1560             String includes,
1561             String excludes,
1562             boolean includeBasedir,
1563             boolean isCaseSensitive,
1564             boolean getFiles,
1565             boolean getDirectories)
1566             throws IOException {
1567         DirectoryScanner scanner = new DirectoryScanner();
1568 
1569         scanner.setBasedir(directory);
1570 
1571         if (includes != null) {
1572             scanner.setIncludes(StringUtils.split(includes, ","));
1573         }
1574 
1575         if (excludes != null) {
1576             scanner.setExcludes(StringUtils.split(excludes, ","));
1577         }
1578 
1579         scanner.setCaseSensitive(isCaseSensitive);
1580 
1581         scanner.scan();
1582 
1583         List<String> list = new ArrayList<String>();
1584 
1585         if (getFiles) {
1586             String[] files = scanner.getIncludedFiles();
1587 
1588             for (String file : files) {
1589                 if (includeBasedir) {
1590                     list.add(directory + FileUtils.FS + file);
1591                 } else {
1592                     list.add(file);
1593                 }
1594             }
1595         }
1596 
1597         if (getDirectories) {
1598             String[] directories = scanner.getIncludedDirectories();
1599 
1600             for (String directory1 : directories) {
1601                 if (includeBasedir) {
1602                     list.add(directory + FileUtils.FS + directory1);
1603                 } else {
1604                     list.add(directory1);
1605                 }
1606             }
1607         }
1608 
1609         return list;
1610     }
1611 
1612     /**
1613      * Copy a directory to an other one.
1614      *
1615      * @param sourceDirectory the source dir
1616      * @param destinationDirectory the target dir
1617      * @throws IOException if any
1618      */
1619     public static void copyDirectory(File sourceDirectory, File destinationDirectory) throws IOException {
1620         copyDirectory(sourceDirectory, destinationDirectory, "**", null);
1621     }
1622 
1623     /**
1624      * Copy a directory to an other one.
1625      *
1626      * @param sourceDirectory the source dir
1627      * @param destinationDirectory the target dir
1628      * @param includes include pattern
1629      * @param excludes exclude pattern
1630      * @throws IOException if any
1631      * @see #getFiles(File, String, String)
1632      */
1633     public static void copyDirectory(File sourceDirectory, File destinationDirectory, String includes, String excludes)
1634             throws IOException {
1635         if (!sourceDirectory.exists()) {
1636             return;
1637         }
1638 
1639         List<File> files = getFiles(sourceDirectory, includes, excludes);
1640 
1641         for (File file : files) {
1642             copyFileToDirectory(file, destinationDirectory);
1643         }
1644     }
1645 
1646     /**
1647      * <p>Copies a entire directory layout : no files will be copied only directories</p>
1648      *
1649      * Note:
1650      * <ul>
1651      * <li>It will include empty directories.
1652      * <li>The <code>sourceDirectory</code> must exists.
1653      * </ul>
1654      *
1655      * @param sourceDirectory the source dir
1656      * @param destinationDirectory the target dir
1657      * @param includes include pattern
1658      * @param excludes exclude pattern
1659      * @throws IOException if any
1660      * @since 1.5.7
1661      */
1662     public static void copyDirectoryLayout(
1663             File sourceDirectory, File destinationDirectory, String[] includes, String[] excludes) throws IOException {
1664         if (sourceDirectory == null) {
1665             throw new IOException("source directory can't be null.");
1666         }
1667 
1668         if (destinationDirectory == null) {
1669             throw new IOException("destination directory can't be null.");
1670         }
1671 
1672         if (sourceDirectory.equals(destinationDirectory)) {
1673             throw new IOException("source and destination are the same directory.");
1674         }
1675 
1676         if (!sourceDirectory.exists()) {
1677             throw new IOException("Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ").");
1678         }
1679 
1680         DirectoryScanner scanner = new DirectoryScanner();
1681 
1682         scanner.setBasedir(sourceDirectory);
1683 
1684         if (includes != null && includes.length >= 1) {
1685             scanner.setIncludes(includes);
1686         } else {
1687             scanner.setIncludes(new String[] {"**"});
1688         }
1689 
1690         if (excludes != null && excludes.length >= 1) {
1691             scanner.setExcludes(excludes);
1692         }
1693 
1694         scanner.addDefaultExcludes();
1695         scanner.scan();
1696         List<String> includedDirectories = Arrays.asList(scanner.getIncludedDirectories());
1697 
1698         for (String name : includedDirectories) {
1699             File source = new File(sourceDirectory, name);
1700 
1701             if (source.equals(sourceDirectory)) {
1702                 continue;
1703             }
1704 
1705             File destination = new File(destinationDirectory, name);
1706             destination.mkdirs();
1707         }
1708     }
1709 
1710     /**
1711      * <p>Copies a entire directory structure.</p>
1712      *
1713      * Note:
1714      * <ul>
1715      * <li>It will include empty directories.
1716      * <li>The <code>sourceDirectory</code> must exists.
1717      * </ul>
1718      *
1719      * @param sourceDirectory the source dir
1720      * @param destinationDirectory the target dir
1721      * @throws IOException if any
1722      */
1723     public static void copyDirectoryStructure(File sourceDirectory, File destinationDirectory) throws IOException {
1724         copyDirectoryStructure(sourceDirectory, destinationDirectory, destinationDirectory, false);
1725     }
1726 
1727     /**
1728      * <p>Copies an entire directory structure but only source files with timestamp later than the destinations'.</p>
1729      *
1730      * Note:
1731      * <ul>
1732      * <li>It will include empty directories.
1733      * <li>The <code>sourceDirectory</code> must exists.
1734      * </ul>
1735      *
1736      * @param sourceDirectory the source dir
1737      * @param destinationDirectory the target dir
1738      * @throws IOException if any
1739      */
1740     public static void copyDirectoryStructureIfModified(File sourceDirectory, File destinationDirectory)
1741             throws IOException {
1742         copyDirectoryStructure(sourceDirectory, destinationDirectory, destinationDirectory, true);
1743     }
1744 
1745     private static void copyDirectoryStructure(
1746             File sourceDirectory, File destinationDirectory, File rootDestinationDirectory, boolean onlyModifiedFiles)
1747             throws IOException {
1748         if (sourceDirectory == null) {
1749             throw new IOException("source directory can't be null.");
1750         }
1751 
1752         if (destinationDirectory == null) {
1753             throw new IOException("destination directory can't be null.");
1754         }
1755 
1756         if (sourceDirectory.equals(destinationDirectory)) {
1757             throw new IOException("source and destination are the same directory.");
1758         }
1759 
1760         if (!sourceDirectory.exists()) {
1761             throw new IOException("Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ").");
1762         }
1763 
1764         File[] files = sourceDirectory.listFiles();
1765 
1766         String sourcePath = sourceDirectory.getAbsolutePath();
1767 
1768         for (File file : files) {
1769             if (file.equals(rootDestinationDirectory)) {
1770                 // We don't copy the destination directory in itself
1771                 continue;
1772             }
1773 
1774             String dest = file.getAbsolutePath();
1775 
1776             dest = dest.substring(sourcePath.length() + 1);
1777 
1778             File destination = new File(destinationDirectory, dest);
1779 
1780             if (file.isFile()) {
1781                 destination = destination.getParentFile();
1782 
1783                 if (onlyModifiedFiles) {
1784                     copyFileToDirectoryIfModified(file, destination);
1785                 } else {
1786                     copyFileToDirectory(file, destination);
1787                 }
1788             } else if (file.isDirectory()) {
1789                 if (!destination.exists() && !destination.mkdirs()) {
1790                     throw new IOException(
1791                             "Could not create destination directory '" + destination.getAbsolutePath() + "'.");
1792                 }
1793 
1794                 copyDirectoryStructure(file, destination, rootDestinationDirectory, onlyModifiedFiles);
1795             } else {
1796                 throw new IOException("Unknown file type: " + file.getAbsolutePath());
1797             }
1798         }
1799     }
1800 
1801     /**
1802      * <p>Renames a file, even if that involves crossing file system boundaries.</p>
1803      *
1804      * <p>This will remove <code>to</code> (if it exists), ensure that <code>to</code>'s parent directory exists and move
1805      * <code>from</code>, which involves deleting <code>from</code> as well.</p>
1806      *
1807      * @param from the file to move
1808      * @param to the new file name
1809      * @throws IOException if anything bad happens during this process. Note that <code>to</code> may have been deleted
1810      *             already when this happens.
1811      */
1812     public static void rename(File from, File to) throws IOException {
1813         if (to.exists() && !to.delete()) {
1814             throw new IOException("Failed to delete " + to + " while trying to rename " + from);
1815         }
1816 
1817         File parent = to.getParentFile();
1818         if (parent != null && !parent.exists() && !parent.mkdirs()) {
1819             throw new IOException("Failed to create directory " + parent + " while trying to rename " + from);
1820         }
1821 
1822         if (!from.renameTo(to)) {
1823             copyFile(from, to);
1824             if (!from.delete()) {
1825                 throw new IOException("Failed to delete " + from + " while trying to rename it.");
1826             }
1827         }
1828     }
1829 
1830     /**
1831      * <p>Create a temporary file in a given directory.</p>
1832      *
1833      * <p>The file denoted by the returned abstract pathname did not exist before this method was invoked, any subsequent
1834      * invocation of this method will yield a different file name.</p>
1835      *
1836      * <p>The filename is prefixNNNNNsuffix where NNNN is a random number</p>
1837      *
1838      * <p>This method is different to {@link File#createTempFile(String, String, File)} of JDK 1.2 as it doesn't create the
1839      * file itself. It uses the location pointed to by java.io.tmpdir when the parentDir attribute is null.</p>
1840      *
1841      * <p>To delete automatically the file created by this method, use the {@link File#deleteOnExit()} method.</p>
1842      *
1843      * @param prefix prefix before the random number
1844      * @param suffix file extension; include the '.'
1845      * @param parentDir Directory to create the temporary file in <code>-java.io.tmpdir</code> used if not specificed
1846      * @return a File reference to the new temporary file.
1847      */
1848     public static File createTempFile(String prefix, String suffix, File parentDir) {
1849         File result = null;
1850         String parent = System.getProperty("java.io.tmpdir");
1851         if (parentDir != null) {
1852             parent = parentDir.getPath();
1853         }
1854         DecimalFormat fmt = new DecimalFormat("#####");
1855         SecureRandom secureRandom = new SecureRandom();
1856         long secureInitializer = secureRandom.nextLong();
1857         Random rand = new Random(secureInitializer + Runtime.getRuntime().freeMemory());
1858         synchronized (rand) {
1859             do {
1860                 result = new File(parent, prefix + fmt.format(Math.abs(rand.nextInt())) + suffix);
1861             } while (result.exists());
1862         }
1863 
1864         return result;
1865     }
1866 
1867     /**
1868      * <b>If wrappers is null or empty, the file will be copy only if {@code to.lastModified() < from.lastModified()}</b>
1869      *
1870      * @param from the file to copy
1871      * @param to the destination file
1872      * @param encoding the file output encoding (only if wrappers is not empty)
1873      * @param wrappers array of {@link FilterWrapper}
1874      * @throws IOException if an IO error occurs during copying or filtering
1875      */
1876     public static void copyFile(File from, File to, String encoding, FilterWrapper[] wrappers) throws IOException {
1877         copyFile(from, to, encoding, wrappers, false);
1878     }
1879 
1880     public abstract static class FilterWrapper {
1881         public abstract Reader getReader(Reader fileReader);
1882     }
1883 
1884     /**
1885      * <b>If wrappers is null or empty, the file will be copy only if {@code to.lastModified() < from.lastModified()}, if overwrite is true</b>
1886      *
1887      * @param from the file to copy
1888      * @param to the destination file
1889      * @param encoding the file output encoding (only if wrappers is not empty)
1890      * @param wrappers array of {@link FilterWrapper}
1891      * @param overwrite if true and wrappers is null or empty, the file will be copied even if {@code to.lastModified() < from.lastModified()}
1892      * @throws IOException if an IO error occurs during copying or filtering
1893      * @since 1.5.2
1894      */
1895     public static void copyFile(File from, File to, String encoding, FilterWrapper[] wrappers, boolean overwrite)
1896             throws IOException {
1897         if (wrappers != null && wrappers.length > 0) {
1898             // buffer so it isn't reading a byte at a time!
1899             Reader fileReader = null;
1900             Writer fileWriter = null;
1901             try {
1902                 if (encoding == null || encoding.length() < 1) {
1903                     fileReader = Files.newBufferedReader(from.toPath());
1904                     fileWriter = Files.newBufferedWriter(to.toPath());
1905                 } else {
1906                     OutputStream outstream = Files.newOutputStream(to.toPath());
1907 
1908                     fileReader = Files.newBufferedReader(from.toPath(), Charset.forName(encoding));
1909 
1910                     fileWriter = new OutputStreamWriter(outstream, encoding);
1911                 }
1912 
1913                 Reader reader = fileReader;
1914                 for (FilterWrapper wrapper : wrappers) {
1915                     reader = wrapper.getReader(reader);
1916                 }
1917 
1918                 IOUtil.copy(reader, fileWriter);
1919                 fileWriter.close();
1920                 fileWriter = null;
1921                 fileReader.close();
1922                 fileReader = null;
1923             } finally {
1924                 IOUtil.close(fileReader);
1925                 IOUtil.close(fileWriter);
1926             }
1927         } else {
1928             if (isSourceNewerThanDestination(from, to) || overwrite) {
1929                 copyFile(from, to);
1930             }
1931         }
1932     }
1933 
1934     private static boolean isSourceNewerThanDestination(File source, File destination) {
1935         return (destination.lastModified() == 0L && source.lastModified() == 0L)
1936                 || destination.lastModified() < source.lastModified();
1937     }
1938 
1939     /**
1940      * Note: the file content is read with platform encoding
1941      *
1942      * @param file the file
1943      * @return a List containing every every line not starting with # and not empty
1944      * @throws IOException if any
1945      */
1946     public static List<String> loadFile(File file) throws IOException {
1947         final List<String> lines = new ArrayList<String>();
1948 
1949         if (file.exists()) {
1950             try (BufferedReader reader = Files.newBufferedReader(file.toPath())) {
1951                 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
1952                     line = line.trim();
1953 
1954                     if (!line.startsWith("#") && line.length() != 0) {
1955                         lines.add(line);
1956                     }
1957                 }
1958             }
1959         }
1960 
1961         return lines;
1962     }
1963 
1964     /**
1965      * For Windows OS, check if the file name contains any of the following characters:
1966      * <code>":", "*", "?", "\"", "&lt;", "&gt;", "|"</code>
1967      *
1968      * @param f not null file
1969      * @return <code>false</code> if the file path contains any of forbidden Windows characters, <code>true</code> if
1970      *         the Os is not Windows or if the file path respect the Windows constraints.
1971      * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
1972      * @since 1.5.2
1973      */
1974     public static boolean isValidWindowsFileName(File f) {
1975         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
1976             if (StringUtils.indexOfAny(f.getName(), INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME) != -1) {
1977                 return false;
1978             }
1979 
1980             File parentFile = f.getParentFile();
1981             if (parentFile != null) {
1982                 return isValidWindowsFileName(parentFile);
1983             }
1984         }
1985 
1986         return true;
1987     }
1988 }