1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17package org.apache.commons.io;
18
19import com.google.common.annotations.VisibleForTesting;
20
21import java.io.File;
22import java.io.FileFilter;
23import java.io.FileInputStream;
24import java.io.FileNotFoundException;
25import java.io.FileOutputStream;
26import java.io.IOException;
27import java.io.InputStream;
28import java.io.OutputStream;
29import java.net.URL;
30import java.util.ArrayList;
31import java.util.Collection;
32import java.util.Date;
33import java.util.Iterator;
34import java.util.List;
35import java.util.zip.CRC32;
36import java.util.zip.CheckedInputStream;
37import java.util.zip.Checksum;
38
39import org.apache.commons.io.filefilter.DirectoryFileFilter;
40import org.apache.commons.io.filefilter.FalseFileFilter;
41import org.apache.commons.io.filefilter.FileFilterUtils;
42import org.apache.commons.io.filefilter.IOFileFilter;
43import org.apache.commons.io.filefilter.SuffixFileFilter;
44import org.apache.commons.io.filefilter.TrueFileFilter;
45import org.apache.commons.io.output.NullOutputStream;
46
47/**
48 * General file manipulation utilities.
49 * <p>
50 * Facilities are provided in the following areas:
51 * <ul>
52 * <li>writing to a file
53 * <li>reading from a file
54 * <li>make a directory including parent directories
55 * <li>copying files and directories
56 * <li>deleting files and directories
57 * <li>converting to and from a URL
58 * <li>listing files and directories by filter and extension
59 * <li>comparing file content
60 * <li>file last changed date
61 * <li>calculating a checksum
62 * </ul>
63 * <p>
64 * Origin of code: Excalibur, Alexandria, Commons-Utils
65 *
66 * @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A>
67 * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
68 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
69 * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph.Reck</a>
70 * @author <a href="mailto:peter@apache.org">Peter Donald</a>
71 * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>
72 * @author Matthew Hawthorne
73 * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
74 * @author Stephen Colebourne
75 * @author Ian Springer
76 * @author Chris Eldredge
77 * @author Jim Harrington
78 * @author Niall Pemberton
79 * @author Sandy McArthur
80 * @version $Id: FileUtils.java 610810 2008-01-10 15:04:49Z niallp $
81 */
82@VisibleForTesting
83public class FileUtils {
84
85    /**
86     * Instances should NOT be constructed in standard programming.
87     */
88    public FileUtils() {
89        super();
90    }
91
92    /**
93     * The number of bytes in a kilobyte.
94     */
95    public static final long ONE_KB = 1024;
96
97    /**
98     * The number of bytes in a megabyte.
99     */
100    public static final long ONE_MB = ONE_KB * ONE_KB;
101
102    /**
103     * The number of bytes in a gigabyte.
104     */
105    public static final long ONE_GB = ONE_KB * ONE_MB;
106
107    /**
108     * An empty array of type <code>File</code>.
109     */
110    public static final File[] EMPTY_FILE_ARRAY = new File[0];
111
112    //-----------------------------------------------------------------------
113    /**
114     * Opens a {@link FileInputStream} for the specified file, providing better
115     * error messages than simply calling <code>new FileInputStream(file)</code>.
116     * <p>
117     * At the end of the method either the stream will be successfully opened,
118     * or an exception will have been thrown.
119     * <p>
120     * An exception is thrown if the file does not exist.
121     * An exception is thrown if the file object exists but is a directory.
122     * An exception is thrown if the file exists but cannot be read.
123     *
124     * @param file  the file to open for input, must not be <code>null</code>
125     * @return a new {@link FileInputStream} for the specified file
126     * @throws FileNotFoundException if the file does not exist
127     * @throws IOException if the file object is a directory
128     * @throws IOException if the file cannot be read
129     * @since Commons IO 1.3
130     */
131    public static FileInputStream openInputStream(File file) throws IOException {
132        if (file.exists()) {
133            if (file.isDirectory()) {
134                throw new IOException("File '" + file + "' exists but is a directory");
135            }
136            if (file.canRead() == false) {
137                throw new IOException("File '" + file + "' cannot be read");
138            }
139        } else {
140            throw new FileNotFoundException("File '" + file + "' does not exist");
141        }
142        return new FileInputStream(file);
143    }
144
145    //-----------------------------------------------------------------------
146    /**
147     * Opens a {@link FileOutputStream} for the specified file, checking and
148     * creating the parent directory if it does not exist.
149     * <p>
150     * At the end of the method either the stream will be successfully opened,
151     * or an exception will have been thrown.
152     * <p>
153     * The parent directory will be created if it does not exist.
154     * The file will be created if it does not exist.
155     * An exception is thrown if the file object exists but is a directory.
156     * An exception is thrown if the file exists but cannot be written to.
157     * An exception is thrown if the parent directory cannot be created.
158     *
159     * @param file  the file to open for output, must not be <code>null</code>
160     * @return a new {@link FileOutputStream} for the specified file
161     * @throws IOException if the file object is a directory
162     * @throws IOException if the file cannot be written to
163     * @throws IOException if a parent directory needs creating but that fails
164     * @since Commons IO 1.3
165     */
166    public static FileOutputStream openOutputStream(File file) throws IOException {
167        if (file.exists()) {
168            if (file.isDirectory()) {
169                throw new IOException("File '" + file + "' exists but is a directory");
170            }
171            if (file.canWrite() == false) {
172                throw new IOException("File '" + file + "' cannot be written to");
173            }
174        } else {
175            File parent = file.getParentFile();
176            if (parent != null && parent.exists() == false) {
177                if (parent.mkdirs() == false) {
178                    throw new IOException("File '" + file + "' could not be created");
179                }
180            }
181        }
182        return new FileOutputStream(file);
183    }
184
185    //-----------------------------------------------------------------------
186    /**
187     * Returns a human-readable version of the file size, where the input
188     * represents a specific number of bytes.
189     *
190     * @param size  the number of bytes
191     * @return a human-readable display value (includes units)
192     */
193    public static String byteCountToDisplaySize(long size) {
194        String displaySize;
195
196        if (size / ONE_GB > 0) {
197            displaySize = String.valueOf(size / ONE_GB) + " GB";
198        } else if (size / ONE_MB > 0) {
199            displaySize = String.valueOf(size / ONE_MB) + " MB";
200        } else if (size / ONE_KB > 0) {
201            displaySize = String.valueOf(size / ONE_KB) + " KB";
202        } else {
203            displaySize = String.valueOf(size) + " bytes";
204        }
205        return displaySize;
206    }
207
208    //-----------------------------------------------------------------------
209    /**
210     * Implements the same behaviour as the "touch" utility on Unix. It creates
211     * a new file with size 0 or, if the file exists already, it is opened and
212     * closed without modifying it, but updating the file date and time.
213     * <p>
214     * NOTE: As from v1.3, this method throws an IOException if the last
215     * modified date of the file cannot be set. Also, as from v1.3 this method
216     * creates parent directories if they do not exist.
217     *
218     * @param file  the File to touch
219     * @throws IOException If an I/O problem occurs
220     */
221    public static void touch(File file) throws IOException {
222        if (!file.exists()) {
223            OutputStream out = openOutputStream(file);
224            IOUtils.closeQuietly(out);
225        }
226        boolean success = file.setLastModified(System.currentTimeMillis());
227        if (!success) {
228            throw new IOException("Unable to set the last modification time for " + file);
229        }
230    }
231
232    //-----------------------------------------------------------------------
233    /**
234     * Converts a Collection containing java.io.File instanced into array
235     * representation. This is to account for the difference between
236     * File.listFiles() and FileUtils.listFiles().
237     *
238     * @param files  a Collection containing java.io.File instances
239     * @return an array of java.io.File
240     */
241    public static File[] convertFileCollectionToFileArray(Collection<File> files) {
242         return files.toArray(new File[files.size()]);
243    }
244
245    //-----------------------------------------------------------------------
246    /**
247     * Finds files within a given directory (and optionally its
248     * subdirectories). All files found are filtered by an IOFileFilter.
249     *
250     * @param files the collection of files found.
251     * @param directory the directory to search in.
252     * @param filter the filter to apply to files and directories.
253     */
254    private static void innerListFiles(Collection<File> files, File directory,
255            IOFileFilter filter) {
256        File[] found = directory.listFiles((FileFilter) filter);
257        if (found != null) {
258            for (int i = 0; i < found.length; i++) {
259                if (found[i].isDirectory()) {
260                    innerListFiles(files, found[i], filter);
261                } else {
262                    files.add(found[i]);
263                }
264            }
265        }
266    }
267
268    /**
269     * Finds files within a given directory (and optionally its
270     * subdirectories). All files found are filtered by an IOFileFilter.
271     * <p>
272     * If your search should recurse into subdirectories you can pass in
273     * an IOFileFilter for directories. You don't need to bind a
274     * DirectoryFileFilter (via logical AND) to this filter. This method does
275     * that for you.
276     * <p>
277     * An example: If you want to search through all directories called
278     * "temp" you pass in <code>FileFilterUtils.NameFileFilter("temp")</code>
279     * <p>
280     * Another common usage of this method is find files in a directory
281     * tree but ignoring the directories generated CVS. You can simply pass
282     * in <code>FileFilterUtils.makeCVSAware(null)</code>.
283     *
284     * @param directory  the directory to search in
285     * @param fileFilter  filter to apply when finding files.
286     * @param dirFilter  optional filter to apply when finding subdirectories.
287     * If this parameter is <code>null</code>, subdirectories will not be included in the
288     * search. Use TrueFileFilter.INSTANCE to match all directories.
289     * @return an collection of java.io.File with the matching files
290     * @see org.apache.commons.io.filefilter.FileFilterUtils
291     * @see org.apache.commons.io.filefilter.NameFileFilter
292     */
293    public static Collection<File> listFiles(
294            File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
295        if (!directory.isDirectory()) {
296            throw new IllegalArgumentException(
297                    "Parameter 'directory' is not a directory");
298        }
299        if (fileFilter == null) {
300            throw new NullPointerException("Parameter 'fileFilter' is null");
301        }
302
303        //Setup effective file filter
304        IOFileFilter effFileFilter = FileFilterUtils.andFileFilter(fileFilter,
305            FileFilterUtils.notFileFilter(DirectoryFileFilter.INSTANCE));
306
307        //Setup effective directory filter
308        IOFileFilter effDirFilter;
309        if (dirFilter == null) {
310            effDirFilter = FalseFileFilter.INSTANCE;
311        } else {
312            effDirFilter = FileFilterUtils.andFileFilter(dirFilter,
313                DirectoryFileFilter.INSTANCE);
314        }
315
316        //Find files
317        Collection<File> files = new java.util.LinkedList<File>();
318        innerListFiles(files, directory,
319            FileFilterUtils.orFileFilter(effFileFilter, effDirFilter));
320        return files;
321    }
322
323    /**
324     * Allows iteration over the files in given directory (and optionally
325     * its subdirectories).
326     * <p>
327     * All files found are filtered by an IOFileFilter. This method is
328     * based on {@link #listFiles(File, IOFileFilter, IOFileFilter)}.
329     *
330     * @param directory  the directory to search in
331     * @param fileFilter  filter to apply when finding files.
332     * @param dirFilter  optional filter to apply when finding subdirectories.
333     * If this parameter is <code>null</code>, subdirectories will not be included in the
334     * search. Use TrueFileFilter.INSTANCE to match all directories.
335     * @return an iterator of java.io.File for the matching files
336     * @see org.apache.commons.io.filefilter.FileFilterUtils
337     * @see org.apache.commons.io.filefilter.NameFileFilter
338     * @since Commons IO 1.2
339     */
340    public static Iterator<File> iterateFiles(
341            File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
342        return listFiles(directory, fileFilter, dirFilter).iterator();
343    }
344
345    //-----------------------------------------------------------------------
346    /**
347     * Converts an array of file extensions to suffixes for use
348     * with IOFileFilters.
349     *
350     * @param extensions  an array of extensions. Format: {"java", "xml"}
351     * @return an array of suffixes. Format: {".java", ".xml"}
352     */
353    private static String[] toSuffixes(String[] extensions) {
354        String[] suffixes = new String[extensions.length];
355        for (int i = 0; i < extensions.length; i++) {
356            suffixes[i] = "." + extensions[i];
357        }
358        return suffixes;
359    }
360
361
362    /**
363     * Finds files within a given directory (and optionally its subdirectories)
364     * which match an array of extensions.
365     *
366     * @param directory  the directory to search in
367     * @param extensions  an array of extensions, ex. {"java","xml"}. If this
368     * parameter is <code>null</code>, all files are returned.
369     * @param recursive  if true all subdirectories are searched as well
370     * @return an collection of java.io.File with the matching files
371     */
372    public static Collection<File> listFiles(
373            File directory, String[] extensions, boolean recursive) {
374        IOFileFilter filter;
375        if (extensions == null) {
376            filter = TrueFileFilter.INSTANCE;
377        } else {
378            String[] suffixes = toSuffixes(extensions);
379            filter = new SuffixFileFilter(suffixes);
380        }
381        return listFiles(directory, filter,
382            (recursive ? TrueFileFilter.INSTANCE : FalseFileFilter.INSTANCE));
383    }
384
385    /**
386     * Allows iteration over the files in a given directory (and optionally
387     * its subdirectories) which match an array of extensions. This method
388     * is based on {@link #listFiles(File, String[], boolean)}.
389     *
390     * @param directory  the directory to search in
391     * @param extensions  an array of extensions, ex. {"java","xml"}. If this
392     * parameter is <code>null</code>, all files are returned.
393     * @param recursive  if true all subdirectories are searched as well
394     * @return an iterator of java.io.File with the matching files
395     * @since Commons IO 1.2
396     */
397    public static Iterator<File> iterateFiles(
398            File directory, String[] extensions, boolean recursive) {
399        return listFiles(directory, extensions, recursive).iterator();
400    }
401
402    //-----------------------------------------------------------------------
403    /**
404     * Compares the contents of two files to determine if they are equal or not.
405     * <p>
406     * This method checks to see if the two files are different lengths
407     * or if they point to the same file, before resorting to byte-by-byte
408     * comparison of the contents.
409     * <p>
410     * Code origin: Avalon
411     *
412     * @param file1  the first file
413     * @param file2  the second file
414     * @return true if the content of the files are equal or they both don't
415     * exist, false otherwise
416     * @throws IOException in case of an I/O error
417     */
418    public static boolean contentEquals(File file1, File file2) throws IOException {
419        boolean file1Exists = file1.exists();
420        if (file1Exists != file2.exists()) {
421            return false;
422        }
423
424        if (!file1Exists) {
425            // two not existing files are equal
426            return true;
427        }
428
429        if (file1.isDirectory() || file2.isDirectory()) {
430            // don't want to compare directory contents
431            throw new IOException("Can't compare directories, only files");
432        }
433
434        if (file1.length() != file2.length()) {
435            // lengths differ, cannot be equal
436            return false;
437        }
438
439        if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) {
440            // same file
441            return true;
442        }
443
444        InputStream input1 = null;
445        InputStream input2 = null;
446        try {
447            input1 = new FileInputStream(file1);
448            input2 = new FileInputStream(file2);
449            return IOUtils.contentEquals(input1, input2);
450
451        } finally {
452            IOUtils.closeQuietly(input1);
453            IOUtils.closeQuietly(input2);
454        }
455    }
456
457    //-----------------------------------------------------------------------
458    /**
459     * Convert from a <code>URL</code> to a <code>File</code>.
460     * <p>
461     * From version 1.1 this method will decode the URL.
462     * Syntax such as <code>file:///my%20docs/file.txt</code> will be
463     * correctly decoded to <code>/my docs/file.txt</code>.
464     *
465     * @param url  the file URL to convert, <code>null</code> returns <code>null</code>
466     * @return the equivalent <code>File</code> object, or <code>null</code>
467     *  if the URL's protocol is not <code>file</code>
468     * @throws IllegalArgumentException if the file is incorrectly encoded
469     */
470    public static File toFile(URL url) {
471        if (url == null || !url.getProtocol().equals("file")) {
472            return null;
473        } else {
474            String filename = url.getFile().replace('/', File.separatorChar);
475            int pos =0;
476            while ((pos = filename.indexOf('%', pos)) >= 0) {
477                if (pos + 2 < filename.length()) {
478                    String hexStr = filename.substring(pos + 1, pos + 3);
479                    char ch = (char) Integer.parseInt(hexStr, 16);
480                    filename = filename.substring(0, pos) + ch + filename.substring(pos + 3);
481                }
482            }
483            return new File(filename);
484        }
485    }
486
487    /**
488     * Converts each of an array of <code>URL</code> to a <code>File</code>.
489     * <p>
490     * Returns an array of the same size as the input.
491     * If the input is <code>null</code>, an empty array is returned.
492     * If the input contains <code>null</code>, the output array contains <code>null</code> at the same
493     * index.
494     * <p>
495     * This method will decode the URL.
496     * Syntax such as <code>file:///my%20docs/file.txt</code> will be
497     * correctly decoded to <code>/my docs/file.txt</code>.
498     *
499     * @param urls  the file URLs to convert, <code>null</code> returns empty array
500     * @return a non-<code>null</code> array of Files matching the input, with a <code>null</code> item
501     *  if there was a <code>null</code> at that index in the input array
502     * @throws IllegalArgumentException if any file is not a URL file
503     * @throws IllegalArgumentException if any file is incorrectly encoded
504     * @since Commons IO 1.1
505     */
506    public static File[] toFiles(URL[] urls) {
507        if (urls == null || urls.length == 0) {
508            return EMPTY_FILE_ARRAY;
509        }
510        File[] files = new File[urls.length];
511        for (int i = 0; i < urls.length; i++) {
512            URL url = urls[i];
513            if (url != null) {
514                if (url.getProtocol().equals("file") == false) {
515                    throw new IllegalArgumentException(
516                            "URL could not be converted to a File: " + url);
517                }
518                files[i] = toFile(url);
519            }
520        }
521        return files;
522    }
523
524    /**
525     * Converts each of an array of <code>File</code> to a <code>URL</code>.
526     * <p>
527     * Returns an array of the same size as the input.
528     *
529     * @param files  the files to convert
530     * @return an array of URLs matching the input
531     * @throws IOException if a file cannot be converted
532     */
533    public static URL[] toURLs(File[] files) throws IOException {
534        URL[] urls = new URL[files.length];
535
536        for (int i = 0; i < urls.length; i++) {
537            urls[i] = files[i].toURI().toURL();
538        }
539
540        return urls;
541    }
542
543    //-----------------------------------------------------------------------
544    /**
545     * Copies a file to a directory preserving the file date.
546     * <p>
547     * This method copies the contents of the specified source file
548     * to a file of the same name in the specified destination directory.
549     * The destination directory is created if it does not exist.
550     * If the destination file exists, then this method will overwrite it.
551     *
552     * @param srcFile  an existing file to copy, must not be <code>null</code>
553     * @param destDir  the directory to place the copy in, must not be <code>null</code>
554     *
555     * @throws NullPointerException if source or destination is null
556     * @throws IOException if source or destination is invalid
557     * @throws IOException if an IO error occurs during copying
558     * @see #copyFile(File, File, boolean)
559     */
560    public static void copyFileToDirectory(File srcFile, File destDir) throws IOException {
561        copyFileToDirectory(srcFile, destDir, true);
562    }
563
564    /**
565     * Copies a file to a directory optionally preserving the file date.
566     * <p>
567     * This method copies the contents of the specified source file
568     * to a file of the same name in the specified destination directory.
569     * The destination directory is created if it does not exist.
570     * If the destination file exists, then this method will overwrite it.
571     *
572     * @param srcFile  an existing file to copy, must not be <code>null</code>
573     * @param destDir  the directory to place the copy in, must not be <code>null</code>
574     * @param preserveFileDate  true if the file date of the copy
575     *  should be the same as the original
576     *
577     * @throws NullPointerException if source or destination is <code>null</code>
578     * @throws IOException if source or destination is invalid
579     * @throws IOException if an IO error occurs during copying
580     * @see #copyFile(File, File, boolean)
581     * @since Commons IO 1.3
582     */
583    public static void copyFileToDirectory(File srcFile, File destDir, boolean preserveFileDate) throws IOException {
584        if (destDir == null) {
585            throw new NullPointerException("Destination must not be null");
586        }
587        if (destDir.exists() && destDir.isDirectory() == false) {
588            throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory");
589        }
590        copyFile(srcFile, new File(destDir, srcFile.getName()), preserveFileDate);
591    }
592
593    /**
594     * Copies a file to a new location preserving the file date.
595     * <p>
596     * This method copies the contents of the specified source file to the
597     * specified destination file. The directory holding the destination file is
598     * created if it does not exist. If the destination file exists, then this
599     * method will overwrite it.
600     *
601     * @param srcFile  an existing file to copy, must not be <code>null</code>
602     * @param destFile  the new file, must not be <code>null</code>
603     *
604     * @throws NullPointerException if source or destination is <code>null</code>
605     * @throws IOException if source or destination is invalid
606     * @throws IOException if an IO error occurs during copying
607     * @see #copyFileToDirectory(File, File)
608     */
609    public static void copyFile(File srcFile, File destFile) throws IOException {
610        copyFile(srcFile, destFile, true);
611    }
612
613    /**
614     * Copies a file to a new location.
615     * <p>
616     * This method copies the contents of the specified source file
617     * to the specified destination file.
618     * The directory holding the destination file is created if it does not exist.
619     * If the destination file exists, then this method will overwrite it.
620     *
621     * @param srcFile  an existing file to copy, must not be <code>null</code>
622     * @param destFile  the new file, must not be <code>null</code>
623     * @param preserveFileDate  true if the file date of the copy
624     *  should be the same as the original
625     *
626     * @throws NullPointerException if source or destination is <code>null</code>
627     * @throws IOException if source or destination is invalid
628     * @throws IOException if an IO error occurs during copying
629     * @see #copyFileToDirectory(File, File, boolean)
630     */
631    public static void copyFile(File srcFile, File destFile,
632            boolean preserveFileDate) throws IOException {
633        if (srcFile == null) {
634            throw new NullPointerException("Source must not be null");
635        }
636        if (destFile == null) {
637            throw new NullPointerException("Destination must not be null");
638        }
639        if (srcFile.exists() == false) {
640            throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
641        }
642        if (srcFile.isDirectory()) {
643            throw new IOException("Source '" + srcFile + "' exists but is a directory");
644        }
645        if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) {
646            throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same");
647        }
648        if (destFile.getParentFile() != null && destFile.getParentFile().exists() == false) {
649            if (destFile.getParentFile().mkdirs() == false) {
650                throw new IOException("Destination '" + destFile + "' directory cannot be created");
651            }
652        }
653        if (destFile.exists() && destFile.canWrite() == false) {
654            throw new IOException("Destination '" + destFile + "' exists but is read-only");
655        }
656        doCopyFile(srcFile, destFile, preserveFileDate);
657    }
658
659    /**
660     * Internal copy file method.
661     *
662     * @param srcFile  the validated source file, must not be <code>null</code>
663     * @param destFile  the validated destination file, must not be <code>null</code>
664     * @param preserveFileDate  whether to preserve the file date
665     * @throws IOException if an error occurs
666     */
667    private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException {
668        if (destFile.exists() && destFile.isDirectory()) {
669            throw new IOException("Destination '" + destFile + "' exists but is a directory");
670        }
671
672        FileInputStream input = new FileInputStream(srcFile);
673        try {
674            FileOutputStream output = new FileOutputStream(destFile);
675            try {
676                IOUtils.copy(input, output);
677            } finally {
678                IOUtils.closeQuietly(output);
679            }
680        } finally {
681            IOUtils.closeQuietly(input);
682        }
683
684        if (srcFile.length() != destFile.length()) {
685            throw new IOException("Failed to copy full contents from '" +
686                    srcFile + "' to '" + destFile + "'");
687        }
688        if (preserveFileDate) {
689            destFile.setLastModified(srcFile.lastModified());
690        }
691    }
692
693    //-----------------------------------------------------------------------
694    /**
695     * Copies a directory to within another directory preserving the file dates.
696     * <p>
697     * This method copies the source directory and all its contents to a
698     * directory of the same name in the specified destination directory.
699     * <p>
700     * The destination directory is created if it does not exist.
701     * If the destination directory did exist, then this method merges
702     * the source with the destination, with the source taking precedence.
703     *
704     * @param srcDir  an existing directory to copy, must not be <code>null</code>
705     * @param destDir  the directory to place the copy in, must not be <code>null</code>
706     *
707     * @throws NullPointerException if source or destination is <code>null</code>
708     * @throws IOException if source or destination is invalid
709     * @throws IOException if an IO error occurs during copying
710     * @since Commons IO 1.2
711     */
712    public static void copyDirectoryToDirectory(File srcDir, File destDir) throws IOException {
713        if (srcDir == null) {
714            throw new NullPointerException("Source must not be null");
715        }
716        if (srcDir.exists() && srcDir.isDirectory() == false) {
717            throw new IllegalArgumentException("Source '" + destDir + "' is not a directory");
718        }
719        if (destDir == null) {
720            throw new NullPointerException("Destination must not be null");
721        }
722        if (destDir.exists() && destDir.isDirectory() == false) {
723            throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory");
724        }
725        copyDirectory(srcDir, new File(destDir, srcDir.getName()), true);
726    }
727
728    /**
729     * Copies a whole directory to a new location preserving the file dates.
730     * <p>
731     * This method copies the specified directory and all its child
732     * directories and files to the specified destination.
733     * The destination is the new location and name of the directory.
734     * <p>
735     * The destination directory is created if it does not exist.
736     * If the destination directory did exist, then this method merges
737     * the source with the destination, with the source taking precedence.
738     *
739     * @param srcDir  an existing directory to copy, must not be <code>null</code>
740     * @param destDir  the new directory, must not be <code>null</code>
741     *
742     * @throws NullPointerException if source or destination is <code>null</code>
743     * @throws IOException if source or destination is invalid
744     * @throws IOException if an IO error occurs during copying
745     * @since Commons IO 1.1
746     */
747    @VisibleForTesting
748    public static void copyDirectory(File srcDir, File destDir) throws IOException {
749        copyDirectory(srcDir, destDir, true);
750    }
751
752    /**
753     * Copies a whole directory to a new location.
754     * <p>
755     * This method copies the contents of the specified source directory
756     * to within the specified destination directory.
757     * <p>
758     * The destination directory is created if it does not exist.
759     * If the destination directory did exist, then this method merges
760     * the source with the destination, with the source taking precedence.
761     *
762     * @param srcDir  an existing directory to copy, must not be <code>null</code>
763     * @param destDir  the new directory, must not be <code>null</code>
764     * @param preserveFileDate  true if the file date of the copy
765     *  should be the same as the original
766     *
767     * @throws NullPointerException if source or destination is <code>null</code>
768     * @throws IOException if source or destination is invalid
769     * @throws IOException if an IO error occurs during copying
770     * @since Commons IO 1.1
771     */
772    public static void copyDirectory(File srcDir, File destDir,
773            boolean preserveFileDate) throws IOException {
774        copyDirectory(srcDir, destDir, null, preserveFileDate);
775    }
776
777    /**
778     * Copies a filtered directory to a new location preserving the file dates.
779     * <p>
780     * This method copies the contents of the specified source directory
781     * to within the specified destination directory.
782     * <p>
783     * The destination directory is created if it does not exist.
784     * If the destination directory did exist, then this method merges
785     * the source with the destination, with the source taking precedence.
786     *
787     * <h4>Example: Copy directories only</h4>
788     *  <pre>
789     *  // only copy the directory structure
790     *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY);
791     *  </pre>
792     *
793     * <h4>Example: Copy directories and txt files</h4>
794     *  <pre>
795     *  // Create a filter for ".txt" files
796     *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
797     *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
798     *
799     *  // Create a filter for either directories or ".txt" files
800     *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
801     *
802     *  // Copy using the filter
803     *  FileUtils.copyDirectory(srcDir, destDir, filter);
804     *  </pre>
805     *
806     * @param srcDir  an existing directory to copy, must not be <code>null</code>
807     * @param destDir  the new directory, must not be <code>null</code>
808     * @param filter  the filter to apply, null means copy all directories and files
809     *  should be the same as the original
810     *
811     * @throws NullPointerException if source or destination is <code>null</code>
812     * @throws IOException if source or destination is invalid
813     * @throws IOException if an IO error occurs during copying
814     * @since Commons IO 1.4
815     */
816    public static void copyDirectory(File srcDir, File destDir,
817            FileFilter filter) throws IOException {
818        copyDirectory(srcDir, destDir, filter, true);
819    }
820
821    /**
822     * Copies a filtered directory to a new location.
823     * <p>
824     * This method copies the contents of the specified source directory
825     * to within the specified destination directory.
826     * <p>
827     * The destination directory is created if it does not exist.
828     * If the destination directory did exist, then this method merges
829     * the source with the destination, with the source taking precedence.
830     *
831     * <h4>Example: Copy directories only</h4>
832     *  <pre>
833     *  // only copy the directory structure
834     *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
835     *  </pre>
836     *
837     * <h4>Example: Copy directories and txt files</h4>
838     *  <pre>
839     *  // Create a filter for ".txt" files
840     *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
841     *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
842     *
843     *  // Create a filter for either directories or ".txt" files
844     *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
845     *
846     *  // Copy using the filter
847     *  FileUtils.copyDirectory(srcDir, destDir, filter, false);
848     *  </pre>
849     *
850     * @param srcDir  an existing directory to copy, must not be <code>null</code>
851     * @param destDir  the new directory, must not be <code>null</code>
852     * @param filter  the filter to apply, null means copy all directories and files
853     * @param preserveFileDate  true if the file date of the copy
854     *  should be the same as the original
855     *
856     * @throws NullPointerException if source or destination is <code>null</code>
857     * @throws IOException if source or destination is invalid
858     * @throws IOException if an IO error occurs during copying
859     * @since Commons IO 1.4
860     */
861    public static void copyDirectory(File srcDir, File destDir,
862            FileFilter filter, boolean preserveFileDate) throws IOException {
863        if (srcDir == null) {
864            throw new NullPointerException("Source must not be null");
865        }
866        if (destDir == null) {
867            throw new NullPointerException("Destination must not be null");
868        }
869        if (srcDir.exists() == false) {
870            throw new FileNotFoundException("Source '" + srcDir + "' does not exist");
871        }
872        if (srcDir.isDirectory() == false) {
873            throw new IOException("Source '" + srcDir + "' exists but is not a directory");
874        }
875        if (srcDir.getCanonicalPath().equals(destDir.getCanonicalPath())) {
876            throw new IOException("Source '" + srcDir + "' and destination '" + destDir + "' are the same");
877        }
878
879        // Cater for destination being directory within the source directory (see IO-141)
880        List<String> exclusionList = null;
881        if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) {
882            File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
883            if (srcFiles != null && srcFiles.length > 0) {
884                exclusionList = new ArrayList<String>(srcFiles.length);
885                for (int i = 0; i < srcFiles.length; i++) {
886                    File copiedFile = new File(destDir, srcFiles[i].getName());
887                    exclusionList.add(copiedFile.getCanonicalPath());
888                }
889            }
890        }
891        doCopyDirectory(srcDir, destDir, filter, preserveFileDate, exclusionList);
892    }
893
894    /**
895     * Internal copy directory method.
896     *
897     * @param srcDir  the validated source directory, must not be <code>null</code>
898     * @param destDir  the validated destination directory, must not be <code>null</code>
899     * @param filter  the filter to apply, null means copy all directories and files
900     * @param preserveFileDate  whether to preserve the file date
901     * @param exclusionList  List of files and directories to exclude from the copy, may be null
902     * @throws IOException if an error occurs
903     * @since Commons IO 1.1
904     */
905    private static void doCopyDirectory(File srcDir, File destDir, FileFilter filter,
906            boolean preserveFileDate, List<String> exclusionList) throws IOException {
907        if (destDir.exists()) {
908            if (destDir.isDirectory() == false) {
909                throw new IOException("Destination '" + destDir + "' exists but is not a directory");
910            }
911        } else {
912            if (destDir.mkdirs() == false) {
913                throw new IOException("Destination '" + destDir + "' directory cannot be created");
914            }
915            if (preserveFileDate) {
916                destDir.setLastModified(srcDir.lastModified());
917            }
918        }
919        if (destDir.canWrite() == false) {
920            throw new IOException("Destination '" + destDir + "' cannot be written to");
921        }
922        // recurse
923        File[] files = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
924        if (files == null) {  // null if security restricted
925            throw new IOException("Failed to list contents of " + srcDir);
926        }
927        for (int i = 0; i < files.length; i++) {
928            File copiedFile = new File(destDir, files[i].getName());
929            if (exclusionList == null || !exclusionList.contains(files[i].getCanonicalPath())) {
930                if (files[i].isDirectory()) {
931                    doCopyDirectory(files[i], copiedFile, filter, preserveFileDate, exclusionList);
932                } else {
933                    doCopyFile(files[i], copiedFile, preserveFileDate);
934                }
935            }
936        }
937    }
938
939    //-----------------------------------------------------------------------
940    /**
941     * Copies bytes from the URL <code>source</code> to a file
942     * <code>destination</code>. The directories up to <code>destination</code>
943     * will be created if they don't already exist. <code>destination</code>
944     * will be overwritten if it already exists.
945     *
946     * @param source  the <code>URL</code> to copy bytes from, must not be <code>null</code>
947     * @param destination  the non-directory <code>File</code> to write bytes to
948     *  (possibly overwriting), must not be <code>null</code>
949     * @throws IOException if <code>source</code> URL cannot be opened
950     * @throws IOException if <code>destination</code> is a directory
951     * @throws IOException if <code>destination</code> cannot be written
952     * @throws IOException if <code>destination</code> needs creating but can't be
953     * @throws IOException if an IO error occurs during copying
954     */
955    public static void copyURLToFile(URL source, File destination) throws IOException {
956        InputStream input = source.openStream();
957        try {
958            FileOutputStream output = openOutputStream(destination);
959            try {
960                IOUtils.copy(input, output);
961            } finally {
962                IOUtils.closeQuietly(output);
963            }
964        } finally {
965            IOUtils.closeQuietly(input);
966        }
967    }
968
969    //-----------------------------------------------------------------------
970    /**
971     * Deletes a directory recursively.
972     *
973     * @param directory  directory to delete
974     * @throws IOException in case deletion is unsuccessful
975     */
976    public static void deleteDirectory(File directory) throws IOException {
977        if (!directory.exists()) {
978            return;
979        }
980
981        cleanDirectory(directory);
982        if (!directory.delete()) {
983            String message =
984                "Unable to delete directory " + directory + ".";
985            throw new IOException(message);
986        }
987    }
988
989    /**
990     * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories.
991     * <p>
992     * The difference between File.delete() and this method are:
993     * <ul>
994     * <li>A directory to be deleted does not have to be empty.</li>
995     * <li>No exceptions are thrown when a file or directory cannot be deleted.</li>
996     * </ul>
997     *
998     * @param file  file or directory to delete, can be <code>null</code>
999     * @return <code>true</code> if the file or directory was deleted, otherwise
1000     * <code>false</code>
1001     *
1002     * @since Commons IO 1.4
1003     */
1004    public static boolean deleteQuietly(File file) {
1005        if (file == null) {
1006            return false;
1007        }
1008        try {
1009            if (file.isDirectory()) {
1010                cleanDirectory(file);
1011            }
1012        } catch (Exception e) {
1013        }
1014
1015        try {
1016            return file.delete();
1017        } catch (Exception e) {
1018            return false;
1019        }
1020    }
1021
1022    /**
1023     * Cleans a directory without deleting it.
1024     *
1025     * @param directory directory to clean
1026     * @throws IOException in case cleaning is unsuccessful
1027     */
1028    public static void cleanDirectory(File directory) throws IOException {
1029        if (!directory.exists()) {
1030            String message = directory + " does not exist";
1031            throw new IllegalArgumentException(message);
1032        }
1033
1034        if (!directory.isDirectory()) {
1035            String message = directory + " is not a directory";
1036            throw new IllegalArgumentException(message);
1037        }
1038
1039        File[] files = directory.listFiles();
1040        if (files == null) {  // null if security restricted
1041            throw new IOException("Failed to list contents of " + directory);
1042        }
1043
1044        IOException exception = null;
1045        for (int i = 0; i < files.length; i++) {
1046            File file = files[i];
1047            try {
1048                forceDelete(file);
1049            } catch (IOException ioe) {
1050                exception = ioe;
1051            }
1052        }
1053
1054        if (null != exception) {
1055            throw exception;
1056        }
1057    }
1058
1059    //-----------------------------------------------------------------------
1060    /**
1061     * Waits for NFS to propagate a file creation, imposing a timeout.
1062     * <p>
1063     * This method repeatedly tests {@link File#exists()} until it returns
1064     * true up to the maximum time specified in seconds.
1065     *
1066     * @param file  the file to check, must not be <code>null</code>
1067     * @param seconds  the maximum time in seconds to wait
1068     * @return true if file exists
1069     * @throws NullPointerException if the file is <code>null</code>
1070     */
1071    public static boolean waitFor(File file, int seconds) {
1072        int timeout = 0;
1073        int tick = 0;
1074        while (!file.exists()) {
1075            if (tick++ >= 10) {
1076                tick = 0;
1077                if (timeout++ > seconds) {
1078                    return false;
1079                }
1080            }
1081            try {
1082                Thread.sleep(100);
1083            } catch (InterruptedException ignore) {
1084                // ignore exception
1085            } catch (Exception ex) {
1086                break;
1087            }
1088        }
1089        return true;
1090    }
1091
1092    //-----------------------------------------------------------------------
1093    /**
1094     * Reads the contents of a file into a String.
1095     * The file is always closed.
1096     *
1097     * @param file  the file to read, must not be <code>null</code>
1098     * @param encoding  the encoding to use, <code>null</code> means platform default
1099     * @return the file contents, never <code>null</code>
1100     * @throws IOException in case of an I/O error
1101     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
1102     */
1103    public static String readFileToString(File file, String encoding) throws IOException {
1104        InputStream in = null;
1105        try {
1106            in = openInputStream(file);
1107            return IOUtils.toString(in, encoding);
1108        } finally {
1109            IOUtils.closeQuietly(in);
1110        }
1111    }
1112
1113
1114    /**
1115     * Reads the contents of a file into a String using the default encoding for the VM.
1116     * The file is always closed.
1117     *
1118     * @param file  the file to read, must not be <code>null</code>
1119     * @return the file contents, never <code>null</code>
1120     * @throws IOException in case of an I/O error
1121     * @since Commons IO 1.3.1
1122     */
1123    public static String readFileToString(File file) throws IOException {
1124        return readFileToString(file, null);
1125    }
1126
1127    /**
1128     * Reads the contents of a file into a byte array.
1129     * The file is always closed.
1130     *
1131     * @param file  the file to read, must not be <code>null</code>
1132     * @return the file contents, never <code>null</code>
1133     * @throws IOException in case of an I/O error
1134     * @since Commons IO 1.1
1135     */
1136    public static byte[] readFileToByteArray(File file) throws IOException {
1137        InputStream in = null;
1138        try {
1139            in = openInputStream(file);
1140            return IOUtils.toByteArray(in);
1141        } finally {
1142            IOUtils.closeQuietly(in);
1143        }
1144    }
1145
1146    /**
1147     * Reads the contents of a file line by line to a List of Strings.
1148     * The file is always closed.
1149     *
1150     * @param file  the file to read, must not be <code>null</code>
1151     * @param encoding  the encoding to use, <code>null</code> means platform default
1152     * @return the list of Strings representing each line in the file, never <code>null</code>
1153     * @throws IOException in case of an I/O error
1154     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
1155     * @since Commons IO 1.1
1156     */
1157    public static List<String> readLines(File file, String encoding) throws IOException {
1158        InputStream in = null;
1159        try {
1160            in = openInputStream(file);
1161            return IOUtils.readLines(in, encoding);
1162        } finally {
1163            IOUtils.closeQuietly(in);
1164        }
1165    }
1166
1167    /**
1168     * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM.
1169     * The file is always closed.
1170     *
1171     * @param file  the file to read, must not be <code>null</code>
1172     * @return the list of Strings representing each line in the file, never <code>null</code>
1173     * @throws IOException in case of an I/O error
1174     * @since Commons IO 1.3
1175     */
1176    public static List<String> readLines(File file) throws IOException {
1177        return readLines(file, null);
1178    }
1179
1180    /**
1181     * Returns an Iterator for the lines in a <code>File</code>.
1182     * <p>
1183     * This method opens an <code>InputStream</code> for the file.
1184     * When you have finished with the iterator you should close the stream
1185     * to free internal resources. This can be done by calling the
1186     * {@link LineIterator#close()} or
1187     * {@link LineIterator#closeQuietly(LineIterator)} method.
1188     * <p>
1189     * The recommended usage pattern is:
1190     * <pre>
1191     * LineIterator it = FileUtils.lineIterator(file, "UTF-8");
1192     * try {
1193     *   while (it.hasNext()) {
1194     *     String line = it.nextLine();
1195     *     /// do something with line
1196     *   }
1197     * } finally {
1198     *   LineIterator.closeQuietly(iterator);
1199     * }
1200     * </pre>
1201     * <p>
1202     * If an exception occurs during the creation of the iterator, the
1203     * underlying stream is closed.
1204     *
1205     * @param file  the file to open for input, must not be <code>null</code>
1206     * @param encoding  the encoding to use, <code>null</code> means platform default
1207     * @return an Iterator of the lines in the file, never <code>null</code>
1208     * @throws IOException in case of an I/O error (file closed)
1209     * @since Commons IO 1.2
1210     */
1211    public static LineIterator lineIterator(File file, String encoding) throws IOException {
1212        InputStream in = null;
1213        try {
1214            in = openInputStream(file);
1215            return IOUtils.lineIterator(in, encoding);
1216        } catch (IOException ex) {
1217            IOUtils.closeQuietly(in);
1218            throw ex;
1219        } catch (RuntimeException ex) {
1220            IOUtils.closeQuietly(in);
1221            throw ex;
1222        }
1223    }
1224
1225    /**
1226     * Returns an Iterator for the lines in a <code>File</code> using the default encoding for the VM.
1227     *
1228     * @param file  the file to open for input, must not be <code>null</code>
1229     * @return an Iterator of the lines in the file, never <code>null</code>
1230     * @throws IOException in case of an I/O error (file closed)
1231     * @since Commons IO 1.3
1232     * @see #lineIterator(File, String)
1233     */
1234    public static LineIterator lineIterator(File file) throws IOException {
1235        return lineIterator(file, null);
1236    }
1237
1238    //-----------------------------------------------------------------------
1239    /**
1240     * Writes a String to a file creating the file if it does not exist.
1241     *
1242     * NOTE: As from v1.3, the parent directories of the file will be created
1243     * if they do not exist.
1244     *
1245     * @param file  the file to write
1246     * @param data  the content to write to the file
1247     * @param encoding  the encoding to use, <code>null</code> means platform default
1248     * @throws IOException in case of an I/O error
1249     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
1250     */
1251    public static void writeStringToFile(File file, String data, String encoding) throws IOException {
1252        OutputStream out = null;
1253        try {
1254            out = openOutputStream(file);
1255            IOUtils.write(data, out, encoding);
1256        } finally {
1257            IOUtils.closeQuietly(out);
1258        }
1259    }
1260
1261    /**
1262     * Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
1263     *
1264     * @param file  the file to write
1265     * @param data  the content to write to the file
1266     * @throws IOException in case of an I/O error
1267     */
1268    public static void writeStringToFile(File file, String data) throws IOException {
1269        writeStringToFile(file, data, null);
1270    }
1271
1272    /**
1273     * Writes a byte array to a file creating the file if it does not exist.
1274     * <p>
1275     * NOTE: As from v1.3, the parent directories of the file will be created
1276     * if they do not exist.
1277     *
1278     * @param file  the file to write to
1279     * @param data  the content to write to the file
1280     * @throws IOException in case of an I/O error
1281     * @since Commons IO 1.1
1282     */
1283    public static void writeByteArrayToFile(File file, byte[] data) throws IOException {
1284        OutputStream out = null;
1285        try {
1286            out = openOutputStream(file);
1287            out.write(data);
1288        } finally {
1289            IOUtils.closeQuietly(out);
1290        }
1291    }
1292
1293    /**
1294     * Writes the <code>toString()</code> value of each item in a collection to
1295     * the specified <code>File</code> line by line.
1296     * The specified character encoding and the default line ending will be used.
1297     * <p>
1298     * NOTE: As from v1.3, the parent directories of the file will be created
1299     * if they do not exist.
1300     *
1301     * @param file  the file to write to
1302     * @param encoding  the encoding to use, <code>null</code> means platform default
1303     * @param lines  the lines to write, <code>null</code> entries produce blank lines
1304     * @throws IOException in case of an I/O error
1305     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
1306     * @since Commons IO 1.1
1307     */
1308    public static void writeLines(File file, String encoding, Collection<Object> lines) throws IOException {
1309        writeLines(file, encoding, lines, null);
1310    }
1311
1312    /**
1313     * Writes the <code>toString()</code> value of each item in a collection to
1314     * the specified <code>File</code> line by line.
1315     * The default VM encoding and the default line ending will be used.
1316     *
1317     * @param file  the file to write to
1318     * @param lines  the lines to write, <code>null</code> entries produce blank lines
1319     * @throws IOException in case of an I/O error
1320     * @since Commons IO 1.3
1321     */
1322    public static void writeLines(File file, Collection<Object> lines) throws IOException {
1323        writeLines(file, null, lines, null);
1324    }
1325
1326    /**
1327     * Writes the <code>toString()</code> value of each item in a collection to
1328     * the specified <code>File</code> line by line.
1329     * The specified character encoding and the line ending will be used.
1330     * <p>
1331     * NOTE: As from v1.3, the parent directories of the file will be created
1332     * if they do not exist.
1333     *
1334     * @param file  the file to write to
1335     * @param encoding  the encoding to use, <code>null</code> means platform default
1336     * @param lines  the lines to write, <code>null</code> entries produce blank lines
1337     * @param lineEnding  the line separator to use, <code>null</code> is system default
1338     * @throws IOException in case of an I/O error
1339     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
1340     * @since Commons IO 1.1
1341     */
1342    public static void writeLines(File file, String encoding, Collection<Object> lines, String lineEnding) throws IOException {
1343        OutputStream out = null;
1344        try {
1345            out = openOutputStream(file);
1346            IOUtils.writeLines(lines, lineEnding, out, encoding);
1347        } finally {
1348            IOUtils.closeQuietly(out);
1349        }
1350    }
1351
1352    /**
1353     * Writes the <code>toString()</code> value of each item in a collection to
1354     * the specified <code>File</code> line by line.
1355     * The default VM encoding and the specified line ending will be used.
1356     *
1357     * @param file  the file to write to
1358     * @param lines  the lines to write, <code>null</code> entries produce blank lines
1359     * @param lineEnding  the line separator to use, <code>null</code> is system default
1360     * @throws IOException in case of an I/O error
1361     * @since Commons IO 1.3
1362     */
1363    public static void writeLines(File file, Collection<Object> lines, String lineEnding) throws IOException {
1364        writeLines(file, null, lines, lineEnding);
1365    }
1366
1367    //-----------------------------------------------------------------------
1368    /**
1369     * Deletes a file. If file is a directory, delete it and all sub-directories.
1370     * <p>
1371     * The difference between File.delete() and this method are:
1372     * <ul>
1373     * <li>A directory to be deleted does not have to be empty.</li>
1374     * <li>You get exceptions when a file or directory cannot be deleted.
1375     *      (java.io.File methods returns a boolean)</li>
1376     * </ul>
1377     *
1378     * @param file  file or directory to delete, must not be <code>null</code>
1379     * @throws NullPointerException if the directory is <code>null</code>
1380     * @throws FileNotFoundException if the file was not found
1381     * @throws IOException in case deletion is unsuccessful
1382     */
1383    public static void forceDelete(File file) throws IOException {
1384        if (file.isDirectory()) {
1385            deleteDirectory(file);
1386        } else {
1387            boolean filePresent = file.exists();
1388            if (!file.delete()) {
1389                if (!filePresent){
1390                    throw new FileNotFoundException("File does not exist: " + file);
1391                }
1392                String message =
1393                    "Unable to delete file: " + file;
1394                throw new IOException(message);
1395            }
1396        }
1397    }
1398
1399    /**
1400     * Schedules a file to be deleted when JVM exits.
1401     * If file is directory delete it and all sub-directories.
1402     *
1403     * @param file  file or directory to delete, must not be <code>null</code>
1404     * @throws NullPointerException if the file is <code>null</code>
1405     * @throws IOException in case deletion is unsuccessful
1406     */
1407    public static void forceDeleteOnExit(File file) throws IOException {
1408        if (file.isDirectory()) {
1409            deleteDirectoryOnExit(file);
1410        } else {
1411            file.deleteOnExit();
1412        }
1413    }
1414
1415    /**
1416     * Schedules a directory recursively for deletion on JVM exit.
1417     *
1418     * @param directory  directory to delete, must not be <code>null</code>
1419     * @throws NullPointerException if the directory is <code>null</code>
1420     * @throws IOException in case deletion is unsuccessful
1421     */
1422    private static void deleteDirectoryOnExit(File directory) throws IOException {
1423        if (!directory.exists()) {
1424            return;
1425        }
1426
1427        cleanDirectoryOnExit(directory);
1428        directory.deleteOnExit();
1429    }
1430
1431    /**
1432     * Cleans a directory without deleting it.
1433     *
1434     * @param directory  directory to clean, must not be <code>null</code>
1435     * @throws NullPointerException if the directory is <code>null</code>
1436     * @throws IOException in case cleaning is unsuccessful
1437     */
1438    private static void cleanDirectoryOnExit(File directory) throws IOException {
1439        if (!directory.exists()) {
1440            String message = directory + " does not exist";
1441            throw new IllegalArgumentException(message);
1442        }
1443
1444        if (!directory.isDirectory()) {
1445            String message = directory + " is not a directory";
1446            throw new IllegalArgumentException(message);
1447        }
1448
1449        File[] files = directory.listFiles();
1450        if (files == null) {  // null if security restricted
1451            throw new IOException("Failed to list contents of " + directory);
1452        }
1453
1454        IOException exception = null;
1455        for (int i = 0; i < files.length; i++) {
1456            File file = files[i];
1457            try {
1458                forceDeleteOnExit(file);
1459            } catch (IOException ioe) {
1460                exception = ioe;
1461            }
1462        }
1463
1464        if (null != exception) {
1465            throw exception;
1466        }
1467    }
1468
1469    /**
1470     * Makes a directory, including any necessary but nonexistent parent
1471     * directories. If there already exists a file with specified name or
1472     * the directory cannot be created then an exception is thrown.
1473     *
1474     * @param directory  directory to create, must not be <code>null</code>
1475     * @throws NullPointerException if the directory is <code>null</code>
1476     * @throws IOException if the directory cannot be created
1477     */
1478    public static void forceMkdir(File directory) throws IOException {
1479        if (directory.exists()) {
1480            if (directory.isFile()) {
1481                String message =
1482                    "File "
1483                        + directory
1484                        + " exists and is "
1485                        + "not a directory. Unable to create directory.";
1486                throw new IOException(message);
1487            }
1488        } else {
1489            if (!directory.mkdirs()) {
1490                String message =
1491                    "Unable to create directory " + directory;
1492                throw new IOException(message);
1493            }
1494        }
1495    }
1496
1497    //-----------------------------------------------------------------------
1498    /**
1499     * Counts the size of a directory recursively (sum of the length of all files).
1500     *
1501     * @param directory  directory to inspect, must not be <code>null</code>
1502     * @return size of directory in bytes, 0 if directory is security restricted
1503     * @throws NullPointerException if the directory is <code>null</code>
1504     */
1505    public static long sizeOfDirectory(File directory) {
1506        if (!directory.exists()) {
1507            String message = directory + " does not exist";
1508            throw new IllegalArgumentException(message);
1509        }
1510
1511        if (!directory.isDirectory()) {
1512            String message = directory + " is not a directory";
1513            throw new IllegalArgumentException(message);
1514        }
1515
1516        long size = 0;
1517
1518        File[] files = directory.listFiles();
1519        if (files == null) {  // null if security restricted
1520            return 0L;
1521        }
1522        for (int i = 0; i < files.length; i++) {
1523            File file = files[i];
1524
1525            if (file.isDirectory()) {
1526                size += sizeOfDirectory(file);
1527            } else {
1528                size += file.length();
1529            }
1530        }
1531
1532        return size;
1533    }
1534
1535    //-----------------------------------------------------------------------
1536    /**
1537     * Tests if the specified <code>File</code> is newer than the reference
1538     * <code>File</code>.
1539     *
1540     * @param file  the <code>File</code> of which the modification date must
1541     * be compared, must not be <code>null</code>
1542     * @param reference  the <code>File</code> of which the modification date
1543     * is used, must not be <code>null</code>
1544     * @return true if the <code>File</code> exists and has been modified more
1545     * recently than the reference <code>File</code>
1546     * @throws IllegalArgumentException if the file is <code>null</code>
1547     * @throws IllegalArgumentException if the reference file is <code>null</code> or doesn't exist
1548     */
1549     public static boolean isFileNewer(File file, File reference) {
1550        if (reference == null) {
1551            throw new IllegalArgumentException("No specified reference file");
1552        }
1553        if (!reference.exists()) {
1554            throw new IllegalArgumentException("The reference file '"
1555                    + file + "' doesn't exist");
1556        }
1557        return isFileNewer(file, reference.lastModified());
1558    }
1559
1560    /**
1561     * Tests if the specified <code>File</code> is newer than the specified
1562     * <code>Date</code>.
1563     *
1564     * @param file  the <code>File</code> of which the modification date
1565     * must be compared, must not be <code>null</code>
1566     * @param date  the date reference, must not be <code>null</code>
1567     * @return true if the <code>File</code> exists and has been modified
1568     * after the given <code>Date</code>.
1569     * @throws IllegalArgumentException if the file is <code>null</code>
1570     * @throws IllegalArgumentException if the date is <code>null</code>
1571     */
1572    public static boolean isFileNewer(File file, Date date) {
1573        if (date == null) {
1574            throw new IllegalArgumentException("No specified date");
1575        }
1576        return isFileNewer(file, date.getTime());
1577    }
1578
1579    /**
1580     * Tests if the specified <code>File</code> is newer than the specified
1581     * time reference.
1582     *
1583     * @param file  the <code>File</code> of which the modification date must
1584     * be compared, must not be <code>null</code>
1585     * @param timeMillis  the time reference measured in milliseconds since the
1586     * epoch (00:00:00 GMT, January 1, 1970)
1587     * @return true if the <code>File</code> exists and has been modified after
1588     * the given time reference.
1589     * @throws IllegalArgumentException if the file is <code>null</code>
1590     */
1591     public static boolean isFileNewer(File file, long timeMillis) {
1592        if (file == null) {
1593            throw new IllegalArgumentException("No specified file");
1594        }
1595        if (!file.exists()) {
1596            return false;
1597        }
1598        return file.lastModified() > timeMillis;
1599    }
1600
1601
1602    //-----------------------------------------------------------------------
1603    /**
1604     * Tests if the specified <code>File</code> is older than the reference
1605     * <code>File</code>.
1606     *
1607     * @param file  the <code>File</code> of which the modification date must
1608     * be compared, must not be <code>null</code>
1609     * @param reference  the <code>File</code> of which the modification date
1610     * is used, must not be <code>null</code>
1611     * @return true if the <code>File</code> exists and has been modified before
1612     * the reference <code>File</code>
1613     * @throws IllegalArgumentException if the file is <code>null</code>
1614     * @throws IllegalArgumentException if the reference file is <code>null</code> or doesn't exist
1615     */
1616     public static boolean isFileOlder(File file, File reference) {
1617        if (reference == null) {
1618            throw new IllegalArgumentException("No specified reference file");
1619        }
1620        if (!reference.exists()) {
1621            throw new IllegalArgumentException("The reference file '"
1622                    + file + "' doesn't exist");
1623        }
1624        return isFileOlder(file, reference.lastModified());
1625    }
1626
1627    /**
1628     * Tests if the specified <code>File</code> is older than the specified
1629     * <code>Date</code>.
1630     *
1631     * @param file  the <code>File</code> of which the modification date
1632     * must be compared, must not be <code>null</code>
1633     * @param date  the date reference, must not be <code>null</code>
1634     * @return true if the <code>File</code> exists and has been modified
1635     * before the given <code>Date</code>.
1636     * @throws IllegalArgumentException if the file is <code>null</code>
1637     * @throws IllegalArgumentException if the date is <code>null</code>
1638     */
1639    public static boolean isFileOlder(File file, Date date) {
1640        if (date == null) {
1641            throw new IllegalArgumentException("No specified date");
1642        }
1643        return isFileOlder(file, date.getTime());
1644    }
1645
1646    /**
1647     * Tests if the specified <code>File</code> is older than the specified
1648     * time reference.
1649     *
1650     * @param file  the <code>File</code> of which the modification date must
1651     * be compared, must not be <code>null</code>
1652     * @param timeMillis  the time reference measured in milliseconds since the
1653     * epoch (00:00:00 GMT, January 1, 1970)
1654     * @return true if the <code>File</code> exists and has been modified before
1655     * the given time reference.
1656     * @throws IllegalArgumentException if the file is <code>null</code>
1657     */
1658     public static boolean isFileOlder(File file, long timeMillis) {
1659        if (file == null) {
1660            throw new IllegalArgumentException("No specified file");
1661        }
1662        if (!file.exists()) {
1663            return false;
1664        }
1665        return file.lastModified() < timeMillis;
1666    }
1667
1668    //-----------------------------------------------------------------------
1669    /**
1670     * Computes the checksum of a file using the CRC32 checksum routine.
1671     * The value of the checksum is returned.
1672     *
1673     * @param file  the file to checksum, must not be <code>null</code>
1674     * @return the checksum value
1675     * @throws NullPointerException if the file or checksum is <code>null</code>
1676     * @throws IllegalArgumentException if the file is a directory
1677     * @throws IOException if an IO error occurs reading the file
1678     * @since Commons IO 1.3
1679     */
1680    public static long checksumCRC32(File file) throws IOException {
1681        CRC32 crc = new CRC32();
1682        checksum(file, crc);
1683        return crc.getValue();
1684    }
1685
1686    /**
1687     * Computes the checksum of a file using the specified checksum object.
1688     * Multiple files may be checked using one <code>Checksum</code> instance
1689     * if desired simply by reusing the same checksum object.
1690     * For example:
1691     * <pre>
1692     *   long csum = FileUtils.checksum(file, new CRC32()).getValue();
1693     * </pre>
1694     *
1695     * @param file  the file to checksum, must not be <code>null</code>
1696     * @param checksum  the checksum object to be used, must not be <code>null</code>
1697     * @return the checksum specified, updated with the content of the file
1698     * @throws NullPointerException if the file or checksum is <code>null</code>
1699     * @throws IllegalArgumentException if the file is a directory
1700     * @throws IOException if an IO error occurs reading the file
1701     * @since Commons IO 1.3
1702     */
1703    public static Checksum checksum(File file, Checksum checksum) throws IOException {
1704        if (file.isDirectory()) {
1705            throw new IllegalArgumentException("Checksums can't be computed on directories");
1706        }
1707        InputStream in = null;
1708        try {
1709            in = new CheckedInputStream(new FileInputStream(file), checksum);
1710            IOUtils.copy(in, new NullOutputStream());
1711        } finally {
1712            IOUtils.closeQuietly(in);
1713        }
1714        return checksum;
1715    }
1716
1717    /**
1718     * Moves a directory.
1719     * <p>
1720     * When the destination directory is on another file system, do a "copy and delete".
1721     *
1722     * @param srcDir the directory to be moved
1723     * @param destDir the destination directory
1724     * @throws NullPointerException if source or destination is <code>null</code>
1725     * @throws IOException if source or destination is invalid
1726     * @throws IOException if an IO error occurs moving the file
1727     * @since Commons IO 1.4
1728     */
1729    public static void moveDirectory(File srcDir, File destDir) throws IOException {
1730        if (srcDir == null) {
1731            throw new NullPointerException("Source must not be null");
1732        }
1733        if (destDir == null) {
1734            throw new NullPointerException("Destination must not be null");
1735        }
1736        if (!srcDir.exists()) {
1737            throw new FileNotFoundException("Source '" + srcDir + "' does not exist");
1738        }
1739        if (!srcDir.isDirectory()) {
1740            throw new IOException("Source '" + srcDir + "' is not a directory");
1741        }
1742        if (destDir.exists()) {
1743            throw new IOException("Destination '" + destDir + "' already exists");
1744        }
1745        boolean rename = srcDir.renameTo(destDir);
1746        if (!rename) {
1747            copyDirectory( srcDir, destDir );
1748            deleteDirectory( srcDir );
1749            if (srcDir.exists()) {
1750                throw new IOException("Failed to delete original directory '" + srcDir +
1751                        "' after copy to '" + destDir + "'");
1752            }
1753        }
1754    }
1755
1756    /**
1757     * Moves a directory to another directory.
1758     *
1759     * @param src the file to be moved
1760     * @param destDir the destination file
1761     * @param createDestDir If <code>true</code> create the destination directory,
1762     * otherwise if <code>false</code> throw an IOException
1763     * @throws NullPointerException if source or destination is <code>null</code>
1764     * @throws IOException if source or destination is invalid
1765     * @throws IOException if an IO error occurs moving the file
1766     * @since Commons IO 1.4
1767     */
1768    public static void moveDirectoryToDirectory(File src, File destDir, boolean createDestDir) throws IOException {
1769        if (src == null) {
1770            throw new NullPointerException("Source must not be null");
1771        }
1772        if (destDir == null) {
1773            throw new NullPointerException("Destination directory must not be null");
1774        }
1775        if (!destDir.exists() && createDestDir) {
1776            destDir.mkdirs();
1777        }
1778        if (!destDir.exists()) {
1779            throw new FileNotFoundException("Destination directory '" + destDir +
1780                    "' does not exist [createDestDir=" + createDestDir +"]");
1781        }
1782        if (!destDir.isDirectory()) {
1783            throw new IOException("Destination '" + destDir + "' is not a directory");
1784        }
1785        moveDirectory(src, new File(destDir, src.getName()));
1786
1787    }
1788
1789    /**
1790     * Moves a file.
1791     * <p>
1792     * When the destination file is on another file system, do a "copy and delete".
1793     *
1794     * @param srcFile the file to be moved
1795     * @param destFile the destination file
1796     * @throws NullPointerException if source or destination is <code>null</code>
1797     * @throws IOException if source or destination is invalid
1798     * @throws IOException if an IO error occurs moving the file
1799     * @since Commons IO 1.4
1800     */
1801    public static void moveFile(File srcFile, File destFile) throws IOException {
1802        if (srcFile == null) {
1803            throw new NullPointerException("Source must not be null");
1804        }
1805        if (destFile == null) {
1806            throw new NullPointerException("Destination must not be null");
1807        }
1808        if (!srcFile.exists()) {
1809            throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
1810        }
1811        if (srcFile.isDirectory()) {
1812            throw new IOException("Source '" + srcFile + "' is a directory");
1813        }
1814        if (destFile.exists()) {
1815            throw new IOException("Destination '" + destFile + "' already exists");
1816        }
1817        if (destFile.isDirectory()) {
1818            throw new IOException("Destination '" + destFile + "' is a directory");
1819        }
1820        boolean rename = srcFile.renameTo(destFile);
1821        if (!rename) {
1822            copyFile( srcFile, destFile );
1823            if (!srcFile.delete()) {
1824                FileUtils.deleteQuietly(destFile);
1825                throw new IOException("Failed to delete original file '" + srcFile +
1826                        "' after copy to '" + destFile + "'");
1827            }
1828        }
1829    }
1830
1831    /**
1832     * Moves a file to a directory.
1833     *
1834     * @param srcFile the file to be moved
1835     * @param destDir the destination file
1836     * @param createDestDir If <code>true</code> create the destination directory,
1837     * otherwise if <code>false</code> throw an IOException
1838     * @throws NullPointerException if source or destination is <code>null</code>
1839     * @throws IOException if source or destination is invalid
1840     * @throws IOException if an IO error occurs moving the file
1841     * @since Commons IO 1.4
1842     */
1843    public static void moveFileToDirectory(File srcFile, File destDir, boolean createDestDir) throws IOException {
1844        if (srcFile == null) {
1845            throw new NullPointerException("Source must not be null");
1846        }
1847        if (destDir == null) {
1848            throw new NullPointerException("Destination directory must not be null");
1849        }
1850        if (!destDir.exists() && createDestDir) {
1851            destDir.mkdirs();
1852        }
1853        if (!destDir.exists()) {
1854            throw new FileNotFoundException("Destination directory '" + destDir +
1855                    "' does not exist [createDestDir=" + createDestDir +"]");
1856        }
1857        if (!destDir.isDirectory()) {
1858            throw new IOException("Destination '" + destDir + "' is not a directory");
1859        }
1860        moveFile(srcFile, new File(destDir, srcFile.getName()));
1861    }
1862
1863    /**
1864     * Moves a file or directory to the destination directory.
1865     * <p>
1866     * When the destination is on another file system, do a "copy and delete".
1867     *
1868     * @param src the file or directory to be moved
1869     * @param destDir the destination directory
1870     * @param createDestDir If <code>true</code> create the destination directory,
1871     * otherwise if <code>false</code> throw an IOException
1872     * @throws NullPointerException if source or destination is <code>null</code>
1873     * @throws IOException if source or destination is invalid
1874     * @throws IOException if an IO error occurs moving the file
1875     * @since Commons IO 1.4
1876     */
1877    public static void moveToDirectory(File src, File destDir, boolean createDestDir) throws IOException {
1878        if (src == null) {
1879            throw new NullPointerException("Source must not be null");
1880        }
1881        if (destDir == null) {
1882            throw new NullPointerException("Destination must not be null");
1883        }
1884        if (!src.exists()) {
1885            throw new FileNotFoundException("Source '" + src + "' does not exist");
1886        }
1887        if (src.isDirectory()) {
1888            moveDirectoryToDirectory(src, destDir, createDestDir);
1889        } else {
1890            moveFileToDirectory(src, destDir, createDestDir);
1891        }
1892    }
1893
1894}
1895