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