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