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