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