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 1706704 2015-10-04 17:35:19Z 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                 System.gc();
1231             }
1232 
1233             try
1234             {
1235                 Thread.sleep( 10 );
1236                 return deleteLegacyStyle( file );
1237             }
1238             catch ( InterruptedException ex )
1239             {
1240                 return deleteLegacyStyle( file );
1241             }
1242         }
1243 
1244         return true;
1245     }
1246 
1247 
1248     /**
1249      * Make a directory.
1250      *
1251      * @param file not null
1252      * @throws IOException              If there already exists a file with specified name or
1253      *                                  the directory is unable to be created
1254      * @throws IllegalArgumentException if the file contains illegal Windows characters under Windows OS.
1255      * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
1256      */
1257     public static void forceMkdir( @Nonnull final File file )
1258         throws IOException
1259     {
1260         if ( Os.isFamily( Os.FAMILY_WINDOWS ) && !isValidWindowsFileName( file ) )
1261         {
1262             throw new IllegalArgumentException(
1263                 "The file (" + file.getAbsolutePath() + ") cannot contain any of the following characters: \n"
1264                     + StringUtils.join( INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME, " " ) );
1265         }
1266 
1267         if ( file.exists() )
1268         {
1269             if ( file.isFile() )
1270             {
1271                 final String message =
1272                     "File " + file + " exists and is " + "not a directory. Unable to create directory.";
1273                 throw new IOException( message );
1274             }
1275         }
1276         else
1277         {
1278             if ( !file.mkdirs() )
1279             {
1280                 final String message = "Unable to create directory " + file;
1281                 throw new IOException( message );
1282             }
1283         }
1284     }
1285 
1286     /**
1287      * Recursively delete a directory.
1288      *
1289      * @param directory a directory
1290      * @throws IOException if any
1291      */
1292     public static void deleteDirectory( @Nonnull final String directory )
1293         throws IOException
1294     {
1295         deleteDirectory( new File( directory ) );
1296     }
1297 
1298     /**
1299      * Recursively delete a directory.
1300      *
1301      * @param directory a directory
1302      * @throws IOException if any
1303      */
1304     public static void deleteDirectory( @Nonnull final File directory )
1305         throws IOException
1306     {
1307         if ( !directory.exists() )
1308         {
1309             return;
1310         }
1311 
1312         /* try delete the directory before its contents, which will take
1313          * care of any directories that are really symbolic links.
1314          */
1315         if ( deleteLegacyStyle( directory ) )
1316         {
1317             return;
1318         }
1319 
1320         cleanDirectory( directory );
1321         if ( !deleteLegacyStyle( directory ) )
1322         {
1323             final String message = "Directory " + directory + " unable to be deleted.";
1324             throw new IOException( message );
1325         }
1326     }
1327 
1328     /**
1329      * Clean a directory without deleting it.
1330      *
1331      * @param directory a directory
1332      * @throws IOException if any
1333      */
1334     public static void cleanDirectory( @Nonnull final File directory )
1335         throws IOException
1336     {
1337         if ( !directory.exists() )
1338         {
1339             final String message = directory + " does not exist";
1340             throw new IllegalArgumentException( message );
1341         }
1342 
1343         if ( !directory.isDirectory() )
1344         {
1345             final String message = directory + " is not a directory";
1346             throw new IllegalArgumentException( message );
1347         }
1348 
1349         IOException exception = null;
1350 
1351         final File[] files = directory.listFiles();
1352 
1353         if ( files == null )
1354         {
1355             return;
1356         }
1357 
1358         for ( final File file : files )
1359         {
1360             try
1361             {
1362                 forceDelete( file );
1363             }
1364             catch ( final IOException ioe )
1365             {
1366                 exception = ioe;
1367             }
1368         }
1369 
1370         if ( null != exception )
1371         {
1372             throw exception;
1373         }
1374     }
1375 
1376     /**
1377      * Recursively count size of a directory.
1378      *
1379      * @param directory a directory
1380      * @return size of directory in bytes.
1381      */
1382     public static long sizeOfDirectory( @Nonnull final String directory )
1383     {
1384         return sizeOfDirectory( new File( directory ) );
1385     }
1386 
1387     /**
1388      * Recursively count size of a directory.
1389      *
1390      * @param directory a directory
1391      * @return size of directory in bytes.
1392      */
1393     public static long sizeOfDirectory( @Nonnull final File directory )
1394     {
1395         if ( !directory.exists() )
1396         {
1397             final String message = directory + " does not exist";
1398             throw new IllegalArgumentException( message );
1399         }
1400 
1401         if ( !directory.isDirectory() )
1402         {
1403             final String message = directory + " is not a directory";
1404             throw new IllegalArgumentException( message );
1405         }
1406 
1407         long size = 0;
1408 
1409         final File[] files = directory.listFiles();
1410         if ( files == null )
1411         {
1412             throw new IllegalArgumentException( "Problems reading directory" );
1413         }
1414 
1415         for ( final File file : files )
1416         {
1417             if ( file.isDirectory() )
1418             {
1419                 size += sizeOfDirectory( file );
1420             }
1421             else
1422             {
1423                 size += file.length();
1424             }
1425         }
1426 
1427         return size;
1428     }
1429 
1430     /**
1431      * Return the files contained in the directory, using inclusion and exclusion Ant patterns,
1432      * including the directory name in each of the files
1433      *
1434      * @param directory the directory to scan
1435      * @param includes  the includes pattern, comma separated
1436      * @param excludes  the excludes pattern, comma separated
1437      * @return a list of File objects
1438      * @throws IOException in case of failure.
1439      * @see #getFileNames(File, String, String, boolean)
1440      */
1441     @Nonnull
1442     public static List<File> getFiles( @Nonnull File directory, @Nullable String includes, @Nullable String excludes )
1443         throws IOException
1444     {
1445         return getFiles( directory, includes, excludes, true );
1446     }
1447 
1448     /**
1449      * Return the files contained in the directory, using inclusion and exclusion Ant patterns
1450      *
1451      * @param directory      the directory to scan
1452      * @param includes       the includes pattern, comma separated
1453      * @param excludes       the excludes pattern, comma separated
1454      * @param includeBasedir true to include the base dir in each file
1455      * @return a list of File objects
1456      * @throws IOException in case of failure.
1457      * @see #getFileNames(File, String, String, boolean)
1458      */
1459     @Nonnull
1460     public static List<File> getFiles( @Nonnull File directory, @Nullable String includes, @Nullable String excludes,
1461                                        boolean includeBasedir )
1462         throws IOException
1463     {
1464         List<String> fileNames = getFileNames( directory, includes, excludes, includeBasedir );
1465 
1466         List<File> files = new ArrayList<File>();
1467 
1468         for ( String filename : fileNames )
1469         {
1470             files.add( new File( filename ) );
1471         }
1472 
1473         return files;
1474     }
1475 
1476     /**
1477      * Return a list of files as String depending options.
1478      * This method use case sensitive file name.
1479      *
1480      * @param directory      the directory to scan
1481      * @param includes       the includes pattern, comma separated
1482      * @param excludes       the excludes pattern, comma separated
1483      * @param includeBasedir true to include the base dir in each String of file
1484      * @return a list of files as String
1485      * @throws IOException in case of failure.
1486      */
1487     @Nonnull public static List<String> getFileNames( @Nonnull File directory, @Nullable String includes,
1488                                                       @Nullable String excludes, boolean includeBasedir )
1489         throws IOException
1490     {
1491         return getFileNames( directory, includes, excludes, includeBasedir, true );
1492     }
1493 
1494     /**
1495      * Return a list of files as String depending options.
1496      *
1497      * @param directory       the directory to scan
1498      * @param includes        the includes pattern, comma separated
1499      * @param excludes        the excludes pattern, comma separated
1500      * @param includeBasedir  true to include the base dir in each String of file
1501      * @param isCaseSensitive true if case sensitive
1502      * @return a list of files as String
1503      * @throws IOException
1504      */
1505     @Nonnull private static List<String> getFileNames( @Nonnull File directory, @Nullable String includes,
1506                                                        @Nullable String excludes, boolean includeBasedir,
1507                                                        boolean isCaseSensitive )
1508         throws IOException
1509     {
1510         return getFileAndDirectoryNames( directory, includes, excludes, includeBasedir, isCaseSensitive, true, false );
1511     }
1512 
1513     /**
1514      * Return a list of directories as String depending options.
1515      * This method use case sensitive file name.
1516      *
1517      * @param directory      the directory to scan
1518      * @param includes       the includes pattern, comma separated
1519      * @param excludes       the excludes pattern, comma separated
1520      * @param includeBasedir true to include the base dir in each String of file
1521      * @return a list of directories as String
1522      * @throws IOException in case of failure.
1523      */
1524     @Nonnull public static List<String> getDirectoryNames( @Nonnull File directory, @Nullable String includes,
1525                                                            @Nullable String excludes, boolean includeBasedir )
1526         throws IOException
1527     {
1528         return getDirectoryNames( directory, includes, excludes, includeBasedir, true );
1529     }
1530 
1531     /**
1532      * Return a list of directories as String depending options.
1533      *
1534      * @param directory       the directory to scan
1535      * @param includes        the includes pattern, comma separated
1536      * @param excludes        the excludes pattern, comma separated
1537      * @param includeBasedir  true to include the base dir in each String of file
1538      * @param isCaseSensitive true if case sensitive
1539      * @return a list of directories as String
1540      * @throws IOException in case of failure.
1541      */
1542     @Nonnull public static List<String> getDirectoryNames( @Nonnull File directory, @Nullable String includes,
1543                                                            @Nullable String excludes, boolean includeBasedir,
1544                                                            boolean isCaseSensitive )
1545         throws IOException
1546     {
1547         return getFileAndDirectoryNames( directory, includes, excludes, includeBasedir, isCaseSensitive, false, true );
1548     }
1549 
1550     /**
1551      * Return a list of files as String depending options.
1552      *
1553      * @param directory       the directory to scan
1554      * @param includes        the includes pattern, comma separated
1555      * @param excludes        the excludes pattern, comma separated
1556      * @param includeBasedir  true to include the base dir in each String of file
1557      * @param isCaseSensitive true if case sensitive
1558      * @param getFiles        true if get files
1559      * @param getDirectories  true if get directories
1560      * @return a list of files as String
1561      */
1562     @Nonnull public static List<String> getFileAndDirectoryNames( File directory, @Nullable String includes,
1563                                                                   @Nullable String excludes, boolean includeBasedir,
1564                                                                   boolean isCaseSensitive, boolean getFiles,
1565                                                                   boolean getDirectories )
1566     {
1567         DirectoryScanner scanner = new DirectoryScanner();
1568 
1569         scanner.setBasedir( directory );
1570 
1571         if ( includes != null )
1572         {
1573             scanner.setIncludes( StringUtils.split( includes, "," ) );
1574         }
1575 
1576         if ( excludes != null )
1577         {
1578             scanner.setExcludes( StringUtils.split( excludes, "," ) );
1579         }
1580 
1581         scanner.setCaseSensitive( isCaseSensitive );
1582 
1583         scanner.scan();
1584 
1585         List<String> list = new ArrayList<String>();
1586 
1587         if ( getFiles )
1588         {
1589             String[] files = scanner.getIncludedFiles();
1590 
1591             for ( String file : files )
1592             {
1593                 if ( includeBasedir )
1594                 {
1595                     list.add( directory + FileUtils.FS + file );
1596                 }
1597                 else
1598                 {
1599                     list.add( file );
1600                 }
1601             }
1602         }
1603 
1604         if ( getDirectories )
1605         {
1606             String[] directories = scanner.getIncludedDirectories();
1607 
1608             for ( String directory1 : directories )
1609             {
1610                 if ( includeBasedir )
1611                 {
1612                     list.add( directory + FileUtils.FS + directory1 );
1613                 }
1614                 else
1615                 {
1616                     list.add( directory1 );
1617                 }
1618             }
1619         }
1620 
1621         return list;
1622     }
1623 
1624     /**
1625      * Copy a directory to an other one.
1626      *
1627      * @param sourceDirectory      the source dir
1628      * @param destinationDirectory the target dir
1629      * @throws IOException if any
1630      */
1631     public static void copyDirectory( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory )
1632         throws IOException
1633     {
1634         copyDirectory( sourceDirectory, destinationDirectory, "**", null );
1635     }
1636 
1637     /**
1638      * Copy a directory to an other one.
1639      *
1640      * @param sourceDirectory      the source dir
1641      * @param destinationDirectory the target dir
1642      * @param includes             include pattern
1643      * @param excludes             exlucde pattern
1644      * @throws IOException if any
1645      * @see #getFiles(File, String, String)
1646      */
1647     public static void copyDirectory( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory,
1648                                       @Nullable String includes, @Nullable String excludes )
1649         throws IOException
1650     {
1651         if ( !sourceDirectory.exists() )
1652         {
1653             return;
1654         }
1655 
1656         List<File> files = getFiles( sourceDirectory, includes, excludes );
1657 
1658         for ( File file : files )
1659         {
1660             copyFileToDirectory( file, destinationDirectory );
1661         }
1662     }
1663 
1664     /**
1665      * Copies a entire directory structure.
1666      * <p/>
1667      * Note:
1668      * <ul>
1669      * <li>It will include empty directories.
1670      * <li>The <code>sourceDirectory</code> must exists.
1671      * </ul>
1672      *
1673      * @param sourceDirectory      the source dir
1674      * @param destinationDirectory the target dir
1675      * @throws IOException if any
1676      */
1677     public static void copyDirectoryStructure( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory )
1678         throws IOException
1679     {
1680         copyDirectoryStructure( sourceDirectory, destinationDirectory, destinationDirectory, false );
1681     }
1682 
1683     private static void copyDirectoryStructure( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory,
1684                                                 File rootDestinationDirectory, boolean onlyModifiedFiles )
1685         throws IOException
1686     {
1687         //noinspection ConstantConditions
1688         if ( sourceDirectory == null )
1689         {
1690             throw new IOException( "source directory can't be null." );
1691         }
1692 
1693         //noinspection ConstantConditions
1694         if ( destinationDirectory == null )
1695         {
1696             throw new IOException( "destination directory can't be null." );
1697         }
1698 
1699         if ( sourceDirectory.equals( destinationDirectory ) )
1700         {
1701             throw new IOException( "source and destination are the same directory." );
1702         }
1703 
1704         if ( !sourceDirectory.exists() )
1705         {
1706             throw new IOException( "Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ")." );
1707         }
1708 
1709         File[] files = sourceDirectory.listFiles();
1710 
1711         if ( files == null )
1712         {
1713             return;
1714         }
1715 
1716         String sourcePath = sourceDirectory.getAbsolutePath();
1717 
1718         for ( File file : files )
1719         {
1720             if ( file.equals( rootDestinationDirectory ) )
1721             {
1722                 // We don't copy the destination directory in itself
1723                 continue;
1724             }
1725 
1726             String dest = file.getAbsolutePath();
1727 
1728             dest = dest.substring( sourcePath.length() + 1 );
1729 
1730             File destination = new File( destinationDirectory, dest );
1731 
1732             if ( file.isFile() )
1733             {
1734                 destination = destination.getParentFile();
1735 
1736                 if ( onlyModifiedFiles )
1737                 {
1738                     copyFileToDirectoryIfModified( file, destination );
1739                 }
1740                 else
1741                 {
1742                     copyFileToDirectory( file, destination );
1743                 }
1744             }
1745             else if ( file.isDirectory() )
1746             {
1747                 if ( !destination.exists() && !destination.mkdirs() )
1748                 {
1749                     throw new IOException(
1750                         "Could not create destination directory '" + destination.getAbsolutePath() + "'." );
1751                 }
1752 
1753                 copyDirectoryStructure( file, destination, rootDestinationDirectory, onlyModifiedFiles );
1754             }
1755             else
1756             {
1757                 throw new IOException( "Unknown file type: " + file.getAbsolutePath() );
1758             }
1759         }
1760     }
1761 
1762     /**
1763      * Renames a file, even if that involves crossing file system boundaries.
1764      * <p/>
1765      * <p>This will remove <code>to</code> (if it exists), ensure that
1766      * <code>to</code>'s parent directory exists and move
1767      * <code>from</code>, which involves deleting <code>from</code> as
1768      * well.</p>
1769      *
1770      * @param from the file to move
1771      * @param to   the new file name
1772      * @throws IOException if anything bad happens during this process.
1773      *                     Note that <code>to</code> may have been deleted already when this happens.
1774      */
1775     public static void rename( @Nonnull File from, @Nonnull File to )
1776         throws IOException
1777     {
1778         if ( to.exists() && !deleteLegacyStyle( to ) )
1779         {
1780             throw new IOException( "Failed to delete " + to + " while trying to rename " + from );
1781         }
1782 
1783         File parent = to.getParentFile();
1784         if ( parent != null && !parent.exists() && !parent.mkdirs() )
1785         {
1786             throw new IOException( "Failed to create directory " + parent + " while trying to rename " + from );
1787         }
1788 
1789         if ( !from.renameTo( to ) )
1790         {
1791             copyFile( from, to );
1792             if ( !deleteLegacyStyle( from ) )
1793             {
1794                 throw new IOException( "Failed to delete " + from + " while trying to rename it." );
1795             }
1796         }
1797     }
1798 
1799     /**
1800      * Create a temporary file in a given directory.
1801      * <p/>
1802      * <p>The file denoted by the returned abstract pathname did not
1803      * exist before this method was invoked, any subsequent invocation
1804      * of this method will yield a different file name.</p>
1805      * <p/>
1806      * The filename is prefixNNNNNsuffix where NNNN is a random number
1807      * </p>
1808      * <p>This method is different to {@link File#createTempFile(String, String, File)} of JDK 1.2
1809      * as it doesn't create the file itself.
1810      * It uses the location pointed to by java.io.tmpdir
1811      * when the parentDir attribute is
1812      * null.</p>
1813      * <p>To delete automatically the file created by this method, use the
1814      * {@link File#deleteOnExit()} method.</p>
1815      *
1816      * @param prefix    prefix before the random number
1817      * @param suffix    file extension; include the '.'
1818      * @param parentDir Directory to create the temporary file in <code>-java.io.tmpdir</code>
1819      *                  used if not specificed
1820      * @return a File reference to the new temporary file.
1821      */
1822     public static File createTempFile( @Nonnull String prefix, @Nonnull String suffix, @Nullable File parentDir )
1823     {
1824         File result;
1825         String parent = System.getProperty( "java.io.tmpdir" );
1826         if ( parentDir != null )
1827         {
1828             parent = parentDir.getPath();
1829         }
1830         DecimalFormat fmt = new DecimalFormat( "#####" );
1831         SecureRandom secureRandom = new SecureRandom();
1832         long secureInitializer = secureRandom.nextLong();
1833         Random rand = new Random( secureInitializer + Runtime.getRuntime().freeMemory() );
1834         do
1835         {
1836             result = new File( parent, prefix + fmt.format( positiveRandom( rand ) ) + suffix );
1837         }
1838         while ( result.exists() );
1839 
1840         return result;
1841     }
1842 
1843     private static int positiveRandom( Random rand )
1844     {
1845         int a = rand.nextInt();
1846         while ( a == Integer.MIN_VALUE )
1847         {
1848             a = rand.nextInt();
1849         }
1850         return Math.abs( a );
1851     }
1852 
1853     /**
1854      * <b>If wrappers is null or empty, the file will be copy only if to.lastModified() < from.lastModified()</b>
1855      *
1856      * @param from     the file to copy
1857      * @param to       the destination file
1858      * @param encoding the file output encoding (only if wrappers is not empty)
1859      * @param wrappers array of {@link FilterWrapper}
1860      * @throws IOException if an IO error occurs during copying or filtering
1861      */
1862     public static void copyFile( @Nonnull File from, @Nonnull File to, @Nullable String encoding,
1863                                  @Nullable FilterWrapper... wrappers )
1864         throws IOException
1865     {
1866         copyFile( from, to, encoding, wrappers, false );
1867     }
1868 
1869     /**
1870      * Wrapper class for Filter.
1871      *
1872      */
1873     public abstract static class FilterWrapper
1874     {
1875         /**
1876          * @param fileReader {@link Reader}
1877          * @return The Reader instance.
1878          */
1879         public abstract Reader getReader( Reader fileReader );
1880     }
1881 
1882     /**
1883      * <b>If wrappers is null or empty, the file will be copy only if to.lastModified() < from.lastModified() or if
1884      * overwrite is true</b>
1885      *
1886      * @param from the file to copy
1887      * @param to the destination file
1888      * @param encoding the file output encoding (only if wrappers is not empty)
1889      * @param wrappers array of {@link FilterWrapper}
1890      * @param overwrite if true and f wrappers is null or empty, the file will be copy enven if to.lastModified() <
1891      *            from.lastModified()
1892      * @throws IOException if an IO error occurs during copying or filtering
1893      */
1894     public static void copyFile( @Nonnull File from, @Nonnull File to, @Nullable String encoding,
1895                                  @Nullable FilterWrapper[] wrappers, boolean overwrite )
1896         throws IOException
1897     {
1898         if ( wrappers != null && wrappers.length > 0 )
1899         {
1900             // buffer so it isn't reading a byte at a time!
1901             Reader fileReader = null;
1902             Writer fileWriter = null;
1903             try
1904             {
1905                 if ( encoding == null || encoding.length() < 1 )
1906                 {
1907                     fileReader = new BufferedReader( new FileReader( from ) );
1908                     fileWriter = new FileWriter( to );
1909                 }
1910                 else
1911                 {
1912                     FileInputStream instream = new FileInputStream( from );
1913 
1914                     FileOutputStream outstream = new FileOutputStream( to );
1915 
1916                     fileReader = new BufferedReader( new InputStreamReader( instream, encoding ) );
1917 
1918                     fileWriter = new OutputStreamWriter( outstream, encoding );
1919                 }
1920 
1921                 Reader reader = fileReader;
1922                 for ( FilterWrapper wrapper : wrappers )
1923                 {
1924                     reader = wrapper.getReader( reader );
1925                 }
1926 
1927                 IOUtil.copy( reader, fileWriter );
1928             }
1929             finally
1930             {
1931                 IOUtil.close( fileReader );
1932                 IOUtil.close( fileWriter );
1933             }
1934         }
1935         else
1936         {
1937             if ( to.lastModified() < from.lastModified() || overwrite )
1938             {
1939                 copyFile( from, to );
1940             }
1941         }
1942     }
1943 
1944     /**
1945      * Note: the file content is read with platform encoding
1946      *
1947      * @param file the file
1948      * @return a List containing every every line not starting with # and not empty
1949      * @throws IOException if any
1950      */
1951     @Nonnull public static List<String> loadFile( @Nonnull File file )
1952         throws IOException
1953     {
1954         List<String> lines = new ArrayList<String>();
1955 
1956         if ( file.exists() )
1957         {
1958             FileReader fileReader = new FileReader( file );
1959             try
1960             {
1961                 BufferedReader reader = new BufferedReader( fileReader );
1962 
1963                 String line = reader.readLine();
1964 
1965                 while ( line != null )
1966                 {
1967                     line = line.trim();
1968 
1969                     if ( !line.startsWith( "#" ) && line.length() != 0 )
1970                     {
1971                         lines.add( line );
1972                     }
1973                     line = reader.readLine();
1974                 }
1975 
1976                 reader.close();
1977             }
1978             finally
1979             {
1980                 fileReader.close();
1981             }
1982         }
1983 
1984         return lines;
1985     }
1986 
1987     /**
1988      * For Windows OS, check if the file name contains any of the following characters:
1989      * <code>":", "*", "?", "\"", "<", ">", "|"</code>
1990      *
1991      * @param f not null file
1992      * @return <code>false</code> if the file path contains any of forbidden Windows characters,
1993      * <code>true</code> if the Os is not Windows or if the file path respect the Windows constraints.
1994      * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
1995      */
1996     private static boolean isValidWindowsFileName( @Nonnull File f )
1997     {
1998         if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
1999         {
2000             if ( StringUtils.indexOfAny( f.getName(), INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME ) != -1 )
2001             {
2002                 return false;
2003             }
2004 
2005             if ( f.getParentFile() != null )
2006             {
2007                 return isValidWindowsFileName( f.getParentFile() );
2008             }
2009         }
2010 
2011         return true;
2012     }
2013 
2014     /**
2015      * Checks whether a given file is a symbolic link.
2016      *
2017      * This only works reliably on java7 and higher. For earlier version we use a highly crappy heuristic
2018      * that mostly does not work.
2019      * <p>
2020      * It doesn't really test for symbolic links but whether the canonical and absolute paths of the file are identical
2021      * - this may lead to false positives on some platforms.
2022      * </p>
2023      *
2024      * @param file the file to check
2025      * @throws IOException in case of failure.
2026      * @return true if symbolic link false otherwise.
2027      *
2028      */
2029     public static boolean isSymbolicLink( @Nonnull final File file )
2030         throws IOException
2031     {
2032         if ( Java7Support.isAtLeastJava7() )
2033         {
2034             return Java7Support.isSymLink( file );
2035         }
2036         return isSymbolicLinkLegacy( file );
2037     }
2038 
2039     /**
2040      * Checks whether a given file is a symbolic link.
2041      *
2042      * @param file the file to check
2043      * @return true if and only if we reliably can say this is a symlink. This will
2044      *         always return false for java versions prior to 1.7.
2045      *
2046      * @throws IOException in case of failure.
2047      */
2048     public static boolean isSymbolicLinkForSure( @Nonnull final File file )
2049         throws IOException
2050     {
2051         return Java7Support.isAtLeastJava7() && Java7Support.isSymLink( file );
2052     }
2053 
2054     /**
2055      * Checks whether a given file is a symbolic link.
2056      * <p>
2057      * It doesn't really test for symbolic links but whether the canonical and absolute
2058      * paths of the file are identical - this may lead to false positives on some platforms.
2059      *
2060      * It also returns true for any file that has been reached via a symbolic link,
2061      * if you decide to traverse into the symlink.
2062      *
2063      * As can be seen from the "return" clause of this method, there is really no
2064      * guarantee of any sort from this method. Small wonder this ever got used for
2065      * anything.
2066      * </p>
2067      *
2068      * @param file the file to check
2069      * @return true if the file is a symbolic link or if we're on some crappy os.
2070      *         false if the file is not a symlink or we're not able to detect it.
2071      */
2072     static boolean isSymbolicLinkLegacy( @Nonnull final File file )
2073         throws IOException
2074     {
2075         final File canonical = new File( file.getCanonicalPath() );
2076         return !file.getAbsolutePath().equals( canonical.getPath() );
2077     }
2078 
2079 }