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    }