001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.io;
018    
019    import java.io.File;
020    import java.io.FileFilter;
021    import java.io.FileInputStream;
022    import java.io.FileNotFoundException;
023    import java.io.FileOutputStream;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.InputStreamReader;
027    import java.io.OutputStream;
028    import java.io.Reader;
029    import java.io.UnsupportedEncodingException;
030    import java.math.BigInteger;
031    import java.net.URL;
032    import java.net.URLConnection;
033    import java.nio.ByteBuffer;
034    import java.nio.channels.FileChannel;
035    import java.nio.charset.Charset;
036    import java.nio.charset.UnsupportedCharsetException;
037    import java.util.ArrayList;
038    import java.util.Collection;
039    import java.util.Date;
040    import java.util.Iterator;
041    import java.util.List;
042    import java.util.zip.CRC32;
043    import java.util.zip.CheckedInputStream;
044    import java.util.zip.Checksum;
045    
046    import org.apache.commons.io.filefilter.DirectoryFileFilter;
047    import org.apache.commons.io.filefilter.FalseFileFilter;
048    import org.apache.commons.io.filefilter.FileFilterUtils;
049    import org.apache.commons.io.filefilter.IOFileFilter;
050    import org.apache.commons.io.filefilter.SuffixFileFilter;
051    import org.apache.commons.io.filefilter.TrueFileFilter;
052    import org.apache.commons.io.output.NullOutputStream;
053    
054    /**
055     * General file manipulation utilities.
056     * <p>
057     * Facilities are provided in the following areas:
058     * <ul>
059     * <li>writing to a file
060     * <li>reading from a file
061     * <li>make a directory including parent directories
062     * <li>copying files and directories
063     * <li>deleting files and directories
064     * <li>converting to and from a URL
065     * <li>listing files and directories by filter and extension
066     * <li>comparing file content
067     * <li>file last changed date
068     * <li>calculating a checksum
069     * </ul>
070     * <p>
071     * Origin of code: Excalibur, Alexandria, Commons-Utils
072     *
073     * @version $Id: FileUtils.java 1307462 2012-03-30 15:13:11Z ggregory $
074     */
075    public class FileUtils {
076    
077        /**
078         * Instances should NOT be constructed in standard programming.
079         */
080        public FileUtils() {
081            super();
082        }
083    
084        /**
085         * The number of bytes in a kilobyte.
086         */
087        public static final long ONE_KB = 1024;
088    
089        /**
090         * The number of bytes in a megabyte.
091         */
092        public static final long ONE_MB = ONE_KB * ONE_KB;
093    
094        /**
095         * The file copy buffer size (30 MB)
096         */
097        private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30;
098    
099        /**
100         * The number of bytes in a gigabyte.
101         */
102        public static final long ONE_GB = ONE_KB * ONE_MB;
103    
104        /**
105         * The number of bytes in a terabyte.
106         */
107        public static final long ONE_TB = ONE_KB * ONE_GB;
108    
109        /**
110         * The number of bytes in a petabyte.
111         */
112        public static final long ONE_PB = ONE_KB * ONE_TB;
113    
114        /**
115         * The number of bytes in an exabyte.
116         */
117        public static final long ONE_EB = ONE_KB * ONE_PB;
118    
119        /**
120         * The number of bytes in a zettabyte.
121         */
122        public static final BigInteger ONE_ZB = BigInteger.valueOf(ONE_KB).multiply(BigInteger.valueOf(ONE_EB));
123    
124        /**
125         * The number of bytes in a yottabyte.
126         */
127        public static final BigInteger ONE_YB = ONE_ZB.multiply(BigInteger.valueOf(ONE_EB));
128    
129        /**
130         * An empty array of type <code>File</code>.
131         */
132        public static final File[] EMPTY_FILE_ARRAY = new File[0];
133    
134        /**
135         * The UTF-8 character set, used to decode octets in URLs.
136         */
137        private static final Charset UTF8 = Charset.forName("UTF-8");
138    
139        //-----------------------------------------------------------------------
140        /**
141         * Construct a file from the set of name elements.
142         * 
143         * @param directory the parent directory
144         * @param names the name elements
145         * @return the file
146         * @since 2.1
147         */
148        public static File getFile(File directory, String... names) {
149            if (directory == null) {
150                throw new NullPointerException("directorydirectory must not be null");
151            }
152            if (names == null) {
153                throw new NullPointerException("names must not be null");
154            }
155            File file = directory;
156            for (String name : names) {
157                file = new File(file, name);
158            }
159            return file;
160        }
161    
162        /**
163         * Construct a file from the set of name elements.
164         * 
165         * @param names the name elements
166         * @return the file
167         * @since 2.1
168         */
169        public static File getFile(String... names) {
170            if (names == null) {
171                throw new NullPointerException("names must not be null");
172            }
173            File file = null;
174            for (String name : names) {
175                if (file == null) {
176                    file = new File(name);
177                } else {
178                    file = new File(file, name);
179                }
180            }
181            return file;
182        }
183    
184        /**
185         * Returns the path to the system temporary directory.
186         * 
187         * @return the path to the system temporary directory.
188         * 
189         * @since 2.0
190         */
191        public static String getTempDirectoryPath() {
192            return System.getProperty("java.io.tmpdir");
193        }
194        
195        /**
196         * Returns a {@link File} representing the system temporary directory.
197         * 
198         * @return the system temporary directory. 
199         * 
200         * @since 2.0
201         */
202        public static File getTempDirectory() {
203            return new File(getTempDirectoryPath());
204        }
205        
206        /**
207         * Returns the path to the user's home directory.
208         * 
209         * @return the path to the user's home directory.
210         * 
211         * @since 2.0
212         */
213        public static String getUserDirectoryPath() {
214            return System.getProperty("user.home");
215        }
216        
217        /**
218         * Returns a {@link File} representing the user's home directory.
219         * 
220         * @return the user's home directory.
221         * 
222         * @since 2.0
223         */
224        public static File getUserDirectory() {
225            return new File(getUserDirectoryPath());
226        }
227        
228        //-----------------------------------------------------------------------
229        /**
230         * Opens a {@link FileInputStream} for the specified file, providing better
231         * error messages than simply calling <code>new FileInputStream(file)</code>.
232         * <p>
233         * At the end of the method either the stream will be successfully opened,
234         * or an exception will have been thrown.
235         * <p>
236         * An exception is thrown if the file does not exist.
237         * An exception is thrown if the file object exists but is a directory.
238         * An exception is thrown if the file exists but cannot be read.
239         * 
240         * @param file  the file to open for input, must not be {@code null}
241         * @return a new {@link FileInputStream} for the specified file
242         * @throws FileNotFoundException if the file does not exist
243         * @throws IOException if the file object is a directory
244         * @throws IOException if the file cannot be read
245         * @since 1.3
246         */
247        public static FileInputStream openInputStream(File file) throws IOException {
248            if (file.exists()) {
249                if (file.isDirectory()) {
250                    throw new IOException("File '" + file + "' exists but is a directory");
251                }
252                if (file.canRead() == false) {
253                    throw new IOException("File '" + file + "' cannot be read");
254                }
255            } else {
256                throw new FileNotFoundException("File '" + file + "' does not exist");
257            }
258            return new FileInputStream(file);
259        }
260    
261        //-----------------------------------------------------------------------
262        /**
263         * Opens a {@link FileOutputStream} for the specified file, checking and
264         * creating the parent directory if it does not exist.
265         * <p>
266         * At the end of the method either the stream will be successfully opened,
267         * or an exception will have been thrown.
268         * <p>
269         * The parent directory will be created if it does not exist.
270         * The file will be created if it does not exist.
271         * An exception is thrown if the file object exists but is a directory.
272         * An exception is thrown if the file exists but cannot be written to.
273         * An exception is thrown if the parent directory cannot be created.
274         * 
275         * @param file  the file to open for output, must not be {@code null}
276         * @return a new {@link FileOutputStream} for the specified file
277         * @throws IOException if the file object is a directory
278         * @throws IOException if the file cannot be written to
279         * @throws IOException if a parent directory needs creating but that fails
280         * @since 1.3
281         */
282        public static FileOutputStream openOutputStream(File file) throws IOException {
283            return openOutputStream(file, false);
284        }
285    
286        /**
287         * Opens a {@link FileOutputStream} for the specified file, checking and
288         * creating the parent directory if it does not exist.
289         * <p>
290         * At the end of the method either the stream will be successfully opened,
291         * or an exception will have been thrown.
292         * <p>
293         * The parent directory will be created if it does not exist.
294         * The file will be created if it does not exist.
295         * An exception is thrown if the file object exists but is a directory.
296         * An exception is thrown if the file exists but cannot be written to.
297         * An exception is thrown if the parent directory cannot be created.
298         * 
299         * @param file  the file to open for output, must not be {@code null}
300         * @param append if {@code true}, then bytes will be added to the
301         * end of the file rather than overwriting
302         * @return a new {@link FileOutputStream} for the specified file
303         * @throws IOException if the file object is a directory
304         * @throws IOException if the file cannot be written to
305         * @throws IOException if a parent directory needs creating but that fails
306         * @since 2.1
307         */
308        public static FileOutputStream openOutputStream(File file, boolean append) throws IOException {
309            if (file.exists()) {
310                if (file.isDirectory()) {
311                    throw new IOException("File '" + file + "' exists but is a directory");
312                }
313                if (file.canWrite() == false) {
314                    throw new IOException("File '" + file + "' cannot be written to");
315                }
316            } else {
317                File parent = file.getParentFile();
318                if (parent != null) {
319                    if (!parent.mkdirs() && !parent.isDirectory()) {
320                        throw new IOException("Directory '" + parent + "' could not be created");
321                    }
322                }
323            }
324            return new FileOutputStream(file, append);
325        }
326    
327        //-----------------------------------------------------------------------
328        /**
329         * Returns a human-readable version of the file size, where the input
330         * represents a specific number of bytes.
331         * 
332         * If the size is over 1GB, the size is returned as the number of whole GB,
333         * i.e. the size is rounded down to the nearest GB boundary.
334         * 
335         * Similarly for the 1MB and 1KB boundaries.
336         *
337         * @param size  the number of bytes
338         * @return a human-readable display value (includes units - GB, MB, KB or bytes)
339         */
340        // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed?
341        public static String byteCountToDisplaySize(long size) {
342            String displaySize;
343    
344            if (size / ONE_EB > 0) {
345                displaySize = String.valueOf(size / ONE_EB) + " EB";
346            } else if (size / ONE_PB > 0) {
347                displaySize = String.valueOf(size / ONE_PB) + " PB";
348            } else if (size / ONE_TB > 0) {
349                displaySize = String.valueOf(size / ONE_TB) + " TB";
350            } else if (size / ONE_GB > 0) {
351                displaySize = String.valueOf(size / ONE_GB) + " GB";
352            } else if (size / ONE_MB > 0) {
353                displaySize = String.valueOf(size / ONE_MB) + " MB";
354            } else if (size / ONE_KB > 0) {
355                displaySize = String.valueOf(size / ONE_KB) + " KB";
356            } else {
357                displaySize = String.valueOf(size) + " bytes";
358            }
359            return displaySize;
360        }
361    
362        //-----------------------------------------------------------------------
363        /**
364         * Implements the same behaviour as the "touch" utility on Unix. It creates
365         * a new file with size 0 or, if the file exists already, it is opened and
366         * closed without modifying it, but updating the file date and time.
367         * <p>
368         * NOTE: As from v1.3, this method throws an IOException if the last
369         * modified date of the file cannot be set. Also, as from v1.3 this method
370         * creates parent directories if they do not exist.
371         *
372         * @param file  the File to touch
373         * @throws IOException If an I/O problem occurs
374         */
375        public static void touch(File file) throws IOException {
376            if (!file.exists()) {
377                OutputStream out = openOutputStream(file);
378                IOUtils.closeQuietly(out);
379            }
380            boolean success = file.setLastModified(System.currentTimeMillis());
381            if (!success) {
382                throw new IOException("Unable to set the last modification time for " + file);
383            }
384        }
385    
386        //-----------------------------------------------------------------------
387        /**
388         * Converts a Collection containing java.io.File instanced into array
389         * representation. This is to account for the difference between
390         * File.listFiles() and FileUtils.listFiles().
391         *
392         * @param files  a Collection containing java.io.File instances
393         * @return an array of java.io.File
394         */
395        public static File[] convertFileCollectionToFileArray(Collection<File> files) {
396             return files.toArray(new File[files.size()]);
397        }
398    
399        //-----------------------------------------------------------------------
400        /**
401         * Finds files within a given directory (and optionally its
402         * subdirectories). All files found are filtered by an IOFileFilter.
403         *
404         * @param files the collection of files found.
405         * @param directory the directory to search in.
406         * @param filter the filter to apply to files and directories.
407         * @param includeSubDirectories indicates if will include the subdirectories themselves
408         */
409        private static void innerListFiles(Collection<File> files, File directory,
410                IOFileFilter filter, boolean includeSubDirectories) {
411            File[] found = directory.listFiles((FileFilter) filter);
412            
413            if (found != null) {
414                for (File file : found) {
415                    if (file.isDirectory()) {
416                        if (includeSubDirectories) {
417                            files.add(file);
418                        }
419                        innerListFiles(files, file, filter, includeSubDirectories);
420                    } else {
421                        files.add(file);
422                    }
423                }
424            }
425        }
426    
427        /**
428         * Finds files within a given directory (and optionally its
429         * subdirectories). All files found are filtered by an IOFileFilter.
430         * <p>
431         * If your search should recurse into subdirectories you can pass in
432         * an IOFileFilter for directories. You don't need to bind a
433         * DirectoryFileFilter (via logical AND) to this filter. This method does
434         * that for you.
435         * <p>
436         * An example: If you want to search through all directories called
437         * "temp" you pass in <code>FileFilterUtils.NameFileFilter("temp")</code>
438         * <p>
439         * Another common usage of this method is find files in a directory
440         * tree but ignoring the directories generated CVS. You can simply pass
441         * in <code>FileFilterUtils.makeCVSAware(null)</code>.
442         *
443         * @param directory  the directory to search in
444         * @param fileFilter  filter to apply when finding files.
445         * @param dirFilter  optional filter to apply when finding subdirectories.
446         * If this parameter is {@code null}, subdirectories will not be included in the
447         * search. Use TrueFileFilter.INSTANCE to match all directories.
448         * @return an collection of java.io.File with the matching files
449         * @see org.apache.commons.io.filefilter.FileFilterUtils
450         * @see org.apache.commons.io.filefilter.NameFileFilter
451         */
452        public static Collection<File> listFiles(
453                File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
454            validateListFilesParameters(directory, fileFilter);
455    
456            IOFileFilter effFileFilter = setUpEffectiveFileFilter(fileFilter);
457            IOFileFilter effDirFilter = setUpEffectiveDirFilter(dirFilter);
458    
459            //Find files
460            Collection<File> files = new java.util.LinkedList<File>();
461            innerListFiles(files, directory,
462                FileFilterUtils.or(effFileFilter, effDirFilter), false);
463            return files;
464        }
465    
466        /**
467         * Validates the given arguments.
468         * <ul>
469         * <li>Throws {@link IllegalArgumentException} if {@code directory} is not a directory</li>
470         * <li>Throws {@link NullPointerException} if {@code fileFilter} is null</li>
471         * </ul>
472         * 
473         * @param directory The File to test
474         * @param fileFilter The IOFileFilter to test
475         */
476        private static void validateListFilesParameters(File directory, IOFileFilter fileFilter) {
477            if (!directory.isDirectory()) {
478                throw new IllegalArgumentException("Parameter 'directory' is not a directory");
479            }
480            if (fileFilter == null) {
481                throw new NullPointerException("Parameter 'fileFilter' is null");
482            }
483        }
484    
485        /**
486         * Returns a filter that accepts files in addition to the {@link File} objects accepted by the given filter.
487         * 
488         * @param fileFilter a base filter to add to
489         * @return a filter that accepts files 
490         */
491        private static IOFileFilter setUpEffectiveFileFilter(IOFileFilter fileFilter) {
492            return FileFilterUtils.and(fileFilter, FileFilterUtils.notFileFilter(DirectoryFileFilter.INSTANCE));
493        }
494    
495        /**
496         * Returns a filter that accepts directories in addition to the {@link File} objects accepted by the given filter.
497         * 
498         * @param dirFilter a base filter to add to
499         * @return a filter that accepts directories 
500         */
501        private static IOFileFilter setUpEffectiveDirFilter(IOFileFilter dirFilter) {
502            return dirFilter == null ? FalseFileFilter.INSTANCE : FileFilterUtils.and(dirFilter,
503                    DirectoryFileFilter.INSTANCE);
504        }
505    
506        /**
507         * Finds files within a given directory (and optionally its
508         * subdirectories). All files found are filtered by an IOFileFilter.
509         * <p>
510         * The resulting collection includes the subdirectories themselves.
511         * <p>
512         * @see org.apache.commons.io.FileUtils#listFiles  
513         *
514         * @param directory  the directory to search in
515         * @param fileFilter  filter to apply when finding files.
516         * @param dirFilter  optional filter to apply when finding subdirectories.
517         * If this parameter is {@code null}, subdirectories will not be included in the
518         * search. Use TrueFileFilter.INSTANCE to match all directories.
519         * @return an collection of java.io.File with the matching files
520         * @see org.apache.commons.io.filefilter.FileFilterUtils
521         * @see org.apache.commons.io.filefilter.NameFileFilter
522         * @since 2.2
523         */
524        public static Collection<File> listFilesAndDirs(
525                File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
526            validateListFilesParameters(directory, fileFilter);
527    
528            IOFileFilter effFileFilter = setUpEffectiveFileFilter(fileFilter);
529            IOFileFilter effDirFilter = setUpEffectiveDirFilter(dirFilter);
530    
531            //Find files
532            Collection<File> files = new java.util.LinkedList<File>();
533            if (directory.isDirectory()) {
534                files.add(directory);
535            }
536            innerListFiles(files, directory,
537                FileFilterUtils.or(effFileFilter, effDirFilter), true);
538            return files;
539        }
540    
541        /**
542         * Allows iteration over the files in given directory (and optionally
543         * its subdirectories).
544         * <p>
545         * All files found are filtered by an IOFileFilter. This method is
546         * based on {@link #listFiles(File, IOFileFilter, IOFileFilter)},
547         * which supports Iterable ('foreach' loop).
548         * <p>
549         * @param directory  the directory to search in
550         * @param fileFilter  filter to apply when finding files.
551         * @param dirFilter  optional filter to apply when finding subdirectories.
552         * If this parameter is {@code null}, subdirectories will not be included in the
553         * search. Use TrueFileFilter.INSTANCE to match all directories.
554         * @return an iterator of java.io.File for the matching files
555         * @see org.apache.commons.io.filefilter.FileFilterUtils
556         * @see org.apache.commons.io.filefilter.NameFileFilter
557         * @since 1.2
558         */
559        public static Iterator<File> iterateFiles(
560                File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
561            return listFiles(directory, fileFilter, dirFilter).iterator();
562        }
563    
564        /**
565         * Allows iteration over the files in given directory (and optionally
566         * its subdirectories).
567         * <p>
568         * All files found are filtered by an IOFileFilter. This method is
569         * based on {@link #listFilesAndDirs(File, IOFileFilter, IOFileFilter)},
570         * which supports Iterable ('foreach' loop).
571         * <p>
572         * The resulting iterator includes the subdirectories themselves.
573         * 
574         * @param directory  the directory to search in
575         * @param fileFilter  filter to apply when finding files.
576         * @param dirFilter  optional filter to apply when finding subdirectories.
577         * If this parameter is {@code null}, subdirectories will not be included in the
578         * search. Use TrueFileFilter.INSTANCE to match all directories.
579         * @return an iterator of java.io.File for the matching files
580         * @see org.apache.commons.io.filefilter.FileFilterUtils
581         * @see org.apache.commons.io.filefilter.NameFileFilter
582         * @since 2.2
583         */
584        public static Iterator<File> iterateFilesAndDirs(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
585            return listFilesAndDirs(directory, fileFilter, dirFilter).iterator();
586        }
587    
588        //-----------------------------------------------------------------------
589        /**
590         * Converts an array of file extensions to suffixes for use
591         * with IOFileFilters.
592         *
593         * @param extensions  an array of extensions. Format: {"java", "xml"}
594         * @return an array of suffixes. Format: {".java", ".xml"}
595         */
596        private static String[] toSuffixes(String[] extensions) {
597            String[] suffixes = new String[extensions.length];
598            for (int i = 0; i < extensions.length; i++) {
599                suffixes[i] = "." + extensions[i];
600            }
601            return suffixes;
602        }
603    
604    
605        /**
606         * Finds files within a given directory (and optionally its subdirectories)
607         * which match an array of extensions.
608         *
609         * @param directory  the directory to search in
610         * @param extensions  an array of extensions, ex. {"java","xml"}. If this
611         * parameter is {@code null}, all files are returned.
612         * @param recursive  if true all subdirectories are searched as well
613         * @return an collection of java.io.File with the matching files
614         */
615        public static Collection<File> listFiles(
616                File directory, String[] extensions, boolean recursive) {
617            IOFileFilter filter;
618            if (extensions == null) {
619                filter = TrueFileFilter.INSTANCE;
620            } else {
621                String[] suffixes = toSuffixes(extensions);
622                filter = new SuffixFileFilter(suffixes);
623            }
624            return listFiles(directory, filter,
625                recursive ? TrueFileFilter.INSTANCE : FalseFileFilter.INSTANCE);
626        }
627    
628        /**
629         * Allows iteration over the files in a given directory (and optionally
630         * its subdirectories) which match an array of extensions. This method
631         * is based on {@link #listFiles(File, String[], boolean)},
632         * which supports Iterable ('foreach' loop).
633         *
634         * @param directory  the directory to search in
635         * @param extensions  an array of extensions, ex. {"java","xml"}. If this
636         * parameter is {@code null}, all files are returned.
637         * @param recursive  if true all subdirectories are searched as well
638         * @return an iterator of java.io.File with the matching files
639         * @since 1.2
640         */
641        public static Iterator<File> iterateFiles(
642                File directory, String[] extensions, boolean recursive) {
643            return listFiles(directory, extensions, recursive).iterator();
644        }
645    
646        //-----------------------------------------------------------------------
647        /**
648         * Compares the contents of two files to determine if they are equal or not.
649         * <p>
650         * This method checks to see if the two files are different lengths
651         * or if they point to the same file, before resorting to byte-by-byte
652         * comparison of the contents.
653         * <p>
654         * Code origin: Avalon
655         *
656         * @param file1  the first file
657         * @param file2  the second file
658         * @return true if the content of the files are equal or they both don't
659         * exist, false otherwise
660         * @throws IOException in case of an I/O error
661         */
662        public static boolean contentEquals(File file1, File file2) throws IOException {
663            boolean file1Exists = file1.exists();
664            if (file1Exists != file2.exists()) {
665                return false;
666            }
667    
668            if (!file1Exists) {
669                // two not existing files are equal
670                return true;
671            }
672    
673            if (file1.isDirectory() || file2.isDirectory()) {
674                // don't want to compare directory contents
675                throw new IOException("Can't compare directories, only files");
676            }
677    
678            if (file1.length() != file2.length()) {
679                // lengths differ, cannot be equal
680                return false;
681            }
682    
683            if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) {
684                // same file
685                return true;
686            }
687    
688            InputStream input1 = null;
689            InputStream input2 = null;
690            try {
691                input1 = new FileInputStream(file1);
692                input2 = new FileInputStream(file2);
693                return IOUtils.contentEquals(input1, input2);
694    
695            } finally {
696                IOUtils.closeQuietly(input1);
697                IOUtils.closeQuietly(input2);
698            }
699        }
700    
701        //-----------------------------------------------------------------------
702        /**
703         * Compares the contents of two files to determine if they are equal or not.
704         * <p>
705         * This method checks to see if the two files point to the same file, 
706         * before resorting to line-by-line comparison of the contents.
707         * <p>
708         *
709         * @param file1  the first file
710         * @param file2  the second file
711         * @param charsetName the character encoding to be used. 
712         *        May be null, in which case the platform default is used
713         * @return true if the content of the files are equal or neither exists,
714         *         false otherwise
715         * @throws IOException in case of an I/O error
716         * @since 2.2
717         * @see IOUtils#contentEqualsIgnoreEOL(Reader, Reader)
718         */
719        public static boolean contentEqualsIgnoreEOL(File file1, File file2, String charsetName) throws IOException {
720            boolean file1Exists = file1.exists();
721            if (file1Exists != file2.exists()) {
722                return false;
723            }
724    
725            if (!file1Exists) {
726                // two not existing files are equal
727                return true;
728            }
729    
730            if (file1.isDirectory() || file2.isDirectory()) {
731                // don't want to compare directory contents
732                throw new IOException("Can't compare directories, only files");
733            }
734    
735            if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) {
736                // same file
737                return true;
738            }
739    
740            Reader input1 = null;
741            Reader input2 = null;
742            try {
743                if (charsetName == null) {
744                    input1 = new InputStreamReader(new FileInputStream(file1));
745                    input2 = new InputStreamReader(new FileInputStream(file2));
746                } else {
747                    input1 = new InputStreamReader(new FileInputStream(file1), charsetName);
748                    input2 = new InputStreamReader(new FileInputStream(file2), charsetName);
749                }
750                return IOUtils.contentEqualsIgnoreEOL(input1, input2);
751    
752            } finally {
753                IOUtils.closeQuietly(input1);
754                IOUtils.closeQuietly(input2);
755            }
756        }
757    
758        //-----------------------------------------------------------------------
759        /**
760         * Convert from a <code>URL</code> to a <code>File</code>.
761         * <p>
762         * From version 1.1 this method will decode the URL.
763         * Syntax such as <code>file:///my%20docs/file.txt</code> will be
764         * correctly decoded to <code>/my docs/file.txt</code>. Starting with version
765         * 1.5, this method uses UTF-8 to decode percent-encoded octets to characters.
766         * Additionally, malformed percent-encoded octets are handled leniently by
767         * passing them through literally.
768         *
769         * @param url  the file URL to convert, {@code null} returns {@code null}
770         * @return the equivalent <code>File</code> object, or {@code null}
771         *  if the URL's protocol is not <code>file</code>
772         */
773        public static File toFile(URL url) {
774            if (url == null || !"file".equalsIgnoreCase(url.getProtocol())) {
775                return null;
776            } else {
777                String filename = url.getFile().replace('/', File.separatorChar);
778                filename = decodeUrl(filename);
779                return new File(filename);
780            }
781        }
782    
783        /**
784         * Decodes the specified URL as per RFC 3986, i.e. transforms
785         * percent-encoded octets to characters by decoding with the UTF-8 character
786         * set. This function is primarily intended for usage with
787         * {@link java.net.URL} which unfortunately does not enforce proper URLs. As
788         * such, this method will leniently accept invalid characters or malformed
789         * percent-encoded octets and simply pass them literally through to the
790         * result string. Except for rare edge cases, this will make unencoded URLs
791         * pass through unaltered.
792         * 
793         * @param url  The URL to decode, may be {@code null}.
794         * @return The decoded URL or {@code null} if the input was
795         *         {@code null}.
796         */
797        static String decodeUrl(String url) {
798            String decoded = url;
799            if (url != null && url.indexOf('%') >= 0) {
800                int n = url.length();
801                StringBuffer buffer = new StringBuffer();
802                ByteBuffer bytes = ByteBuffer.allocate(n);
803                for (int i = 0; i < n;) {
804                    if (url.charAt(i) == '%') {
805                        try {
806                            do {
807                                byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16);
808                                bytes.put(octet);
809                                i += 3;
810                            } while (i < n && url.charAt(i) == '%');
811                            continue;
812                        } catch (RuntimeException e) {
813                            // malformed percent-encoded octet, fall through and
814                            // append characters literally
815                        } finally {
816                            if (bytes.position() > 0) {
817                                bytes.flip();
818                                buffer.append(UTF8.decode(bytes).toString());
819                                bytes.clear();
820                            }
821                        }
822                    }
823                    buffer.append(url.charAt(i++));
824                }
825                decoded = buffer.toString();
826            }
827            return decoded;
828        }
829    
830        /**
831         * Converts each of an array of <code>URL</code> to a <code>File</code>.
832         * <p>
833         * Returns an array of the same size as the input.
834         * If the input is {@code null}, an empty array is returned.
835         * If the input contains {@code null}, the output array contains {@code null} at the same
836         * index.
837         * <p>
838         * This method will decode the URL.
839         * Syntax such as <code>file:///my%20docs/file.txt</code> will be
840         * correctly decoded to <code>/my docs/file.txt</code>.
841         *
842         * @param urls  the file URLs to convert, {@code null} returns empty array
843         * @return a non-{@code null} array of Files matching the input, with a {@code null} item
844         *  if there was a {@code null} at that index in the input array
845         * @throws IllegalArgumentException if any file is not a URL file
846         * @throws IllegalArgumentException if any file is incorrectly encoded
847         * @since 1.1
848         */
849        public static File[] toFiles(URL[] urls) {
850            if (urls == null || urls.length == 0) {
851                return EMPTY_FILE_ARRAY;
852            }
853            File[] files = new File[urls.length];
854            for (int i = 0; i < urls.length; i++) {
855                URL url = urls[i];
856                if (url != null) {
857                    if (url.getProtocol().equals("file") == false) {
858                        throw new IllegalArgumentException(
859                                "URL could not be converted to a File: " + url);
860                    }
861                    files[i] = toFile(url);
862                }
863            }
864            return files;
865        }
866    
867        /**
868         * Converts each of an array of <code>File</code> to a <code>URL</code>.
869         * <p>
870         * Returns an array of the same size as the input.
871         *
872         * @param files  the files to convert
873         * @return an array of URLs matching the input
874         * @throws IOException if a file cannot be converted
875         */
876        public static URL[] toURLs(File[] files) throws IOException {
877            URL[] urls = new URL[files.length];
878    
879            for (int i = 0; i < urls.length; i++) {
880                urls[i] = files[i].toURI().toURL();
881            }
882    
883            return urls;
884        }
885    
886        //-----------------------------------------------------------------------
887        /**
888         * Copies a file to a directory preserving the file date.
889         * <p>
890         * This method copies the contents of the specified source file
891         * to a file of the same name in the specified destination directory.
892         * The destination directory is created if it does not exist.
893         * If the destination file exists, then this method will overwrite it.
894         * <p>
895         * <strong>Note:</strong> This method tries to preserve the file's last
896         * modified date/times using {@link File#setLastModified(long)}, however
897         * it is not guaranteed that the operation will succeed.
898         * If the modification operation fails, no indication is provided.
899         *
900         * @param srcFile  an existing file to copy, must not be {@code null}
901         * @param destDir  the directory to place the copy in, must not be {@code null}
902         *
903         * @throws NullPointerException if source or destination is null
904         * @throws IOException if source or destination is invalid
905         * @throws IOException if an IO error occurs during copying
906         * @see #copyFile(File, File, boolean)
907         */
908        public static void copyFileToDirectory(File srcFile, File destDir) throws IOException {
909            copyFileToDirectory(srcFile, destDir, true);
910        }
911    
912        /**
913         * Copies a file to a directory optionally preserving the file date.
914         * <p>
915         * This method copies the contents of the specified source file
916         * to a file of the same name in the specified destination directory.
917         * The destination directory is created if it does not exist.
918         * If the destination file exists, then this method will overwrite it.
919         * <p>
920         * <strong>Note:</strong> Setting <code>preserveFileDate</code> to
921         * {@code true} tries to preserve the file's last modified
922         * date/times using {@link File#setLastModified(long)}, however it is
923         * not guaranteed that the operation will succeed.
924         * If the modification operation fails, no indication is provided.
925         *
926         * @param srcFile  an existing file to copy, must not be {@code null}
927         * @param destDir  the directory to place the copy in, must not be {@code null}
928         * @param preserveFileDate  true if the file date of the copy
929         *  should be the same as the original
930         *
931         * @throws NullPointerException if source or destination is {@code null}
932         * @throws IOException if source or destination is invalid
933         * @throws IOException if an IO error occurs during copying
934         * @see #copyFile(File, File, boolean)
935         * @since 1.3
936         */
937        public static void copyFileToDirectory(File srcFile, File destDir, boolean preserveFileDate) throws IOException {
938            if (destDir == null) {
939                throw new NullPointerException("Destination must not be null");
940            }
941            if (destDir.exists() && destDir.isDirectory() == false) {
942                throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory");
943            }
944            File destFile = new File(destDir, srcFile.getName());
945            copyFile(srcFile, destFile, preserveFileDate);
946        }
947    
948        /**
949         * Copies a file to a new location preserving the file date.
950         * <p>
951         * This method copies the contents of the specified source file to the
952         * specified destination file. The directory holding the destination file is
953         * created if it does not exist. If the destination file exists, then this
954         * method will overwrite it.
955         * <p>
956         * <strong>Note:</strong> This method tries to preserve the file's last
957         * modified date/times using {@link File#setLastModified(long)}, however
958         * it is not guaranteed that the operation will succeed.
959         * If the modification operation fails, no indication is provided.
960         * 
961         * @param srcFile  an existing file to copy, must not be {@code null}
962         * @param destFile  the new file, must not be {@code null}
963         * 
964         * @throws NullPointerException if source or destination is {@code null}
965         * @throws IOException if source or destination is invalid
966         * @throws IOException if an IO error occurs during copying
967         * @see #copyFileToDirectory(File, File)
968         */
969        public static void copyFile(File srcFile, File destFile) throws IOException {
970            copyFile(srcFile, destFile, true);
971        }
972    
973        /**
974         * Copies a file to a new location.
975         * <p>
976         * This method copies the contents of the specified source file
977         * to the specified destination file.
978         * The directory holding the destination file is created if it does not exist.
979         * If the destination file exists, then this method will overwrite it.
980         * <p>
981         * <strong>Note:</strong> Setting <code>preserveFileDate</code> to
982         * {@code true} tries to preserve the file's last modified
983         * date/times using {@link File#setLastModified(long)}, however it is
984         * not guaranteed that the operation will succeed.
985         * If the modification operation fails, no indication is provided.
986         *
987         * @param srcFile  an existing file to copy, must not be {@code null}
988         * @param destFile  the new file, must not be {@code null}
989         * @param preserveFileDate  true if the file date of the copy
990         *  should be the same as the original
991         *
992         * @throws NullPointerException if source or destination is {@code null}
993         * @throws IOException if source or destination is invalid
994         * @throws IOException if an IO error occurs during copying
995         * @see #copyFileToDirectory(File, File, boolean)
996         */
997        public static void copyFile(File srcFile, File destFile,
998                boolean preserveFileDate) throws IOException {
999            if (srcFile == null) {
1000                throw new NullPointerException("Source must not be null");
1001            }
1002            if (destFile == null) {
1003                throw new NullPointerException("Destination must not be null");
1004            }
1005            if (srcFile.exists() == false) {
1006                throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
1007            }
1008            if (srcFile.isDirectory()) {
1009                throw new IOException("Source '" + srcFile + "' exists but is a directory");
1010            }
1011            if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) {
1012                throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same");
1013            }
1014            File parentFile = destFile.getParentFile();
1015            if (parentFile != null) {
1016                if (!parentFile.mkdirs() && !parentFile.isDirectory()) {
1017                    throw new IOException("Destination '" + parentFile + "' directory cannot be created");
1018                }
1019            }
1020            if (destFile.exists() && destFile.canWrite() == false) {
1021                throw new IOException("Destination '" + destFile + "' exists but is read-only");
1022            }
1023            doCopyFile(srcFile, destFile, preserveFileDate);
1024        }
1025    
1026        /**
1027         * Copy bytes from a <code>File</code> to an <code>OutputStream</code>.
1028         * <p>
1029         * This method buffers the input internally, so there is no need to use a <code>BufferedInputStream</code>.
1030         * </p>
1031         * 
1032         * @param input
1033         *            the <code>File</code> to read from
1034         * @param output
1035         *            the <code>OutputStream</code> to write to
1036         * @return the number of bytes copied
1037         * @throws NullPointerException
1038         *             if the input or output is null
1039         * @throws IOException
1040         *             if an I/O error occurs
1041         * @since 2.1
1042         */
1043        public static long copyFile(File input, OutputStream output) throws IOException {
1044            final FileInputStream fis = new FileInputStream(input);
1045            try {
1046                return IOUtils.copyLarge(fis, output);
1047            } finally {
1048                fis.close();
1049            }
1050        }
1051        
1052        /**
1053         * Internal copy file method.
1054         * 
1055         * @param srcFile  the validated source file, must not be {@code null}
1056         * @param destFile  the validated destination file, must not be {@code null}
1057         * @param preserveFileDate  whether to preserve the file date
1058         * @throws IOException if an error occurs
1059         */
1060        private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException {
1061            if (destFile.exists() && destFile.isDirectory()) {
1062                throw new IOException("Destination '" + destFile + "' exists but is a directory");
1063            }
1064    
1065            FileInputStream fis = null;
1066            FileOutputStream fos = null;
1067            FileChannel input = null;
1068            FileChannel output = null;
1069            try {
1070                fis = new FileInputStream(srcFile);
1071                fos = new FileOutputStream(destFile);
1072                input  = fis.getChannel();
1073                output = fos.getChannel();
1074                long size = input.size();
1075                long pos = 0;
1076                long count = 0;
1077                while (pos < size) {
1078                    count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : size - pos;
1079                    pos += output.transferFrom(input, pos, count);
1080                }
1081            } finally {
1082                IOUtils.closeQuietly(output);
1083                IOUtils.closeQuietly(fos);
1084                IOUtils.closeQuietly(input);
1085                IOUtils.closeQuietly(fis);
1086            }
1087    
1088            if (srcFile.length() != destFile.length()) {
1089                throw new IOException("Failed to copy full contents from '" +
1090                        srcFile + "' to '" + destFile + "'");
1091            }
1092            if (preserveFileDate) {
1093                destFile.setLastModified(srcFile.lastModified());
1094            }
1095        }
1096    
1097        //-----------------------------------------------------------------------
1098        /**
1099         * Copies a directory to within another directory preserving the file dates.
1100         * <p>
1101         * This method copies the source directory and all its contents to a
1102         * directory of the same name in the specified destination directory.
1103         * <p>
1104         * The destination directory is created if it does not exist.
1105         * If the destination directory did exist, then this method merges
1106         * the source with the destination, with the source taking precedence.
1107         * <p>
1108         * <strong>Note:</strong> This method tries to preserve the files' last
1109         * modified date/times using {@link File#setLastModified(long)}, however
1110         * it is not guaranteed that those operations will succeed.
1111         * If the modification operation fails, no indication is provided.
1112         *
1113         * @param srcDir  an existing directory to copy, must not be {@code null}
1114         * @param destDir  the directory to place the copy in, must not be {@code null}
1115         *
1116         * @throws NullPointerException if source or destination is {@code null}
1117         * @throws IOException if source or destination is invalid
1118         * @throws IOException if an IO error occurs during copying
1119         * @since 1.2
1120         */
1121        public static void copyDirectoryToDirectory(File srcDir, File destDir) throws IOException {
1122            if (srcDir == null) {
1123                throw new NullPointerException("Source must not be null");
1124            }
1125            if (srcDir.exists() && srcDir.isDirectory() == false) {
1126                throw new IllegalArgumentException("Source '" + destDir + "' is not a directory");
1127            }
1128            if (destDir == null) {
1129                throw new NullPointerException("Destination must not be null");
1130            }
1131            if (destDir.exists() && destDir.isDirectory() == false) {
1132                throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory");
1133            }
1134            copyDirectory(srcDir, new File(destDir, srcDir.getName()), true);
1135        }
1136    
1137        /**
1138         * Copies a whole directory to a new location preserving the file dates.
1139         * <p>
1140         * This method copies the specified directory and all its child
1141         * directories and files to the specified destination.
1142         * The destination is the new location and name of the directory.
1143         * <p>
1144         * The destination directory is created if it does not exist.
1145         * If the destination directory did exist, then this method merges
1146         * the source with the destination, with the source taking precedence.
1147         * <p>
1148         * <strong>Note:</strong> This method tries to preserve the files' last
1149         * modified date/times using {@link File#setLastModified(long)}, however
1150         * it is not guaranteed that those operations will succeed.
1151         * If the modification operation fails, no indication is provided.
1152         *
1153         * @param srcDir  an existing directory to copy, must not be {@code null}
1154         * @param destDir  the new directory, must not be {@code null}
1155         *
1156         * @throws NullPointerException if source or destination is {@code null}
1157         * @throws IOException if source or destination is invalid
1158         * @throws IOException if an IO error occurs during copying
1159         * @since 1.1
1160         */
1161        public static void copyDirectory(File srcDir, File destDir) throws IOException {
1162            copyDirectory(srcDir, destDir, true);
1163        }
1164    
1165        /**
1166         * Copies a whole directory to a new location.
1167         * <p>
1168         * This method copies the contents of the specified source directory
1169         * to within the specified destination directory.
1170         * <p>
1171         * The destination directory is created if it does not exist.
1172         * If the destination directory did exist, then this method merges
1173         * the source with the destination, with the source taking precedence.
1174         * <p>
1175         * <strong>Note:</strong> Setting <code>preserveFileDate</code> to
1176         * {@code true} tries to preserve the files' last modified
1177         * date/times using {@link File#setLastModified(long)}, however it is
1178         * not guaranteed that those operations will succeed.
1179         * If the modification operation fails, no indication is provided.
1180         *
1181         * @param srcDir  an existing directory to copy, must not be {@code null}
1182         * @param destDir  the new directory, must not be {@code null}
1183         * @param preserveFileDate  true if the file date of the copy
1184         *  should be the same as the original
1185         *
1186         * @throws NullPointerException if source or destination is {@code null}
1187         * @throws IOException if source or destination is invalid
1188         * @throws IOException if an IO error occurs during copying
1189         * @since 1.1
1190         */
1191        public static void copyDirectory(File srcDir, File destDir,
1192                boolean preserveFileDate) throws IOException {
1193            copyDirectory(srcDir, destDir, null, preserveFileDate);
1194        }
1195    
1196        /**
1197         * Copies a filtered directory to a new location preserving the file dates.
1198         * <p>
1199         * This method copies the contents of the specified source directory
1200         * to within the specified destination directory.
1201         * <p>
1202         * The destination directory is created if it does not exist.
1203         * If the destination directory did exist, then this method merges
1204         * the source with the destination, with the source taking precedence.
1205         * <p>
1206         * <strong>Note:</strong> This method tries to preserve the files' last
1207         * modified date/times using {@link File#setLastModified(long)}, however
1208         * it is not guaranteed that those operations will succeed.
1209         * If the modification operation fails, no indication is provided.
1210         *
1211         * <h4>Example: Copy directories only</h4> 
1212         *  <pre>
1213         *  // only copy the directory structure
1214         *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY);
1215         *  </pre>
1216         *
1217         * <h4>Example: Copy directories and txt files</h4>
1218         *  <pre>
1219         *  // Create a filter for ".txt" files
1220         *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
1221         *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
1222         *
1223         *  // Create a filter for either directories or ".txt" files
1224         *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
1225         *
1226         *  // Copy using the filter
1227         *  FileUtils.copyDirectory(srcDir, destDir, filter);
1228         *  </pre>
1229         *
1230         * @param srcDir  an existing directory to copy, must not be {@code null}
1231         * @param destDir  the new directory, must not be {@code null}
1232         * @param filter  the filter to apply, null means copy all directories and files
1233         *  should be the same as the original
1234         *
1235         * @throws NullPointerException if source or destination is {@code null}
1236         * @throws IOException if source or destination is invalid
1237         * @throws IOException if an IO error occurs during copying
1238         * @since 1.4
1239         */
1240        public static void copyDirectory(File srcDir, File destDir,
1241                FileFilter filter) throws IOException {
1242            copyDirectory(srcDir, destDir, filter, true);
1243        }
1244    
1245        /**
1246         * Copies a filtered directory to a new location.
1247         * <p>
1248         * This method copies the contents of the specified source directory
1249         * to within the specified destination directory.
1250         * <p>
1251         * The destination directory is created if it does not exist.
1252         * If the destination directory did exist, then this method merges
1253         * the source with the destination, with the source taking precedence.
1254         * <p>
1255         * <strong>Note:</strong> Setting <code>preserveFileDate</code> to
1256         * {@code true} tries to preserve the files' last modified
1257         * date/times using {@link File#setLastModified(long)}, however it is
1258         * not guaranteed that those operations will succeed.
1259         * If the modification operation fails, no indication is provided.
1260         *
1261         * <h4>Example: Copy directories only</h4> 
1262         *  <pre>
1263         *  // only copy the directory structure
1264         *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
1265         *  </pre>
1266         *
1267         * <h4>Example: Copy directories and txt files</h4>
1268         *  <pre>
1269         *  // Create a filter for ".txt" files
1270         *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
1271         *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
1272         *
1273         *  // Create a filter for either directories or ".txt" files
1274         *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
1275         *
1276         *  // Copy using the filter
1277         *  FileUtils.copyDirectory(srcDir, destDir, filter, false);
1278         *  </pre>
1279         * 
1280         * @param srcDir  an existing directory to copy, must not be {@code null}
1281         * @param destDir  the new directory, must not be {@code null}
1282         * @param filter  the filter to apply, null means copy all directories and files
1283         * @param preserveFileDate  true if the file date of the copy
1284         *  should be the same as the original
1285         *
1286         * @throws NullPointerException if source or destination is {@code null}
1287         * @throws IOException if source or destination is invalid
1288         * @throws IOException if an IO error occurs during copying
1289         * @since 1.4
1290         */
1291        public static void copyDirectory(File srcDir, File destDir,
1292                FileFilter filter, boolean preserveFileDate) throws IOException {
1293            if (srcDir == null) {
1294                throw new NullPointerException("Source must not be null");
1295            }
1296            if (destDir == null) {
1297                throw new NullPointerException("Destination must not be null");
1298            }
1299            if (srcDir.exists() == false) {
1300                throw new FileNotFoundException("Source '" + srcDir + "' does not exist");
1301            }
1302            if (srcDir.isDirectory() == false) {
1303                throw new IOException("Source '" + srcDir + "' exists but is not a directory");
1304            }
1305            if (srcDir.getCanonicalPath().equals(destDir.getCanonicalPath())) {
1306                throw new IOException("Source '" + srcDir + "' and destination '" + destDir + "' are the same");
1307            }
1308    
1309            // Cater for destination being directory within the source directory (see IO-141)
1310            List<String> exclusionList = null;
1311            if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) {
1312                File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
1313                if (srcFiles != null && srcFiles.length > 0) {
1314                    exclusionList = new ArrayList<String>(srcFiles.length);
1315                    for (File srcFile : srcFiles) {
1316                        File copiedFile = new File(destDir, srcFile.getName());
1317                        exclusionList.add(copiedFile.getCanonicalPath());
1318                    }
1319                }
1320            }
1321            doCopyDirectory(srcDir, destDir, filter, preserveFileDate, exclusionList);
1322        }
1323    
1324        /**
1325         * Internal copy directory method.
1326         * 
1327         * @param srcDir  the validated source directory, must not be {@code null}
1328         * @param destDir  the validated destination directory, must not be {@code null}
1329         * @param filter  the filter to apply, null means copy all directories and files
1330         * @param preserveFileDate  whether to preserve the file date
1331         * @param exclusionList  List of files and directories to exclude from the copy, may be null
1332         * @throws IOException if an error occurs
1333         * @since 1.1
1334         */
1335        private static void doCopyDirectory(File srcDir, File destDir, FileFilter filter,
1336                boolean preserveFileDate, List<String> exclusionList) throws IOException {
1337            // recurse
1338            File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
1339            if (srcFiles == null) {  // null if abstract pathname does not denote a directory, or if an I/O error occurs
1340                throw new IOException("Failed to list contents of " + srcDir);
1341            }
1342            if (destDir.exists()) {
1343                if (destDir.isDirectory() == false) {
1344                    throw new IOException("Destination '" + destDir + "' exists but is not a directory");
1345                }
1346            } else {
1347                if (!destDir.mkdirs() && !destDir.isDirectory()) {
1348                    throw new IOException("Destination '" + destDir + "' directory cannot be created");
1349                }
1350            }
1351            if (destDir.canWrite() == false) {
1352                throw new IOException("Destination '" + destDir + "' cannot be written to");
1353            }
1354            for (File srcFile : srcFiles) {
1355                File dstFile = new File(destDir, srcFile.getName());
1356                if (exclusionList == null || !exclusionList.contains(srcFile.getCanonicalPath())) {
1357                    if (srcFile.isDirectory()) {
1358                        doCopyDirectory(srcFile, dstFile, filter, preserveFileDate, exclusionList);
1359                    } else {
1360                        doCopyFile(srcFile, dstFile, preserveFileDate);
1361                    }
1362                }
1363            }
1364    
1365            // Do this last, as the above has probably affected directory metadata
1366            if (preserveFileDate) {
1367                destDir.setLastModified(srcDir.lastModified());
1368            }
1369        }
1370    
1371        //-----------------------------------------------------------------------
1372        /**
1373         * Copies bytes from the URL <code>source</code> to a file
1374         * <code>destination</code>. The directories up to <code>destination</code>
1375         * will be created if they don't already exist. <code>destination</code>
1376         * will be overwritten if it already exists.
1377         * <p>
1378         * Warning: this method does not set a connection or read timeout and thus
1379         * might block forever. Use {@link #copyURLToFile(URL, File, int, int)}
1380         * with reasonable timeouts to prevent this.
1381         *
1382         * @param source  the <code>URL</code> to copy bytes from, must not be {@code null}
1383         * @param destination  the non-directory <code>File</code> to write bytes to
1384         *  (possibly overwriting), must not be {@code null}
1385         * @throws IOException if <code>source</code> URL cannot be opened
1386         * @throws IOException if <code>destination</code> is a directory
1387         * @throws IOException if <code>destination</code> cannot be written
1388         * @throws IOException if <code>destination</code> needs creating but can't be
1389         * @throws IOException if an IO error occurs during copying
1390         */
1391        public static void copyURLToFile(URL source, File destination) throws IOException {
1392            InputStream input = source.openStream();
1393            copyInputStreamToFile(input, destination);
1394        }
1395    
1396        /**
1397         * Copies bytes from the URL <code>source</code> to a file
1398         * <code>destination</code>. The directories up to <code>destination</code>
1399         * will be created if they don't already exist. <code>destination</code>
1400         * will be overwritten if it already exists.
1401         *
1402         * @param source  the <code>URL</code> to copy bytes from, must not be {@code null}
1403         * @param destination  the non-directory <code>File</code> to write bytes to
1404         *  (possibly overwriting), must not be {@code null}
1405         * @param connectionTimeout the number of milliseconds until this method
1406         *  will timeout if no connection could be established to the <code>source</code>
1407         * @param readTimeout the number of milliseconds until this method will
1408         *  timeout if no data could be read from the <code>source</code> 
1409         * @throws IOException if <code>source</code> URL cannot be opened
1410         * @throws IOException if <code>destination</code> is a directory
1411         * @throws IOException if <code>destination</code> cannot be written
1412         * @throws IOException if <code>destination</code> needs creating but can't be
1413         * @throws IOException if an IO error occurs during copying
1414         * @since 2.0
1415         */
1416        public static void copyURLToFile(URL source, File destination,
1417                int connectionTimeout, int readTimeout) throws IOException {
1418            URLConnection connection = source.openConnection();
1419            connection.setConnectTimeout(connectionTimeout);
1420            connection.setReadTimeout(readTimeout);
1421            InputStream input = connection.getInputStream();
1422            copyInputStreamToFile(input, destination);
1423        }
1424    
1425        /**
1426         * Copies bytes from an {@link InputStream} <code>source</code> to a file
1427         * <code>destination</code>. The directories up to <code>destination</code>
1428         * will be created if they don't already exist. <code>destination</code>
1429         * will be overwritten if it already exists.
1430         *
1431         * @param source  the <code>InputStream</code> to copy bytes from, must not be {@code null}
1432         * @param destination  the non-directory <code>File</code> to write bytes to
1433         *  (possibly overwriting), must not be {@code null}
1434         * @throws IOException if <code>destination</code> is a directory
1435         * @throws IOException if <code>destination</code> cannot be written
1436         * @throws IOException if <code>destination</code> needs creating but can't be
1437         * @throws IOException if an IO error occurs during copying
1438         * @since 2.0
1439         */
1440        public static void copyInputStreamToFile(InputStream source, File destination) throws IOException {
1441            try {
1442                FileOutputStream output = openOutputStream(destination);
1443                try {
1444                    IOUtils.copy(source, output);
1445                    output.close(); // don't swallow close Exception if copy completes normally
1446                } finally {
1447                    IOUtils.closeQuietly(output);
1448                }
1449            } finally {
1450                IOUtils.closeQuietly(source);
1451            }
1452        }
1453    
1454        //-----------------------------------------------------------------------
1455        /**
1456         * Deletes a directory recursively. 
1457         *
1458         * @param directory  directory to delete
1459         * @throws IOException in case deletion is unsuccessful
1460         */
1461        public static void deleteDirectory(File directory) throws IOException {
1462            if (!directory.exists()) {
1463                return;
1464            }
1465    
1466            if (!isSymlink(directory)) {
1467                cleanDirectory(directory);
1468            }
1469    
1470            if (!directory.delete()) {
1471                String message =
1472                    "Unable to delete directory " + directory + ".";
1473                throw new IOException(message);
1474            }
1475        }
1476    
1477        /**
1478         * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories.
1479         * <p>
1480         * The difference between File.delete() and this method are:
1481         * <ul>
1482         * <li>A directory to be deleted does not have to be empty.</li>
1483         * <li>No exceptions are thrown when a file or directory cannot be deleted.</li>
1484         * </ul>
1485         *
1486         * @param file  file or directory to delete, can be {@code null}
1487         * @return {@code true} if the file or directory was deleted, otherwise
1488         * {@code false}
1489         *
1490         * @since 1.4
1491         */
1492        public static boolean deleteQuietly(File file) {
1493            if (file == null) {
1494                return false;
1495            }
1496            try {
1497                if (file.isDirectory()) {
1498                    cleanDirectory(file);
1499                }
1500            } catch (Exception ignored) {
1501            }
1502    
1503            try {
1504                return file.delete();
1505            } catch (Exception ignored) {
1506                return false;
1507            }
1508        }
1509    
1510        /**
1511         * Determines whether the {@code parent} directory contains the {@code child} element (a file or directory).
1512         * <p>
1513         * Files are normalized before comparison.
1514         * </p>
1515         * 
1516         * Edge cases:
1517         * <ul>
1518         * <li>A {@code directory} must not be null: if null, throw IllegalArgumentException</li>
1519         * <li>A {@code directory} must be a directory: if not a directory, throw IllegalArgumentException</li>
1520         * <li>A directory does not contain itself: return false</li>
1521         * <li>A null child file is not contained in any parent: return false</li>
1522         * </ul>
1523         * 
1524         * @param directory
1525         *            the file to consider as the parent.
1526         * @param child
1527         *            the file to consider as the child.
1528         * @return true is the candidate leaf is under by the specified composite. False otherwise.
1529         * @throws IOException
1530         *             if an IO error occurs while checking the files.
1531         * @since 2.2
1532         * @see FilenameUtils#directoryContains(String, String)
1533         */
1534        public static boolean directoryContains(final File directory, final File child) throws IOException {
1535            
1536            // Fail fast against NullPointerException
1537            if (directory == null) {
1538                throw new IllegalArgumentException("Directory must not be null");
1539            }
1540        
1541            if (!directory.isDirectory()) {
1542                throw new IllegalArgumentException("Not a directory: " + directory);
1543            }
1544        
1545            if (child == null) {
1546                return false;
1547            }
1548        
1549            if (!directory.exists() || !child.exists()) {
1550                return false;
1551            }
1552        
1553            // Canonicalize paths (normalizes relative paths)
1554            String canonicalParent = directory.getCanonicalPath();
1555            String canonicalChild = child.getCanonicalPath();
1556        
1557            return FilenameUtils.directoryContains(canonicalParent, canonicalChild);
1558        }
1559    
1560        /**
1561         * Cleans a directory without deleting it.
1562         *
1563         * @param directory directory to clean
1564         * @throws IOException in case cleaning is unsuccessful
1565         */
1566        public static void cleanDirectory(File directory) throws IOException {
1567            if (!directory.exists()) {
1568                String message = directory + " does not exist";
1569                throw new IllegalArgumentException(message);
1570            }
1571    
1572            if (!directory.isDirectory()) {
1573                String message = directory + " is not a directory";
1574                throw new IllegalArgumentException(message);
1575            }
1576    
1577            File[] files = directory.listFiles();
1578            if (files == null) {  // null if security restricted
1579                throw new IOException("Failed to list contents of " + directory);
1580            }
1581    
1582            IOException exception = null;
1583            for (File file : files) {
1584                try {
1585                    forceDelete(file);
1586                } catch (IOException ioe) {
1587                    exception = ioe;
1588                }
1589            }
1590    
1591            if (null != exception) {
1592                throw exception;
1593            }
1594        }
1595    
1596        //-----------------------------------------------------------------------
1597        /**
1598         * Waits for NFS to propagate a file creation, imposing a timeout.
1599         * <p>
1600         * This method repeatedly tests {@link File#exists()} until it returns
1601         * true up to the maximum time specified in seconds.
1602         *
1603         * @param file  the file to check, must not be {@code null}
1604         * @param seconds  the maximum time in seconds to wait
1605         * @return true if file exists
1606         * @throws NullPointerException if the file is {@code null}
1607         */
1608        public static boolean waitFor(File file, int seconds) {
1609            int timeout = 0;
1610            int tick = 0;
1611            while (!file.exists()) {
1612                if (tick++ >= 10) {
1613                    tick = 0;
1614                    if (timeout++ > seconds) {
1615                        return false;
1616                    }
1617                }
1618                try {
1619                    Thread.sleep(100);
1620                } catch (InterruptedException ignore) {
1621                    // ignore exception
1622                } catch (Exception ex) {
1623                    break;
1624                }
1625            }
1626            return true;
1627        }
1628    
1629        //-----------------------------------------------------------------------
1630        /**
1631         * Reads the contents of a file into a String.
1632         * The file is always closed.
1633         *
1634         * @param file  the file to read, must not be {@code null}
1635         * @param encoding  the encoding to use, {@code null} means platform default
1636         * @return the file contents, never {@code null}
1637         * @throws IOException in case of an I/O error
1638         * @since 2.3
1639         */
1640        public static String readFileToString(File file, Charset encoding) throws IOException {
1641            InputStream in = null;
1642            try {
1643                in = openInputStream(file);
1644                return IOUtils.toString(in, Charsets.toCharset(encoding));
1645            } finally {
1646                IOUtils.closeQuietly(in);
1647            }
1648        }
1649    
1650        /**
1651         * Reads the contents of a file into a String. The file is always closed.
1652         * 
1653         * @param file
1654         *            the file to read, must not be {@code null}
1655         * @param encoding
1656         *            the encoding to use, {@code null} means platform default
1657         * @return the file contents, never {@code null}
1658         * @throws IOException
1659         *             in case of an I/O error
1660         * @throws UnsupportedCharsetException
1661         *             thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not
1662         *             supported.
1663         * @since 2.3
1664         */
1665        public static String readFileToString(File file, String encoding) throws IOException {
1666            return readFileToString(file, Charsets.toCharset(encoding));
1667        }
1668    
1669    
1670        /**
1671         * Reads the contents of a file into a String using the default encoding for the VM. 
1672         * The file is always closed.
1673         *
1674         * @param file  the file to read, must not be {@code null}
1675         * @return the file contents, never {@code null}
1676         * @throws IOException in case of an I/O error
1677         * @since 1.3.1
1678         */
1679        public static String readFileToString(File file) throws IOException {
1680            return readFileToString(file, Charset.defaultCharset());
1681        }
1682    
1683        /**
1684         * Reads the contents of a file into a byte array.
1685         * The file is always closed.
1686         *
1687         * @param file  the file to read, must not be {@code null}
1688         * @return the file contents, never {@code null}
1689         * @throws IOException in case of an I/O error
1690         * @since 1.1
1691         */
1692        public static byte[] readFileToByteArray(File file) throws IOException {
1693            InputStream in = null;
1694            try {
1695                in = openInputStream(file);
1696                return IOUtils.toByteArray(in, file.length());
1697            } finally {
1698                IOUtils.closeQuietly(in);
1699            }
1700        }
1701    
1702        /**
1703         * Reads the contents of a file line by line to a List of Strings.
1704         * The file is always closed.
1705         *
1706         * @param file  the file to read, must not be {@code null}
1707         * @param encoding  the encoding to use, {@code null} means platform default
1708         * @return the list of Strings representing each line in the file, never {@code null}
1709         * @throws IOException in case of an I/O error
1710         * @since 2.3
1711         */
1712        public static List<String> readLines(File file, Charset encoding) throws IOException {
1713            InputStream in = null;
1714            try {
1715                in = openInputStream(file);
1716                return IOUtils.readLines(in, Charsets.toCharset(encoding));
1717            } finally {
1718                IOUtils.closeQuietly(in);
1719            }
1720        }
1721    
1722        /**
1723         * Reads the contents of a file line by line to a List of Strings. The file is always closed.
1724         * 
1725         * @param file
1726         *            the file to read, must not be {@code null}
1727         * @param encoding
1728         *            the encoding to use, {@code null} means platform default
1729         * @return the list of Strings representing each line in the file, never {@code null}
1730         * @throws IOException
1731         *             in case of an I/O error
1732         * @throws UnsupportedCharsetException
1733         *             thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not
1734         *             supported.
1735         * @since 1.1
1736         */
1737        public static List<String> readLines(File file, String encoding) throws IOException {
1738            return readLines(file, Charsets.toCharset(encoding));
1739        }
1740    
1741        /**
1742         * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM.
1743         * The file is always closed.
1744         *
1745         * @param file  the file to read, must not be {@code null}
1746         * @return the list of Strings representing each line in the file, never {@code null}
1747         * @throws IOException in case of an I/O error
1748         * @since 1.3
1749         */
1750        public static List<String> readLines(File file) throws IOException {
1751            return readLines(file, Charset.defaultCharset());
1752        }
1753    
1754        /**
1755         * Returns an Iterator for the lines in a <code>File</code>.
1756         * <p>
1757         * This method opens an <code>InputStream</code> for the file.
1758         * When you have finished with the iterator you should close the stream
1759         * to free internal resources. This can be done by calling the
1760         * {@link LineIterator#close()} or
1761         * {@link LineIterator#closeQuietly(LineIterator)} method.
1762         * <p>
1763         * The recommended usage pattern is:
1764         * <pre>
1765         * LineIterator it = FileUtils.lineIterator(file, "UTF-8");
1766         * try {
1767         *   while (it.hasNext()) {
1768         *     String line = it.nextLine();
1769         *     /// do something with line
1770         *   }
1771         * } finally {
1772         *   LineIterator.closeQuietly(iterator);
1773         * }
1774         * </pre>
1775         * <p>
1776         * If an exception occurs during the creation of the iterator, the
1777         * underlying stream is closed.
1778         *
1779         * @param file  the file to open for input, must not be {@code null}
1780         * @param encoding  the encoding to use, {@code null} means platform default
1781         * @return an Iterator of the lines in the file, never {@code null}
1782         * @throws IOException in case of an I/O error (file closed)
1783         * @since 1.2
1784         */
1785        public static LineIterator lineIterator(File file, String encoding) throws IOException {
1786            InputStream in = null;
1787            try {
1788                in = openInputStream(file);
1789                return IOUtils.lineIterator(in, encoding);
1790            } catch (IOException ex) {
1791                IOUtils.closeQuietly(in);
1792                throw ex;
1793            } catch (RuntimeException ex) {
1794                IOUtils.closeQuietly(in);
1795                throw ex;
1796            }
1797        }
1798    
1799        /**
1800         * Returns an Iterator for the lines in a <code>File</code> using the default encoding for the VM.
1801         *
1802         * @param file  the file to open for input, must not be {@code null}
1803         * @return an Iterator of the lines in the file, never {@code null}
1804         * @throws IOException in case of an I/O error (file closed)
1805         * @since 1.3
1806         * @see #lineIterator(File, String)
1807         */
1808        public static LineIterator lineIterator(File file) throws IOException {
1809            return lineIterator(file, null);
1810        }
1811    
1812        //-----------------------------------------------------------------------
1813        /**
1814         * Writes a String to a file creating the file if it does not exist.
1815         *
1816         * NOTE: As from v1.3, the parent directories of the file will be created
1817         * if they do not exist.
1818         *
1819         * @param file  the file to write
1820         * @param data  the content to write to the file
1821         * @param encoding  the encoding to use, {@code null} means platform default
1822         * @throws IOException in case of an I/O error
1823         * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
1824         */
1825        public static void writeStringToFile(File file, String data, String encoding) throws IOException {
1826            writeStringToFile(file, data, encoding, false);
1827        }
1828    
1829        /**
1830         * Writes a String to a file creating the file if it does not exist.
1831         *
1832         * @param file  the file to write
1833         * @param data  the content to write to the file
1834         * @param encoding  the encoding to use, {@code null} means platform default
1835         * @param append if {@code true}, then the String will be added to the
1836         * end of the file rather than overwriting
1837         * @throws IOException in case of an I/O error
1838         * @since 2.3
1839         */
1840        public static void writeStringToFile(File file, String data, Charset encoding, boolean append) throws IOException {
1841            OutputStream out = null;
1842            try {
1843                out = openOutputStream(file, append);
1844                IOUtils.write(data, out, encoding);
1845                out.close(); // don't swallow close Exception if copy completes normally
1846            } finally {
1847                IOUtils.closeQuietly(out);
1848            }
1849        }
1850    
1851        /**
1852         * Writes a String to a file creating the file if it does not exist.
1853         *
1854         * @param file  the file to write
1855         * @param data  the content to write to the file
1856         * @param encoding  the encoding to use, {@code null} means platform default
1857         * @param append if {@code true}, then the String will be added to the
1858         * end of the file rather than overwriting
1859         * @throws IOException in case of an I/O error
1860         * @throws UnsupportedCharsetException
1861         *             thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not
1862         *             supported by the VM
1863         * @since 2.1
1864         */
1865        public static void writeStringToFile(File file, String data, String encoding, boolean append) throws IOException {
1866            writeStringToFile(file, data, Charsets.toCharset(encoding), append);
1867        }
1868    
1869        /**
1870         * Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
1871         * 
1872         * @param file  the file to write
1873         * @param data  the content to write to the file
1874         * @throws IOException in case of an I/O error
1875         */
1876        public static void writeStringToFile(File file, String data) throws IOException {
1877            writeStringToFile(file, data, Charset.defaultCharset(), false);
1878        }
1879    
1880        /**
1881         * Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
1882         * 
1883         * @param file  the file to write
1884         * @param data  the content to write to the file
1885         * @param append if {@code true}, then the String will be added to the
1886         * end of the file rather than overwriting
1887         * @throws IOException in case of an I/O error
1888         * @since 2.1
1889         */
1890        public static void writeStringToFile(File file, String data, boolean append) throws IOException {
1891            writeStringToFile(file, data, Charset.defaultCharset(), append);
1892        }
1893    
1894        /**
1895         * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM.
1896         * 
1897         * @param file  the file to write
1898         * @param data  the content to write to the file
1899         * @throws IOException in case of an I/O error
1900         * @since 2.0
1901         */
1902        public static void write(File file, CharSequence data) throws IOException {
1903            write(file, data, Charset.defaultCharset(), false);
1904        }
1905    
1906        /**
1907         * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM.
1908         * 
1909         * @param file  the file to write
1910         * @param data  the content to write to the file
1911         * @param append if {@code true}, then the data will be added to the
1912         * end of the file rather than overwriting
1913         * @throws IOException in case of an I/O error
1914         * @since 2.1
1915         */
1916        public static void write(File file, CharSequence data, boolean append) throws IOException {
1917            write(file, data, Charset.defaultCharset(), append);
1918        }
1919    
1920        /**
1921         * Writes a CharSequence to a file creating the file if it does not exist.
1922         *
1923         * @param file  the file to write
1924         * @param data  the content to write to the file
1925         * @param encoding  the encoding to use, {@code null} means platform default
1926         * @throws IOException in case of an I/O error
1927         * @since 2.3
1928         */
1929        public static void write(File file, CharSequence data, Charset encoding) throws IOException {
1930            write(file, data, encoding, false);
1931        }
1932    
1933        /**
1934         * Writes a CharSequence to a file creating the file if it does not exist.
1935         *
1936         * @param file  the file to write
1937         * @param data  the content to write to the file
1938         * @param encoding  the encoding to use, {@code null} means platform default
1939         * @throws IOException in case of an I/O error
1940         * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
1941         * @since 2.0
1942         */
1943        public static void write(File file, CharSequence data, String encoding) throws IOException {
1944            write(file, data, encoding, false);
1945        }
1946    
1947        /**
1948         * Writes a CharSequence to a file creating the file if it does not exist.
1949         *
1950         * @param file  the file to write
1951         * @param data  the content to write to the file
1952         * @param encoding  the encoding to use, {@code null} means platform default
1953         * @param append if {@code true}, then the data will be added to the
1954         * end of the file rather than overwriting
1955         * @throws IOException in case of an I/O error
1956         * @since IO 2.3
1957         */
1958        public static void write(File file, CharSequence data, Charset encoding, boolean append) throws IOException {
1959            String str = data == null ? null : data.toString();
1960            writeStringToFile(file, str, encoding, append);
1961        }
1962    
1963        /**
1964         * Writes a CharSequence to a file creating the file if it does not exist.
1965         *
1966         * @param file  the file to write
1967         * @param data  the content to write to the file
1968         * @param encoding  the encoding to use, {@code null} means platform default
1969         * @param append if {@code true}, then the data will be added to the
1970         * end of the file rather than overwriting
1971         * @throws IOException in case of an I/O error
1972         * @throws UnsupportedCharsetException
1973         *             thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not
1974         *             supported by the VM
1975         * @since IO 2.1
1976         */
1977        public static void write(File file, CharSequence data, String encoding, boolean append) throws IOException {
1978            write(file, data, Charsets.toCharset(encoding), append);
1979        }
1980    
1981        /**
1982         * Writes a byte array to a file creating the file if it does not exist.
1983         * <p>
1984         * NOTE: As from v1.3, the parent directories of the file will be created
1985         * if they do not exist.
1986         *
1987         * @param file  the file to write to
1988         * @param data  the content to write to the file
1989         * @throws IOException in case of an I/O error
1990         * @since 1.1
1991         */
1992        public static void writeByteArrayToFile(File file, byte[] data) throws IOException {
1993            writeByteArrayToFile(file, data, false);
1994        }
1995    
1996        /**
1997         * Writes a byte array to a file creating the file if it does not exist.
1998         *
1999         * @param file  the file to write to
2000         * @param data  the content to write to the file
2001         * @param append if {@code true}, then bytes will be added to the
2002         * end of the file rather than overwriting
2003         * @throws IOException in case of an I/O error
2004         * @since IO 2.1
2005         */
2006        public static void writeByteArrayToFile(File file, byte[] data, boolean append) throws IOException {
2007            OutputStream out = null;
2008            try {
2009                out = openOutputStream(file, append);
2010                out.write(data);
2011                out.close(); // don't swallow close Exception if copy completes normally
2012            } finally {
2013                IOUtils.closeQuietly(out);
2014            }
2015        }
2016    
2017        /**
2018         * Writes the <code>toString()</code> value of each item in a collection to
2019         * the specified <code>File</code> line by line.
2020         * The specified character encoding and the default line ending will be used.
2021         * <p>
2022         * NOTE: As from v1.3, the parent directories of the file will be created
2023         * if they do not exist.
2024         *
2025         * @param file  the file to write to
2026         * @param encoding  the encoding to use, {@code null} means platform default
2027         * @param lines  the lines to write, {@code null} entries produce blank lines
2028         * @throws IOException in case of an I/O error
2029         * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
2030         * @since 1.1
2031         */
2032        public static void writeLines(File file, String encoding, Collection<?> lines) throws IOException {
2033            writeLines(file, encoding, lines, null, false);
2034        }
2035    
2036        /**
2037         * Writes the <code>toString()</code> value of each item in a collection to
2038         * the specified <code>File</code> line by line, optionally appending.
2039         * The specified character encoding and the default line ending will be used.
2040         *
2041         * @param file  the file to write to
2042         * @param encoding  the encoding to use, {@code null} means platform default
2043         * @param lines  the lines to write, {@code null} entries produce blank lines
2044         * @param append if {@code true}, then the lines will be added to the
2045         * end of the file rather than overwriting
2046         * @throws IOException in case of an I/O error
2047         * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
2048         * @since 2.1
2049         */
2050        public static void writeLines(File file, String encoding, Collection<?> lines, boolean append) throws IOException {
2051            writeLines(file, encoding, lines, null, append);
2052        }
2053    
2054        /**
2055         * Writes the <code>toString()</code> value of each item in a collection to
2056         * the specified <code>File</code> line by line.
2057         * The default VM encoding and the default line ending will be used.
2058         *
2059         * @param file  the file to write to
2060         * @param lines  the lines to write, {@code null} entries produce blank lines
2061         * @throws IOException in case of an I/O error
2062         * @since 1.3
2063         */
2064        public static void writeLines(File file, Collection<?> lines) throws IOException {
2065            writeLines(file, null, lines, null, false);
2066        }
2067        
2068        /**
2069         * Writes the <code>toString()</code> value of each item in a collection to
2070         * the specified <code>File</code> line by line.
2071         * The default VM encoding and the default line ending will be used.
2072         *
2073         * @param file  the file to write to
2074         * @param lines  the lines to write, {@code null} entries produce blank lines
2075         * @param append if {@code true}, then the lines will be added to the
2076         * end of the file rather than overwriting
2077         * @throws IOException in case of an I/O error
2078         * @since 2.1
2079         */
2080        public static void writeLines(File file, Collection<?> lines, boolean append) throws IOException {
2081            writeLines(file, null, lines, null, append);
2082        }
2083    
2084        /**
2085         * Writes the <code>toString()</code> value of each item in a collection to
2086         * the specified <code>File</code> line by line.
2087         * The specified character encoding and the line ending will be used.
2088         * <p>
2089         * NOTE: As from v1.3, the parent directories of the file will be created
2090         * if they do not exist.
2091         *
2092         * @param file  the file to write to
2093         * @param encoding  the encoding to use, {@code null} means platform default
2094         * @param lines  the lines to write, {@code null} entries produce blank lines
2095         * @param lineEnding  the line separator to use, {@code null} is system default
2096         * @throws IOException in case of an I/O error
2097         * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
2098         * @since 1.1
2099         */
2100        public static void writeLines(File file, String encoding, Collection<?> lines, String lineEnding)
2101            throws IOException {
2102            writeLines(file, encoding, lines, lineEnding, false);
2103        }
2104    
2105        /**
2106         * Writes the <code>toString()</code> value of each item in a collection to
2107         * the specified <code>File</code> line by line.
2108         * The specified character encoding and the line ending will be used.
2109         *
2110         * @param file  the file to write to
2111         * @param encoding  the encoding to use, {@code null} means platform default
2112         * @param lines  the lines to write, {@code null} entries produce blank lines
2113         * @param lineEnding  the line separator to use, {@code null} is system default
2114         * @param append if {@code true}, then the lines will be added to the
2115         * end of the file rather than overwriting
2116         * @throws IOException in case of an I/O error
2117         * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
2118         * @since 2.1
2119         */
2120        public static void writeLines(File file, String encoding, Collection<?> lines, String lineEnding, boolean append)
2121            throws IOException {
2122            OutputStream out = null;
2123            try {
2124                out = openOutputStream(file, append);
2125                IOUtils.writeLines(lines, lineEnding, out, encoding);
2126                out.close(); // don't swallow close Exception if copy completes normally
2127            } finally {
2128                IOUtils.closeQuietly(out);
2129            }
2130        }
2131    
2132        /**
2133         * Writes the <code>toString()</code> value of each item in a collection to
2134         * the specified <code>File</code> line by line.
2135         * The default VM encoding and the specified line ending will be used.
2136         *
2137         * @param file  the file to write to
2138         * @param lines  the lines to write, {@code null} entries produce blank lines
2139         * @param lineEnding  the line separator to use, {@code null} is system default
2140         * @throws IOException in case of an I/O error
2141         * @since 1.3
2142         */
2143        public static void writeLines(File file, Collection<?> lines, String lineEnding) throws IOException {
2144            writeLines(file, null, lines, lineEnding, false);
2145        }
2146    
2147        /**
2148         * Writes the <code>toString()</code> value of each item in a collection to
2149         * the specified <code>File</code> line by line.
2150         * The default VM encoding and the specified line ending will be used.
2151         *
2152         * @param file  the file to write to
2153         * @param lines  the lines to write, {@code null} entries produce blank lines
2154         * @param lineEnding  the line separator to use, {@code null} is system default
2155         * @param append if {@code true}, then the lines will be added to the
2156         * end of the file rather than overwriting
2157         * @throws IOException in case of an I/O error
2158         * @since 2.1
2159         */
2160        public static void writeLines(File file, Collection<?> lines, String lineEnding, boolean append)
2161            throws IOException {
2162            writeLines(file, null, lines, lineEnding, append);
2163        }
2164    
2165        //-----------------------------------------------------------------------
2166        /**
2167         * Deletes a file. If file is a directory, delete it and all sub-directories.
2168         * <p>
2169         * The difference between File.delete() and this method are:
2170         * <ul>
2171         * <li>A directory to be deleted does not have to be empty.</li>
2172         * <li>You get exceptions when a file or directory cannot be deleted.
2173         *      (java.io.File methods returns a boolean)</li>
2174         * </ul>
2175         *
2176         * @param file  file or directory to delete, must not be {@code null}
2177         * @throws NullPointerException if the directory is {@code null}
2178         * @throws FileNotFoundException if the file was not found
2179         * @throws IOException in case deletion is unsuccessful
2180         */
2181        public static void forceDelete(File file) throws IOException {
2182            if (file.isDirectory()) {
2183                deleteDirectory(file);
2184            } else {
2185                boolean filePresent = file.exists();
2186                if (!file.delete()) {
2187                    if (!filePresent){
2188                        throw new FileNotFoundException("File does not exist: " + file);
2189                    }
2190                    String message =
2191                        "Unable to delete file: " + file;
2192                    throw new IOException(message);
2193                }
2194            }
2195        }
2196    
2197        /**
2198         * Schedules a file to be deleted when JVM exits.
2199         * If file is directory delete it and all sub-directories.
2200         *
2201         * @param file  file or directory to delete, must not be {@code null}
2202         * @throws NullPointerException if the file is {@code null}
2203         * @throws IOException in case deletion is unsuccessful
2204         */
2205        public static void forceDeleteOnExit(File file) throws IOException {
2206            if (file.isDirectory()) {
2207                deleteDirectoryOnExit(file);
2208            } else {
2209                file.deleteOnExit();
2210            }
2211        }
2212    
2213        /**
2214         * Schedules a directory recursively for deletion on JVM exit.
2215         *
2216         * @param directory  directory to delete, must not be {@code null}
2217         * @throws NullPointerException if the directory is {@code null}
2218         * @throws IOException in case deletion is unsuccessful
2219         */
2220        private static void deleteDirectoryOnExit(File directory) throws IOException {
2221            if (!directory.exists()) {
2222                return;
2223            }
2224    
2225            directory.deleteOnExit();
2226            if (!isSymlink(directory)) {
2227                cleanDirectoryOnExit(directory);
2228            }
2229        }
2230    
2231        /**
2232         * Cleans a directory without deleting it.
2233         *
2234         * @param directory  directory to clean, must not be {@code null}
2235         * @throws NullPointerException if the directory is {@code null}
2236         * @throws IOException in case cleaning is unsuccessful
2237         */
2238        private static void cleanDirectoryOnExit(File directory) throws IOException {
2239            if (!directory.exists()) {
2240                String message = directory + " does not exist";
2241                throw new IllegalArgumentException(message);
2242            }
2243    
2244            if (!directory.isDirectory()) {
2245                String message = directory + " is not a directory";
2246                throw new IllegalArgumentException(message);
2247            }
2248    
2249            File[] files = directory.listFiles();
2250            if (files == null) {  // null if security restricted
2251                throw new IOException("Failed to list contents of " + directory);
2252            }
2253    
2254            IOException exception = null;
2255            for (File file : files) {
2256                try {
2257                    forceDeleteOnExit(file);
2258                } catch (IOException ioe) {
2259                    exception = ioe;
2260                }
2261            }
2262    
2263            if (null != exception) {
2264                throw exception;
2265            }
2266        }
2267    
2268        /**
2269         * Makes a directory, including any necessary but nonexistent parent
2270         * directories. If a file already exists with specified name but it is
2271         * not a directory then an IOException is thrown.
2272         * If the directory cannot be created (or does not already exist)
2273         * then an IOException is thrown.
2274         *
2275         * @param directory  directory to create, must not be {@code null}
2276         * @throws NullPointerException if the directory is {@code null}
2277         * @throws IOException if the directory cannot be created or the file already exists but is not a directory
2278         */
2279        public static void forceMkdir(File directory) throws IOException {
2280            if (directory.exists()) {
2281                if (!directory.isDirectory()) {
2282                    String message =
2283                        "File "
2284                            + directory
2285                            + " exists and is "
2286                            + "not a directory. Unable to create directory.";
2287                    throw new IOException(message);
2288                }
2289            } else {
2290                if (!directory.mkdirs()) {
2291                    // Double-check that some other thread or process hasn't made
2292                    // the directory in the background
2293                    if (!directory.isDirectory())
2294                    {
2295                        String message =
2296                            "Unable to create directory " + directory;
2297                        throw new IOException(message);
2298                    }
2299                }
2300            }
2301        }
2302    
2303        //-----------------------------------------------------------------------
2304        /**
2305         * Returns the size of the specified file or directory. If the provided 
2306         * {@link File} is a regular file, then the file's length is returned.
2307         * If the argument is a directory, then the size of the directory is
2308         * calculated recursively. If a directory or subdirectory is security 
2309         * restricted, its size will not be included.
2310         * 
2311         * @param file the regular file or directory to return the size 
2312         *        of (must not be {@code null}).
2313         * 
2314         * @return the length of the file, or recursive size of the directory, 
2315         *         provided (in bytes).
2316         * 
2317         * @throws NullPointerException if the file is {@code null}
2318         * @throws IllegalArgumentException if the file does not exist.
2319         *         
2320         * @since 2.0
2321         */
2322        public static long sizeOf(File file) {
2323    
2324            if (!file.exists()) {
2325                String message = file + " does not exist";
2326                throw new IllegalArgumentException(message);
2327            }
2328    
2329            if (file.isDirectory()) {
2330                return sizeOfDirectory(file);
2331            } else {
2332                return file.length();
2333            }
2334    
2335        }
2336    
2337        /**
2338         * Counts the size of a directory recursively (sum of the length of all files).
2339         *
2340         * @param directory  directory to inspect, must not be {@code null}
2341         * @return size of directory in bytes, 0 if directory is security restricted
2342         * @throws NullPointerException if the directory is {@code null}
2343         */
2344        public static long sizeOfDirectory(File directory) {
2345            if (!directory.exists()) {
2346                String message = directory + " does not exist";
2347                throw new IllegalArgumentException(message);
2348            }
2349    
2350            if (!directory.isDirectory()) {
2351                String message = directory + " is not a directory";
2352                throw new IllegalArgumentException(message);
2353            }
2354    
2355            long size = 0;
2356    
2357            File[] files = directory.listFiles();
2358            if (files == null) {  // null if security restricted
2359                return 0L;
2360            }
2361            for (File file : files) {
2362                size += sizeOf(file);
2363            }
2364    
2365            return size;
2366        }
2367    
2368        //-----------------------------------------------------------------------
2369        /**
2370         * Tests if the specified <code>File</code> is newer than the reference
2371         * <code>File</code>.
2372         *
2373         * @param file  the <code>File</code> of which the modification date must
2374         * be compared, must not be {@code null}
2375         * @param reference  the <code>File</code> of which the modification date
2376         * is used, must not be {@code null}
2377         * @return true if the <code>File</code> exists and has been modified more
2378         * recently than the reference <code>File</code>
2379         * @throws IllegalArgumentException if the file is {@code null}
2380         * @throws IllegalArgumentException if the reference file is {@code null} or doesn't exist
2381         */
2382         public static boolean isFileNewer(File file, File reference) {
2383            if (reference == null) {
2384                throw new IllegalArgumentException("No specified reference file");
2385            }
2386            if (!reference.exists()) {
2387                throw new IllegalArgumentException("The reference file '"
2388                        + reference + "' doesn't exist");
2389            }
2390            return isFileNewer(file, reference.lastModified());
2391        }
2392    
2393        /**
2394         * Tests if the specified <code>File</code> is newer than the specified
2395         * <code>Date</code>.
2396         * 
2397         * @param file  the <code>File</code> of which the modification date
2398         * must be compared, must not be {@code null}
2399         * @param date  the date reference, must not be {@code null}
2400         * @return true if the <code>File</code> exists and has been modified
2401         * after the given <code>Date</code>.
2402         * @throws IllegalArgumentException if the file is {@code null}
2403         * @throws IllegalArgumentException if the date is {@code null}
2404         */
2405        public static boolean isFileNewer(File file, Date date) {
2406            if (date == null) {
2407                throw new IllegalArgumentException("No specified date");
2408            }
2409            return isFileNewer(file, date.getTime());
2410        }
2411    
2412        /**
2413         * Tests if the specified <code>File</code> is newer than the specified
2414         * time reference.
2415         *
2416         * @param file  the <code>File</code> of which the modification date must
2417         * be compared, must not be {@code null}
2418         * @param timeMillis  the time reference measured in milliseconds since the
2419         * epoch (00:00:00 GMT, January 1, 1970)
2420         * @return true if the <code>File</code> exists and has been modified after
2421         * the given time reference.
2422         * @throws IllegalArgumentException if the file is {@code null}
2423         */
2424         public static boolean isFileNewer(File file, long timeMillis) {
2425            if (file == null) {
2426                throw new IllegalArgumentException("No specified file");
2427            }
2428            if (!file.exists()) {
2429                return false;
2430            }
2431            return file.lastModified() > timeMillis;
2432        }
2433    
2434    
2435        //-----------------------------------------------------------------------
2436        /**
2437         * Tests if the specified <code>File</code> is older than the reference
2438         * <code>File</code>.
2439         *
2440         * @param file  the <code>File</code> of which the modification date must
2441         * be compared, must not be {@code null}
2442         * @param reference  the <code>File</code> of which the modification date
2443         * is used, must not be {@code null}
2444         * @return true if the <code>File</code> exists and has been modified before
2445         * the reference <code>File</code>
2446         * @throws IllegalArgumentException if the file is {@code null}
2447         * @throws IllegalArgumentException if the reference file is {@code null} or doesn't exist
2448         */
2449         public static boolean isFileOlder(File file, File reference) {
2450            if (reference == null) {
2451                throw new IllegalArgumentException("No specified reference file");
2452            }
2453            if (!reference.exists()) {
2454                throw new IllegalArgumentException("The reference file '"
2455                        + reference + "' doesn't exist");
2456            }
2457            return isFileOlder(file, reference.lastModified());
2458        }
2459    
2460        /**
2461         * Tests if the specified <code>File</code> is older than the specified
2462         * <code>Date</code>.
2463         * 
2464         * @param file  the <code>File</code> of which the modification date
2465         * must be compared, must not be {@code null}
2466         * @param date  the date reference, must not be {@code null}
2467         * @return true if the <code>File</code> exists and has been modified
2468         * before the given <code>Date</code>.
2469         * @throws IllegalArgumentException if the file is {@code null}
2470         * @throws IllegalArgumentException if the date is {@code null}
2471         */
2472        public static boolean isFileOlder(File file, Date date) {
2473            if (date == null) {
2474                throw new IllegalArgumentException("No specified date");
2475            }
2476            return isFileOlder(file, date.getTime());
2477        }
2478    
2479        /**
2480         * Tests if the specified <code>File</code> is older than the specified
2481         * time reference.
2482         *
2483         * @param file  the <code>File</code> of which the modification date must
2484         * be compared, must not be {@code null}
2485         * @param timeMillis  the time reference measured in milliseconds since the
2486         * epoch (00:00:00 GMT, January 1, 1970)
2487         * @return true if the <code>File</code> exists and has been modified before
2488         * the given time reference.
2489         * @throws IllegalArgumentException if the file is {@code null}
2490         */
2491         public static boolean isFileOlder(File file, long timeMillis) {
2492            if (file == null) {
2493                throw new IllegalArgumentException("No specified file");
2494            }
2495            if (!file.exists()) {
2496                return false;
2497            }
2498            return file.lastModified() < timeMillis;
2499        }
2500    
2501        //-----------------------------------------------------------------------
2502        /**
2503         * Computes the checksum of a file using the CRC32 checksum routine.
2504         * The value of the checksum is returned.
2505         *
2506         * @param file  the file to checksum, must not be {@code null}
2507         * @return the checksum value
2508         * @throws NullPointerException if the file or checksum is {@code null}
2509         * @throws IllegalArgumentException if the file is a directory
2510         * @throws IOException if an IO error occurs reading the file
2511         * @since 1.3
2512         */
2513        public static long checksumCRC32(File file) throws IOException {
2514            CRC32 crc = new CRC32();
2515            checksum(file, crc);
2516            return crc.getValue();
2517        }
2518    
2519        /**
2520         * Computes the checksum of a file using the specified checksum object.
2521         * Multiple files may be checked using one <code>Checksum</code> instance
2522         * if desired simply by reusing the same checksum object.
2523         * For example:
2524         * <pre>
2525         *   long csum = FileUtils.checksum(file, new CRC32()).getValue();
2526         * </pre>
2527         *
2528         * @param file  the file to checksum, must not be {@code null}
2529         * @param checksum  the checksum object to be used, must not be {@code null}
2530         * @return the checksum specified, updated with the content of the file
2531         * @throws NullPointerException if the file or checksum is {@code null}
2532         * @throws IllegalArgumentException if the file is a directory
2533         * @throws IOException if an IO error occurs reading the file
2534         * @since 1.3
2535         */
2536        public static Checksum checksum(File file, Checksum checksum) throws IOException {
2537            if (file.isDirectory()) {
2538                throw new IllegalArgumentException("Checksums can't be computed on directories");
2539            }
2540            InputStream in = null;
2541            try {
2542                in = new CheckedInputStream(new FileInputStream(file), checksum);
2543                IOUtils.copy(in, new NullOutputStream());
2544            } finally {
2545                IOUtils.closeQuietly(in);
2546            }
2547            return checksum;
2548        }
2549    
2550        /**
2551         * Moves a directory.
2552         * <p>
2553         * When the destination directory is on another file system, do a "copy and delete".
2554         *
2555         * @param srcDir the directory to be moved
2556         * @param destDir the destination directory
2557         * @throws NullPointerException if source or destination is {@code null}
2558         * @throws FileExistsException if the destination directory exists
2559         * @throws IOException if source or destination is invalid
2560         * @throws IOException if an IO error occurs moving the file
2561         * @since 1.4
2562         */
2563        public static void moveDirectory(File srcDir, File destDir) throws IOException {
2564            if (srcDir == null) {
2565                throw new NullPointerException("Source must not be null");
2566            }
2567            if (destDir == null) {
2568                throw new NullPointerException("Destination must not be null");
2569            }
2570            if (!srcDir.exists()) {
2571                throw new FileNotFoundException("Source '" + srcDir + "' does not exist");
2572            }
2573            if (!srcDir.isDirectory()) {
2574                throw new IOException("Source '" + srcDir + "' is not a directory");
2575            }
2576            if (destDir.exists()) {
2577                throw new FileExistsException("Destination '" + destDir + "' already exists");
2578            }
2579            boolean rename = srcDir.renameTo(destDir);
2580            if (!rename) {
2581                if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) {
2582                    throw new IOException("Cannot move directory: "+srcDir+" to a subdirectory of itself: "+destDir);
2583                }
2584                copyDirectory( srcDir, destDir );
2585                deleteDirectory( srcDir );
2586                if (srcDir.exists()) {
2587                    throw new IOException("Failed to delete original directory '" + srcDir +
2588                            "' after copy to '" + destDir + "'");
2589                }
2590            }
2591        }
2592    
2593        /**
2594         * Moves a directory to another directory.
2595         *
2596         * @param src the file to be moved
2597         * @param destDir the destination file
2598         * @param createDestDir If {@code true} create the destination directory,
2599         * otherwise if {@code false} throw an IOException
2600         * @throws NullPointerException if source or destination is {@code null}
2601         * @throws FileExistsException if the directory exists in the destination directory
2602         * @throws IOException if source or destination is invalid
2603         * @throws IOException if an IO error occurs moving the file
2604         * @since 1.4
2605         */
2606        public static void moveDirectoryToDirectory(File src, File destDir, boolean createDestDir) throws IOException {
2607            if (src == null) {
2608                throw new NullPointerException("Source must not be null");
2609            }
2610            if (destDir == null) {
2611                throw new NullPointerException("Destination directory must not be null");
2612            }
2613            if (!destDir.exists() && createDestDir) {
2614                destDir.mkdirs();
2615            }
2616            if (!destDir.exists()) {
2617                throw new FileNotFoundException("Destination directory '" + destDir +
2618                        "' does not exist [createDestDir=" + createDestDir +"]");
2619            }
2620            if (!destDir.isDirectory()) {
2621                throw new IOException("Destination '" + destDir + "' is not a directory");
2622            }
2623            moveDirectory(src, new File(destDir, src.getName()));
2624        
2625        }
2626    
2627        /**
2628         * Moves a file.
2629         * <p>
2630         * When the destination file is on another file system, do a "copy and delete".
2631         *
2632         * @param srcFile the file to be moved
2633         * @param destFile the destination file
2634         * @throws NullPointerException if source or destination is {@code null}
2635         * @throws FileExistsException if the destination file exists
2636         * @throws IOException if source or destination is invalid
2637         * @throws IOException if an IO error occurs moving the file
2638         * @since 1.4
2639         */
2640        public static void moveFile(File srcFile, File destFile) throws IOException {
2641            if (srcFile == null) {
2642                throw new NullPointerException("Source must not be null");
2643            }
2644            if (destFile == null) {
2645                throw new NullPointerException("Destination must not be null");
2646            }
2647            if (!srcFile.exists()) {
2648                throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
2649            }
2650            if (srcFile.isDirectory()) {
2651                throw new IOException("Source '" + srcFile + "' is a directory");
2652            }
2653            if (destFile.exists()) {
2654                throw new FileExistsException("Destination '" + destFile + "' already exists");
2655            }
2656            if (destFile.isDirectory()) {
2657                throw new IOException("Destination '" + destFile + "' is a directory");
2658            }
2659            boolean rename = srcFile.renameTo(destFile);
2660            if (!rename) {
2661                copyFile( srcFile, destFile );
2662                if (!srcFile.delete()) {
2663                    FileUtils.deleteQuietly(destFile);
2664                    throw new IOException("Failed to delete original file '" + srcFile +
2665                            "' after copy to '" + destFile + "'");
2666                }
2667            }
2668        }
2669    
2670        /**
2671         * Moves a file to a directory.
2672         *
2673         * @param srcFile the file to be moved
2674         * @param destDir the destination file
2675         * @param createDestDir If {@code true} create the destination directory,
2676         * otherwise if {@code false} throw an IOException
2677         * @throws NullPointerException if source or destination is {@code null}
2678         * @throws FileExistsException if the destination file exists
2679         * @throws IOException if source or destination is invalid
2680         * @throws IOException if an IO error occurs moving the file
2681         * @since 1.4
2682         */
2683        public static void moveFileToDirectory(File srcFile, File destDir, boolean createDestDir) throws IOException {
2684            if (srcFile == null) {
2685                throw new NullPointerException("Source must not be null");
2686            }
2687            if (destDir == null) {
2688                throw new NullPointerException("Destination directory must not be null");
2689            }
2690            if (!destDir.exists() && createDestDir) {
2691                destDir.mkdirs();
2692            }
2693            if (!destDir.exists()) {
2694                throw new FileNotFoundException("Destination directory '" + destDir +
2695                        "' does not exist [createDestDir=" + createDestDir +"]");
2696            }
2697            if (!destDir.isDirectory()) {
2698                throw new IOException("Destination '" + destDir + "' is not a directory");
2699            }
2700            moveFile(srcFile, new File(destDir, srcFile.getName()));
2701        }
2702    
2703        /**
2704         * Moves a file or directory to the destination directory.
2705         * <p>
2706         * When the destination is on another file system, do a "copy and delete".
2707         *
2708         * @param src the file or directory to be moved
2709         * @param destDir the destination directory 
2710         * @param createDestDir If {@code true} create the destination directory,
2711         * otherwise if {@code false} throw an IOException
2712         * @throws NullPointerException if source or destination is {@code null}
2713         * @throws FileExistsException if the directory or file exists in the destination directory
2714         * @throws IOException if source or destination is invalid
2715         * @throws IOException if an IO error occurs moving the file
2716         * @since 1.4
2717         */
2718        public static void moveToDirectory(File src, File destDir, boolean createDestDir) throws IOException {
2719            if (src == null) {
2720                throw new NullPointerException("Source must not be null");
2721            }
2722            if (destDir == null) {
2723                throw new NullPointerException("Destination must not be null");
2724            }
2725            if (!src.exists()) {
2726                throw new FileNotFoundException("Source '" + src + "' does not exist");
2727            }
2728            if (src.isDirectory()) {
2729                moveDirectoryToDirectory(src, destDir, createDestDir);
2730            } else {
2731                moveFileToDirectory(src, destDir, createDestDir);
2732            }
2733        }
2734    
2735        /**
2736         * Determines whether the specified file is a Symbolic Link rather than an actual file.
2737         * <p>
2738         * Will not return true if there is a Symbolic Link anywhere in the path,
2739         * only if the specific file is.
2740         * <p>
2741         * <b>Note:</b> the current implementation always returns {@code false} if the system
2742         * is detected as Windows using {@link FilenameUtils#isSystemWindows()}
2743         * 
2744         * @param file the file to check
2745         * @return true if the file is a Symbolic Link
2746         * @throws IOException if an IO error occurs while checking the file
2747         * @since 2.0
2748         */
2749        public static boolean isSymlink(File file) throws IOException {
2750            if (file == null) {
2751                throw new NullPointerException("File must not be null");
2752            }
2753            if (FilenameUtils.isSystemWindows()) {
2754                return false;
2755            }
2756            File fileInCanonicalDir = null;
2757            if (file.getParent() == null) {
2758                fileInCanonicalDir = file;
2759            } else {
2760                File canonicalDir = file.getParentFile().getCanonicalFile();
2761                fileInCanonicalDir = new File(canonicalDir, file.getName());
2762            }
2763            
2764            if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) {
2765                return false;
2766            } else {
2767                return true;
2768            }
2769        }
2770    
2771    }