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