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