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.java 1670518 2015-03-31 23:38:42Z hboutemy $
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
756      *                                       occurs during copying.
757      */
758     public static void copyFileToDirectory( @Nonnull final File source, @Nonnull final File destinationDirectory )
759         throws IOException
760     {
761         if ( destinationDirectory.exists() && !destinationDirectory.isDirectory() )
762         {
763             throw new IllegalArgumentException( "Destination is not a directory" );
764         }
765 
766         copyFile( source, new File( destinationDirectory, source.getName() ) );
767     }
768 
769     /**
770      * Copy file from source to destination only if source is newer than the target file.
771      * If <code>destinationDirectory</code> does not exist, it
772      * (and any parent directories) will be created. If a file <code>source</code> in
773      * <code>destinationDirectory</code> exists, it will be overwritten.
774      *
775      * @param source               An existing <code>File</code> to copy.
776      * @param destinationDirectory A directory to copy <code>source</code> into.
777      * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
778      * @throws IllegalArgumentException      if <code>destinationDirectory</code> isn't a directory.
779      * @throws IOException                   if <code>source</code> does not exist, the file in
780      *                                       <code>destinationDirectory</code> cannot be written to, or an IO error
781      *                                       occurs during copying.
782      */
783     private static void copyFileToDirectoryIfModified( @Nonnull final File source,
784                                                        @Nonnull final File destinationDirectory )
785         throws IOException
786     {
787         if ( destinationDirectory.exists() && !destinationDirectory.isDirectory() )
788         {
789             throw new IllegalArgumentException( "Destination is not a directory" );
790         }
791 
792         copyFileIfModified( source, new File( destinationDirectory, source.getName() ) );
793     }
794 
795 
796     /**
797      * Copy file from source to destination. The directories up to <code>destination</code> will be
798      * created if they don't already exist. <code>destination</code> will be overwritten if it
799      * already exists.
800      *
801      * @param source      An existing non-directory <code>File</code> to copy bytes from.
802      * @param destination A non-directory <code>File</code> to write bytes to (possibly
803      *                    overwriting).
804      * @throws IOException                   if <code>source</code> does not exist, <code>destination</code> cannot be
805      *                                       written to, or an IO error occurs during copying.
806      * @throws java.io.FileNotFoundException if <code>destination</code> is a directory
807      */
808     public static void copyFile( @Nonnull final File source, @Nonnull final File destination )
809         throws IOException
810     {
811         //check source exists
812         if ( !source.exists() )
813         {
814             final String message = "File " + source + " does not exist";
815             throw new IOException( message );
816         }
817         if ( Java7Support.isAtLeastJava7() && Java7Support.isSymLink( source ) )
818         {
819             File target = Java7Support.readSymbolicLink( source );
820             Java7Support.createSymbolicLink( destination, target );
821             return;
822         }
823 
824         //check source != destination, see PLXUTILS-10
825         if ( source.getCanonicalPath().equals( destination.getCanonicalPath() ) )
826         {
827             //if they are equal, we can exit the method without doing any work
828             return;
829         }
830 
831         mkdirsFor( destination );
832 
833         doCopyFile( source, destination );
834 
835         if ( source.length() != destination.length() )
836         {
837             final String message = "Failed to copy full contents from " + source + " to " + destination;
838             throw new IOException( message );
839         }
840     }
841 
842     private static void mkdirsFor( @Nonnull File destination )
843     {
844         //does destination directory exist ?
845         if ( destination.getParentFile() != null && !destination.getParentFile().exists() )
846         {
847             //noinspection ResultOfMethodCallIgnored
848             destination.getParentFile().mkdirs();
849         }
850     }
851 
852     private static void doCopyFile( @Nonnull File source, @Nonnull File destination )
853         throws IOException
854     {
855         FileInputStream fis = null;
856         FileOutputStream fos = null;
857         FileChannel input = null;
858         FileChannel output = null;
859         try
860         {
861             fis = new FileInputStream( source );
862             fos = new FileOutputStream( destination );
863             input = fis.getChannel();
864             output = fos.getChannel();
865             long size = input.size();
866             long pos = 0;
867             long count;
868             while ( pos < size )
869             {
870                 count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : size - pos;
871                 pos += output.transferFrom( input, pos, count );
872             }
873         }
874         finally
875         {
876             IOUtil.close( output );
877             IOUtil.close( fos );
878             IOUtil.close( input );
879             IOUtil.close( fis );
880         }
881     }
882 
883     /**
884      * Copy file from source to destination only if source timestamp is later than the destination timestamp.
885      * The directories up to <code>destination</code> will be created if they don't already exist.
886      * <code>destination</code> will be overwritten if it already exists.
887      *
888      * @param source      An existing non-directory <code>File</code> to copy bytes from.
889      * @param destination A non-directory <code>File</code> to write bytes to (possibly
890      *                    overwriting).
891      * @return true if no problem occured
892      * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot be
893      *                     written to, or an IO error occurs during copying.
894      */
895     private static boolean copyFileIfModified( @Nonnull final File source, @Nonnull final File destination )
896         throws IOException
897     {
898         if ( destination.lastModified() < source.lastModified() )
899         {
900             copyFile( source, destination );
901 
902             return true;
903         }
904 
905         return false;
906     }
907 
908     /**
909      * Copies bytes from the URL <code>source</code> to a file <code>destination</code>.
910      * The directories up to <code>destination</code> will be created if they don't already exist.
911      * <code>destination</code> will be overwritten if it already exists.
912      *
913      * @param source      A <code>URL</code> to copy bytes from.
914      * @param destination A non-directory <code>File</code> to write bytes to (possibly
915      *                    overwriting).
916      * @throws IOException if
917      *                     <ul>
918      *                     <li><code>source</code> URL cannot be opened</li>
919      *                     <li><code>destination</code> cannot be written to</li>
920      *                     <li>an IO error occurs during copying</li>
921      *                     </ul>
922      */
923     public static void copyURLToFile( @Nonnull final URL source, @Nonnull final File destination )
924         throws IOException
925     {
926         copyStreamToFile( source.openStream(), destination );
927     }
928 
929     /**
930      * Copies bytes from the {@link InputStream} <code>source</code> to a file <code>destination</code>.
931      * The directories up to <code>destination</code> will be created if they don't already exist.
932      * <code>destination</code> will be overwritten if it already exists.
933      *
934      * @param source      An {@link InputStream} to copy bytes from. This stream is
935      *                    guaranteed to be closed.
936      * @param destination A non-directory <code>File</code> to write bytes to (possibly
937      *                    overwriting).
938      * @throws IOException if
939      *                     <ul>
940      *                     <li><code>source</code> URL cannot be opened</li>
941      *                     <li><code>destination</code> cannot be written to</li>
942      *                     <li>an IO error occurs during copying</li>
943      *                     </ul>
944      */
945     private static void copyStreamToFile( @Nonnull final @WillClose InputStream source,
946                                           @Nonnull final File destination )
947         throws IOException
948     {
949         FileOutputStream output = null;
950         try
951         {
952             //does destination directory exist ?
953             if ( destination.getParentFile() != null && !destination.getParentFile().exists() )
954             {
955                 //noinspection ResultOfMethodCallIgnored
956                 destination.getParentFile().mkdirs();
957             }
958 
959             //make sure we can write to destination
960             if ( destination.exists() && !destination.canWrite() )
961             {
962                 final String message = "Unable to open file " + destination + " for writing.";
963                 throw new IOException( message );
964             }
965 
966             output = new FileOutputStream( destination );
967             IOUtil.copy( source, output );
968         }
969         finally
970         {
971             IOUtil.close( source );
972             IOUtil.close( output );
973         }
974     }
975 
976     /**
977      * Normalize a path.
978      * Eliminates "/../" and "/./" in a string. Returns <code>null</code> if the ..'s went past the
979      * root.
980      * Eg:
981      * <pre>
982      * /foo//               -->     /foo/
983      * /foo/./              -->     /foo/
984      * /foo/../bar          -->     /bar
985      * /foo/../bar/         -->     /bar/
986      * /foo/../bar/../baz   -->     /baz
987      * //foo//./bar         -->     /foo/bar
988      * /../                 -->     null
989      * </pre>
990      *
991      * @param path the path to normalize
992      * @return the normalized String, or <code>null</code> if too many ..'s.
993      */
994     public static @Nonnull String normalize( @Nonnull final String path )
995     {
996         String normalized = path;
997         // Resolve occurrences of "//" in the normalized path
998         while ( true )
999         {
1000             int index = normalized.indexOf( "//" );
1001             if ( index < 0 )
1002             {
1003                 break;
1004             }
1005             normalized = normalized.substring( 0, index ) + normalized.substring( index + 1 );
1006         }
1007 
1008         // Resolve occurrences of "/./" in the normalized path
1009         while ( true )
1010         {
1011             int index = normalized.indexOf( "/./" );
1012             if ( index < 0 )
1013             {
1014                 break;
1015             }
1016             normalized = normalized.substring( 0, index ) + normalized.substring( index + 2 );
1017         }
1018 
1019         // Resolve occurrences of "/../" in the normalized path
1020         while ( true )
1021         {
1022             int index = normalized.indexOf( "/../" );
1023             if ( index < 0 )
1024             {
1025                 break;
1026             }
1027             if ( index == 0 )
1028             {
1029                 return null;  // Trying to go outside our context
1030             }
1031             int index2 = normalized.lastIndexOf( '/', index - 1 );
1032             normalized = normalized.substring( 0, index2 ) + normalized.substring( index + 3 );
1033         }
1034 
1035         // Return the normalized path that we have completed
1036         return normalized;
1037     }
1038 
1039     /**
1040      * Resolve a file <code>filename</code> to it's canonical form. If <code>filename</code> is
1041      * relative (doesn't start with <code>/</code>), it will be resolved relative to
1042      * <code>baseFile</code>, otherwise it is treated as a normal root-relative path.
1043      *
1044      * @param baseFile Where to resolve <code>filename</code> from, if <code>filename</code> is
1045      *                 relative.
1046      * @param filename Absolute or relative file path to resolve.
1047      * @return The canonical <code>File</code> of <code>filename</code>.
1048      */
1049     public static @Nonnull File resolveFile( final File baseFile, @Nonnull String filename )
1050     {
1051         String filenm = filename;
1052         if ( '/' != File.separatorChar )
1053         {
1054             filenm = filename.replace( '/', File.separatorChar );
1055         }
1056 
1057         if ( '\\' != File.separatorChar )
1058         {
1059             filenm = filename.replace( '\\', File.separatorChar );
1060         }
1061 
1062         // deal with absolute files
1063         if ( filenm.startsWith( File.separator ) || ( Os.isFamily( Os.FAMILY_WINDOWS ) && filenm.indexOf( ":" ) > 0 ) )
1064         {
1065             File file = new File( filenm );
1066 
1067             try
1068             {
1069                 file = file.getCanonicalFile();
1070             }
1071             catch ( final IOException ioe )
1072             {
1073                 // nop
1074             }
1075 
1076             return file;
1077         }
1078         // FIXME: I'm almost certain this // removal is unnecessary, as getAbsoluteFile() strips
1079         // them. However, I'm not sure about this UNC stuff. (JT)
1080         final char[] chars = filename.toCharArray();
1081         final StringBuilder sb = new StringBuilder();
1082 
1083         //remove duplicate file separators in succession - except
1084         //on win32 at start of filename as UNC filenames can
1085         //be \\AComputer\AShare\myfile.txt
1086         int start = 0;
1087         if ( '\\' == File.separatorChar )
1088         {
1089             sb.append( filenm.charAt( 0 ) );
1090             start++;
1091         }
1092 
1093         for ( int i = start; i < chars.length; i++ )
1094         {
1095             final boolean doubleSeparator = File.separatorChar == chars[i] && File.separatorChar == chars[i - 1];
1096 
1097             if ( !doubleSeparator )
1098             {
1099                 sb.append( chars[i] );
1100             }
1101         }
1102 
1103         filenm = sb.toString();
1104 
1105         //must be relative
1106         File file = ( new File( baseFile, filenm ) ).getAbsoluteFile();
1107 
1108         try
1109         {
1110             file = file.getCanonicalFile();
1111         }
1112         catch ( final IOException ioe )
1113         {
1114             // nop
1115         }
1116 
1117         return file;
1118     }
1119 
1120     /**
1121      * Delete a file. If file is directory delete it and all sub-directories.
1122      *
1123      * @param file the file path
1124      * @throws IOException if any
1125      */
1126     public static void forceDelete( @Nonnull final String file )
1127         throws IOException
1128     {
1129         forceDelete( new File( file ) );
1130     }
1131 
1132     /**
1133      * Delete a file. If file is directory delete it and all sub-directories.
1134      *
1135      * @param file a file
1136      * @throws IOException if any
1137      */
1138     public static void forceDelete( @Nonnull final File file )
1139         throws IOException
1140     {
1141         if ( file.isDirectory() )
1142         {
1143             deleteDirectory( file );
1144         }
1145         else
1146         {
1147             /*
1148              * NOTE: Always try to delete the file even if it appears to be non-existent. This will ensure that a
1149              * symlink whose target does not exist is deleted, too.
1150              */
1151             boolean filePresent = file.getCanonicalFile().exists();
1152             if ( !deleteFile( file ) && filePresent )
1153             {
1154                 final String message = "File " + file + " unable to be deleted.";
1155                 throw new IOException( message );
1156             }
1157         }
1158     }
1159 
1160     /**
1161      * deletes a file.
1162      *
1163      * @param file The file to delete
1164      * @throws IOException If the file cannot be delted.
1165      */
1166 
1167 
1168     public static void delete( @Nonnull File file )
1169         throws IOException
1170     {
1171         if ( Java7Support.isAtLeastJava7() )
1172         {
1173             Java7Support.delete( file );
1174         }
1175         else
1176         {
1177             if ( !file.delete() )
1178             {
1179                 throw new IOException( "Could not delete " + file.getName() );
1180             }
1181         }
1182     }
1183 
1184     public static boolean deleteLegacyStyle( @Nonnull File file )
1185     {
1186         if ( Java7Support.isAtLeastJava7() )
1187         {
1188             try
1189             {
1190                 Java7Support.delete( file );
1191                 return true;
1192             }
1193             catch ( IOException e )
1194             {
1195                 return false;
1196             }
1197         }
1198         else
1199         {
1200             return file.delete();
1201         }
1202     }
1203 
1204     /**
1205      * Accommodate Windows bug encountered in both Sun and IBM JDKs.
1206      * Others possible. If the delete does not work, call System.gc(),
1207      * wait a little and try again.
1208      *
1209      * @param file a file
1210      * @throws IOException if any
1211      */
1212     private static boolean deleteFile( @Nonnull File file )
1213         throws IOException
1214     {
1215         if ( file.isDirectory() )
1216         {
1217             throw new IOException( "File " + file + " isn't a file." );
1218         }
1219 
1220         if ( !deleteLegacyStyle( file ) )
1221         {
1222             if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
1223             {
1224                 file = file.getCanonicalFile();
1225                 System.gc();
1226             }
1227 
1228             try
1229             {
1230                 Thread.sleep( 10 );
1231                 return deleteLegacyStyle( file );
1232             }
1233             catch ( InterruptedException ex )
1234             {
1235                 return deleteLegacyStyle( file );
1236             }
1237         }
1238 
1239         return true;
1240     }
1241 
1242 
1243     /**
1244      * Make a directory.
1245      *
1246      * @param file not null
1247      * @throws IOException              If there already exists a file with specified name or
1248      *                                  the directory is unable to be created
1249      * @throws IllegalArgumentException if the file contains illegal Windows characters under Windows OS.
1250      * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
1251      */
1252     public static void forceMkdir( @Nonnull final File file )
1253         throws IOException
1254     {
1255         if ( Os.isFamily( Os.FAMILY_WINDOWS ) && !isValidWindowsFileName( file ) )
1256         {
1257             throw new IllegalArgumentException(
1258                 "The file (" + file.getAbsolutePath() + ") cannot contain any of the following characters: \n"
1259                     + StringUtils.join( INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME, " " ) );
1260         }
1261 
1262         if ( file.exists() )
1263         {
1264             if ( file.isFile() )
1265             {
1266                 final String message =
1267                     "File " + file + " exists and is " + "not a directory. Unable to create directory.";
1268                 throw new IOException( message );
1269             }
1270         }
1271         else
1272         {
1273             if ( !file.mkdirs() )
1274             {
1275                 final String message = "Unable to create directory " + file;
1276                 throw new IOException( message );
1277             }
1278         }
1279     }
1280 
1281     /**
1282      * Recursively delete a directory.
1283      *
1284      * @param directory a directory
1285      * @throws IOException if any
1286      */
1287     public static void deleteDirectory( @Nonnull final String directory )
1288         throws IOException
1289     {
1290         deleteDirectory( new File( directory ) );
1291     }
1292 
1293     /**
1294      * Recursively delete a directory.
1295      *
1296      * @param directory a directory
1297      * @throws IOException if any
1298      */
1299     public static void deleteDirectory( @Nonnull final File directory )
1300         throws IOException
1301     {
1302         if ( !directory.exists() )
1303         {
1304             return;
1305         }
1306 
1307         /* try delete the directory before its contents, which will take
1308          * care of any directories that are really symbolic links.
1309          */
1310         if ( deleteLegacyStyle( directory ) )
1311         {
1312             return;
1313         }
1314 
1315         cleanDirectory( directory );
1316         if ( !deleteLegacyStyle( directory ) )
1317         {
1318             final String message = "Directory " + directory + " unable to be deleted.";
1319             throw new IOException( message );
1320         }
1321     }
1322 
1323     /**
1324      * Clean a directory without deleting it.
1325      *
1326      * @param directory a directory
1327      * @throws IOException if any
1328      */
1329     public static void cleanDirectory( @Nonnull final File directory )
1330         throws IOException
1331     {
1332         if ( !directory.exists() )
1333         {
1334             final String message = directory + " does not exist";
1335             throw new IllegalArgumentException( message );
1336         }
1337 
1338         if ( !directory.isDirectory() )
1339         {
1340             final String message = directory + " is not a directory";
1341             throw new IllegalArgumentException( message );
1342         }
1343 
1344         IOException exception = null;
1345 
1346         final File[] files = directory.listFiles();
1347 
1348         if ( files == null )
1349         {
1350             return;
1351         }
1352 
1353         for ( final File file : files )
1354         {
1355             try
1356             {
1357                 forceDelete( file );
1358             }
1359             catch ( final IOException ioe )
1360             {
1361                 exception = ioe;
1362             }
1363         }
1364 
1365         if ( null != exception )
1366         {
1367             throw exception;
1368         }
1369     }
1370 
1371     /**
1372      * Recursively count size of a directory.
1373      *
1374      * @param directory a directory
1375      * @return size of directory in bytes.
1376      */
1377     public static long sizeOfDirectory( @Nonnull final String directory )
1378     {
1379         return sizeOfDirectory( new File( directory ) );
1380     }
1381 
1382     /**
1383      * Recursively count size of a directory.
1384      *
1385      * @param directory a directory
1386      * @return size of directory in bytes.
1387      */
1388     public static long sizeOfDirectory( @Nonnull final File directory )
1389     {
1390         if ( !directory.exists() )
1391         {
1392             final String message = directory + " does not exist";
1393             throw new IllegalArgumentException( message );
1394         }
1395 
1396         if ( !directory.isDirectory() )
1397         {
1398             final String message = directory + " is not a directory";
1399             throw new IllegalArgumentException( message );
1400         }
1401 
1402         long size = 0;
1403 
1404         final File[] files = directory.listFiles();
1405         if ( files == null )
1406         {
1407             throw new IllegalArgumentException( "Problems reading directory" );
1408         }
1409 
1410         for ( final File file : files )
1411         {
1412             if ( file.isDirectory() )
1413             {
1414                 size += sizeOfDirectory( file );
1415             }
1416             else
1417             {
1418                 size += file.length();
1419             }
1420         }
1421 
1422         return size;
1423     }
1424 
1425     /**
1426      * Return the files contained in the directory, using inclusion and exclusion Ant patterns,
1427      * including the directory name in each of the files
1428      *
1429      * @param directory the directory to scan
1430      * @param includes  the includes pattern, comma separated
1431      * @param excludes  the excludes pattern, comma separated
1432      * @return a list of File objects
1433      * @throws IOException
1434      * @see #getFileNames(File, String, String, boolean)
1435      */
1436     @Nonnull
1437     public static List<File> getFiles( @Nonnull File directory, @Nullable String includes, @Nullable String excludes )
1438         throws IOException
1439     {
1440         return getFiles( directory, includes, excludes, true );
1441     }
1442 
1443     /**
1444      * Return the files contained in the directory, using inclusion and exclusion Ant patterns
1445      *
1446      * @param directory      the directory to scan
1447      * @param includes       the includes pattern, comma separated
1448      * @param excludes       the excludes pattern, comma separated
1449      * @param includeBasedir true to include the base dir in each file
1450      * @return a list of File objects
1451      * @throws IOException
1452      * @see #getFileNames(File, String, String, boolean)
1453      */
1454     @Nonnull
1455     public static List<File> getFiles( @Nonnull File directory, @Nullable String includes, @Nullable String excludes,
1456                                        boolean includeBasedir )
1457         throws IOException
1458     {
1459         List<String> fileNames = getFileNames( directory, includes, excludes, includeBasedir );
1460 
1461         List<File> files = new ArrayList<File>();
1462 
1463         for ( String filename : fileNames )
1464         {
1465             files.add( new File( filename ) );
1466         }
1467 
1468         return files;
1469     }
1470 
1471     /**
1472      * Return a list of files as String depending options.
1473      * This method use case sensitive file name.
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      * @return a list of files as String
1480      * @throws IOException
1481      */
1482     @Nonnull public static List<String> getFileNames( @Nonnull File directory, @Nullable String includes,
1483                                                       @Nullable String excludes, boolean includeBasedir )
1484         throws IOException
1485     {
1486         return getFileNames( directory, includes, excludes, includeBasedir, true );
1487     }
1488 
1489     /**
1490      * Return a list of files as String depending options.
1491      *
1492      * @param directory       the directory to scan
1493      * @param includes        the includes pattern, comma separated
1494      * @param excludes        the excludes pattern, comma separated
1495      * @param includeBasedir  true to include the base dir in each String of file
1496      * @param isCaseSensitive true if case sensitive
1497      * @return a list of files as String
1498      * @throws IOException
1499      */
1500     @Nonnull private static List<String> getFileNames( @Nonnull File directory, @Nullable String includes,
1501                                                        @Nullable String excludes, boolean includeBasedir,
1502                                                        boolean isCaseSensitive )
1503         throws IOException
1504     {
1505         return getFileAndDirectoryNames( directory, includes, excludes, includeBasedir, isCaseSensitive, true, false );
1506     }
1507 
1508     /**
1509      * Return a list of directories as String depending options.
1510      * This method use case sensitive file name.
1511      *
1512      * @param directory      the directory to scan
1513      * @param includes       the includes pattern, comma separated
1514      * @param excludes       the excludes pattern, comma separated
1515      * @param includeBasedir true to include the base dir in each String of file
1516      * @return a list of directories as String
1517      * @throws IOException
1518      */
1519     @Nonnull public static List<String> getDirectoryNames( @Nonnull File directory, @Nullable String includes,
1520                                                            @Nullable String excludes, boolean includeBasedir )
1521         throws IOException
1522     {
1523         return getDirectoryNames( directory, includes, excludes, includeBasedir, true );
1524     }
1525 
1526     /**
1527      * Return a list of directories as String depending options.
1528      *
1529      * @param directory       the directory to scan
1530      * @param includes        the includes pattern, comma separated
1531      * @param excludes        the excludes pattern, comma separated
1532      * @param includeBasedir  true to include the base dir in each String of file
1533      * @param isCaseSensitive true if case sensitive
1534      * @return a list of directories as String
1535      * @throws IOException
1536      */
1537     @Nonnull public static List<String> getDirectoryNames( @Nonnull File directory, @Nullable String includes,
1538                                                            @Nullable String excludes, boolean includeBasedir,
1539                                                            boolean isCaseSensitive )
1540         throws IOException
1541     {
1542         return getFileAndDirectoryNames( directory, includes, excludes, includeBasedir, isCaseSensitive, false, true );
1543     }
1544 
1545     /**
1546      * Return a list of files as String depending options.
1547      *
1548      * @param directory       the directory to scan
1549      * @param includes        the includes pattern, comma separated
1550      * @param excludes        the excludes pattern, comma separated
1551      * @param includeBasedir  true to include the base dir in each String of file
1552      * @param isCaseSensitive true if case sensitive
1553      * @param getFiles        true if get files
1554      * @param getDirectories  true if get directories
1555      * @return a list of files as String
1556      */
1557     @Nonnull public static List<String> getFileAndDirectoryNames( File directory, @Nullable String includes,
1558                                                                   @Nullable String excludes, boolean includeBasedir,
1559                                                                   boolean isCaseSensitive, boolean getFiles,
1560                                                                   boolean getDirectories )
1561     {
1562         DirectoryScanner scanner = new DirectoryScanner();
1563 
1564         scanner.setBasedir( directory );
1565 
1566         if ( includes != null )
1567         {
1568             scanner.setIncludes( StringUtils.split( includes, "," ) );
1569         }
1570 
1571         if ( excludes != null )
1572         {
1573             scanner.setExcludes( StringUtils.split( excludes, "," ) );
1574         }
1575 
1576         scanner.setCaseSensitive( isCaseSensitive );
1577 
1578         scanner.scan();
1579 
1580         List<String> list = new ArrayList<String>();
1581 
1582         if ( getFiles )
1583         {
1584             String[] files = scanner.getIncludedFiles();
1585 
1586             for ( String file : files )
1587             {
1588                 if ( includeBasedir )
1589                 {
1590                     list.add( directory + FileUtils.FS + file );
1591                 }
1592                 else
1593                 {
1594                     list.add( file );
1595                 }
1596             }
1597         }
1598 
1599         if ( getDirectories )
1600         {
1601             String[] directories = scanner.getIncludedDirectories();
1602 
1603             for ( String directory1 : directories )
1604             {
1605                 if ( includeBasedir )
1606                 {
1607                     list.add( directory + FileUtils.FS + directory1 );
1608                 }
1609                 else
1610                 {
1611                     list.add( directory1 );
1612                 }
1613             }
1614         }
1615 
1616         return list;
1617     }
1618 
1619     /**
1620      * Copy a directory to an other one.
1621      *
1622      * @param sourceDirectory      the source dir
1623      * @param destinationDirectory the target dir
1624      * @throws IOException if any
1625      */
1626     public static void copyDirectory( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory )
1627         throws IOException
1628     {
1629         copyDirectory( sourceDirectory, destinationDirectory, "**", null );
1630     }
1631 
1632     /**
1633      * Copy a directory to an other one.
1634      *
1635      * @param sourceDirectory      the source dir
1636      * @param destinationDirectory the target dir
1637      * @param includes             include pattern
1638      * @param excludes             exlucde pattern
1639      * @throws IOException if any
1640      * @see #getFiles(File, String, String)
1641      */
1642     public static void copyDirectory( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory,
1643                                       @Nullable String includes, @Nullable String excludes )
1644         throws IOException
1645     {
1646         if ( !sourceDirectory.exists() )
1647         {
1648             return;
1649         }
1650 
1651         List<File> files = getFiles( sourceDirectory, includes, excludes );
1652 
1653         for ( File file : files )
1654         {
1655             copyFileToDirectory( file, destinationDirectory );
1656         }
1657     }
1658 
1659     /**
1660      * Copies a entire directory structure.
1661      * <p/>
1662      * Note:
1663      * <ul>
1664      * <li>It will include empty directories.
1665      * <li>The <code>sourceDirectory</code> must exists.
1666      * </ul>
1667      *
1668      * @param sourceDirectory      the source dir
1669      * @param destinationDirectory the target dir
1670      * @throws IOException if any
1671      */
1672     public static void copyDirectoryStructure( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory )
1673         throws IOException
1674     {
1675         copyDirectoryStructure( sourceDirectory, destinationDirectory, destinationDirectory, false );
1676     }
1677 
1678     private static void copyDirectoryStructure( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory,
1679                                                 File rootDestinationDirectory, boolean onlyModifiedFiles )
1680         throws IOException
1681     {
1682         //noinspection ConstantConditions
1683         if ( sourceDirectory == null )
1684         {
1685             throw new IOException( "source directory can't be null." );
1686         }
1687 
1688         //noinspection ConstantConditions
1689         if ( destinationDirectory == null )
1690         {
1691             throw new IOException( "destination directory can't be null." );
1692         }
1693 
1694         if ( sourceDirectory.equals( destinationDirectory ) )
1695         {
1696             throw new IOException( "source and destination are the same directory." );
1697         }
1698 
1699         if ( !sourceDirectory.exists() )
1700         {
1701             throw new IOException( "Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ")." );
1702         }
1703 
1704         File[] files = sourceDirectory.listFiles();
1705 
1706         if ( files == null )
1707         {
1708             return;
1709         }
1710 
1711         String sourcePath = sourceDirectory.getAbsolutePath();
1712 
1713         for ( File file : files )
1714         {
1715             if ( file.equals( rootDestinationDirectory ) )
1716             {
1717                 // We don't copy the destination directory in itself
1718                 continue;
1719             }
1720 
1721             String dest = file.getAbsolutePath();
1722 
1723             dest = dest.substring( sourcePath.length() + 1 );
1724 
1725             File destination = new File( destinationDirectory, dest );
1726 
1727             if ( file.isFile() )
1728             {
1729                 destination = destination.getParentFile();
1730 
1731                 if ( onlyModifiedFiles )
1732                 {
1733                     copyFileToDirectoryIfModified( file, destination );
1734                 }
1735                 else
1736                 {
1737                     copyFileToDirectory( file, destination );
1738                 }
1739             }
1740             else if ( file.isDirectory() )
1741             {
1742                 if ( !destination.exists() && !destination.mkdirs() )
1743                 {
1744                     throw new IOException(
1745                         "Could not create destination directory '" + destination.getAbsolutePath() + "'." );
1746                 }
1747 
1748                 copyDirectoryStructure( file, destination, rootDestinationDirectory, onlyModifiedFiles );
1749             }
1750             else
1751             {
1752                 throw new IOException( "Unknown file type: " + file.getAbsolutePath() );
1753             }
1754         }
1755     }
1756 
1757     /**
1758      * Renames a file, even if that involves crossing file system boundaries.
1759      * <p/>
1760      * <p>This will remove <code>to</code> (if it exists), ensure that
1761      * <code>to</code>'s parent directory exists and move
1762      * <code>from</code>, which involves deleting <code>from</code> as
1763      * well.</p>
1764      *
1765      * @param from the file to move
1766      * @param to   the new file name
1767      * @throws IOException if anything bad happens during this process.
1768      *                     Note that <code>to</code> may have been deleted already when this happens.
1769      */
1770     public static void rename( @Nonnull File from, @Nonnull File to )
1771         throws IOException
1772     {
1773         if ( to.exists() && !deleteLegacyStyle( to ) )
1774         {
1775             throw new IOException( "Failed to delete " + to + " while trying to rename " + from );
1776         }
1777 
1778         File parent = to.getParentFile();
1779         if ( parent != null && !parent.exists() && !parent.mkdirs() )
1780         {
1781             throw new IOException( "Failed to create directory " + parent + " while trying to rename " + from );
1782         }
1783 
1784         if ( !from.renameTo( to ) )
1785         {
1786             copyFile( from, to );
1787             if ( !deleteLegacyStyle( from ) )
1788             {
1789                 throw new IOException( "Failed to delete " + from + " while trying to rename it." );
1790             }
1791         }
1792     }
1793 
1794     /**
1795      * Create a temporary file in a given directory.
1796      * <p/>
1797      * <p>The file denoted by the returned abstract pathname did not
1798      * exist before this method was invoked, any subsequent invocation
1799      * of this method will yield a different file name.</p>
1800      * <p/>
1801      * The filename is prefixNNNNNsuffix where NNNN is a random number
1802      * </p>
1803      * <p>This method is different to {@link File#createTempFile(String, String, File)} of JDK 1.2
1804      * as it doesn't create the file itself.
1805      * It uses the location pointed to by java.io.tmpdir
1806      * when the parentDir attribute is
1807      * null.</p>
1808      * <p>To delete automatically the file created by this method, use the
1809      * {@link File#deleteOnExit()} method.</p>
1810      *
1811      * @param prefix    prefix before the random number
1812      * @param suffix    file extension; include the '.'
1813      * @param parentDir Directory to create the temporary file in <code>-java.io.tmpdir</code>
1814      *                  used if not specificed
1815      * @return a File reference to the new temporary file.
1816      */
1817     public static File createTempFile( @Nonnull String prefix, @Nonnull String suffix, @Nullable File parentDir )
1818     {
1819         File result;
1820         String parent = System.getProperty( "java.io.tmpdir" );
1821         if ( parentDir != null )
1822         {
1823             parent = parentDir.getPath();
1824         }
1825         DecimalFormat fmt = new DecimalFormat( "#####" );
1826         SecureRandom secureRandom = new SecureRandom();
1827         long secureInitializer = secureRandom.nextLong();
1828         Random rand = new Random( secureInitializer + Runtime.getRuntime().freeMemory() );
1829         do
1830         {
1831             result = new File( parent, prefix + fmt.format( positiveRandom( rand ) ) + suffix );
1832         }
1833         while ( result.exists() );
1834 
1835         return result;
1836     }
1837 
1838     private static int positiveRandom( Random rand )
1839     {
1840         int a = rand.nextInt();
1841         while ( a == Integer.MIN_VALUE )
1842         {
1843             a = rand.nextInt();
1844         }
1845         return Math.abs( a );
1846     }
1847 
1848     /**
1849      * <b>If wrappers is null or empty, the file will be copy only if to.lastModified() < from.lastModified()</b>
1850      *
1851      * @param from     the file to copy
1852      * @param to       the destination file
1853      * @param encoding the file output encoding (only if wrappers is not empty)
1854      * @param wrappers array of {@link FilterWrapper}
1855      * @throws IOException if an IO error occurs during copying or filtering
1856      */
1857     public static void copyFile( @Nonnull File from, @Nonnull File to, @Nullable String encoding,
1858                                  @Nullable FilterWrapper... wrappers )
1859         throws IOException
1860     {
1861         copyFile( from, to, encoding, wrappers, false );
1862     }
1863 
1864     /**
1865      * 
1866      */
1867     public abstract static class FilterWrapper
1868     {
1869         public abstract Reader getReader( Reader fileReader );
1870     }
1871 
1872     /**
1873      * <b>If wrappers is null or empty, the file will be copy only if to.lastModified() < from.lastModified() or if
1874      * overwrite is true</b>
1875      *
1876      * @param from the file to copy
1877      * @param to the destination file
1878      * @param encoding the file output encoding (only if wrappers is not empty)
1879      * @param wrappers array of {@link FilterWrapper}
1880      * @param overwrite if true and f wrappers is null or empty, the file will be copy enven if to.lastModified() <
1881      *            from.lastModified()
1882      * @throws IOException if an IO error occurs during copying or filtering
1883      */
1884     public static void copyFile( @Nonnull File from, @Nonnull File to, @Nullable String encoding,
1885                                  @Nullable FilterWrapper[] wrappers, boolean overwrite )
1886         throws IOException
1887     {
1888         if ( wrappers != null && wrappers.length > 0 )
1889         {
1890             // buffer so it isn't reading a byte at a time!
1891             Reader fileReader = null;
1892             Writer fileWriter = null;
1893             try
1894             {
1895                 if ( encoding == null || encoding.length() < 1 )
1896                 {
1897                     fileReader = new BufferedReader( new FileReader( from ) );
1898                     fileWriter = new FileWriter( to );
1899                 }
1900                 else
1901                 {
1902                     FileInputStream instream = new FileInputStream( from );
1903 
1904                     FileOutputStream outstream = new FileOutputStream( to );
1905 
1906                     fileReader = new BufferedReader( new InputStreamReader( instream, encoding ) );
1907 
1908                     fileWriter = new OutputStreamWriter( outstream, encoding );
1909                 }
1910 
1911                 Reader reader = fileReader;
1912                 for ( FilterWrapper wrapper : wrappers )
1913                 {
1914                     reader = wrapper.getReader( reader );
1915                 }
1916 
1917                 IOUtil.copy( reader, fileWriter );
1918             }
1919             finally
1920             {
1921                 IOUtil.close( fileReader );
1922                 IOUtil.close( fileWriter );
1923             }
1924         }
1925         else
1926         {
1927             if ( to.lastModified() < from.lastModified() || overwrite )
1928             {
1929                 copyFile( from, to );
1930             }
1931         }
1932     }
1933 
1934     /**
1935      * Note: the file content is read with platform encoding
1936      *
1937      * @param file the file
1938      * @return a List containing every every line not starting with # and not empty
1939      * @throws IOException if any
1940      */
1941     @Nonnull public static List<String> loadFile( @Nonnull File file )
1942         throws IOException
1943     {
1944         List<String> lines = new ArrayList<String>();
1945 
1946         if ( file.exists() )
1947         {
1948             FileReader fileReader = new FileReader( file );
1949             try
1950             {
1951                 BufferedReader reader = new BufferedReader( fileReader );
1952 
1953                 String line = reader.readLine();
1954 
1955                 while ( line != null )
1956                 {
1957                     line = line.trim();
1958 
1959                     if ( !line.startsWith( "#" ) && line.length() != 0 )
1960                     {
1961                         lines.add( line );
1962                     }
1963                     line = reader.readLine();
1964                 }
1965 
1966                 reader.close();
1967             }
1968             finally
1969             {
1970                 fileReader.close();
1971             }
1972         }
1973 
1974         return lines;
1975     }
1976 
1977     /**
1978      * For Windows OS, check if the file name contains any of the following characters:
1979      * <code>":", "*", "?", "\"", "<", ">", "|"</code>
1980      *
1981      * @param f not null file
1982      * @return <code>false</code> if the file path contains any of forbidden Windows characters,
1983      * <code>true</code> if the Os is not Windows or if the file path respect the Windows constraints.
1984      * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
1985      */
1986     private static boolean isValidWindowsFileName( @Nonnull File f )
1987     {
1988         if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
1989         {
1990             if ( StringUtils.indexOfAny( f.getName(), INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME ) != -1 )
1991             {
1992                 return false;
1993             }
1994 
1995             if ( f.getParentFile() != null )
1996             {
1997                 return isValidWindowsFileName( f.getParentFile() );
1998             }
1999         }
2000 
2001         return true;
2002     }
2003 
2004     /**
2005      * Checks whether a given file is a symbolic link.
2006      *
2007      * This only works reliably on java7 and higher. For earlier version we use a highly crappy heuristic
2008      * that mostly does not work.
2009      * <p>
2010      * It doesn't really test for symbolic links but whether the canonical and absolute paths of the file are identical
2011      * - this may lead to false positives on some platforms.
2012      * </p>
2013      *
2014      * @param file the file to check
2015      *
2016      */
2017     public static boolean isSymbolicLink( final @Nonnull File file )
2018         throws IOException
2019     {
2020         if ( Java7Support.isAtLeastJava7() )
2021         {
2022             return Java7Support.isSymLink( file );
2023         }
2024         return isSymbolicLinkLegacy( file );
2025     }
2026 
2027     /**
2028      * Checks whether a given file is a symbolic link.
2029      *
2030      * @param file the file to check
2031      * @return true if and only if we reliably can say this is a symlink. This will
2032      *         always return false for java versions prior to 1.7.
2033      *
2034      */
2035     public static boolean isSymbolicLinkForSure( final @Nonnull File file )
2036         throws IOException
2037     {
2038         return Java7Support.isAtLeastJava7() && Java7Support.isSymLink( file );
2039     }
2040 
2041     /**
2042      * Checks whether a given file is a symbolic link.
2043      * <p>
2044      * It doesn't really test for symbolic links but whether the canonical and absolute
2045      * paths of the file are identical - this may lead to false positives on some platforms.
2046      *
2047      * It also returns true for any file that has been reached via a symbolic link,
2048      * if you decide to traverse into the symlink.
2049      *
2050      * As can be seen from the "return" clause of this method, there is really no
2051      * guarantee of any sort from this method. Small wonder this ever got used for
2052      * anything.
2053      * </p>
2054      *
2055      * @param file the file to check
2056      * @return true if the file is a symbolic link or if we're on some crappy os.
2057      *         false if the file is not a symlink or we're not able to detect it.
2058      */
2059     static boolean isSymbolicLinkLegacy( final @Nonnull File file )
2060         throws IOException
2061     {
2062         final File canonical = new File( file.getCanonicalPath() );
2063         return !file.getAbsolutePath().equals( canonical.getPath() );
2064     }
2065 
2066 }