View Javadoc

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