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