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