001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.io;
018    
019    import java.io.File;
020    import java.io.FileFilter;
021    import java.io.FileInputStream;
022    import java.io.FileNotFoundException;
023    import java.io.FileOutputStream;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.OutputStream;
027    import java.net.URL;
028    import java.util.ArrayList;
029    import java.util.Collection;
030    import java.util.Date;
031    import java.util.Iterator;
032    import java.util.List;
033    import java.util.zip.CRC32;
034    import java.util.zip.CheckedInputStream;
035    import java.util.zip.Checksum;
036    
037    import org.apache.commons.io.filefilter.DirectoryFileFilter;
038    import org.apache.commons.io.filefilter.FalseFileFilter;
039    import org.apache.commons.io.filefilter.FileFilterUtils;
040    import org.apache.commons.io.filefilter.IOFileFilter;
041    import org.apache.commons.io.filefilter.SuffixFileFilter;
042    import org.apache.commons.io.filefilter.TrueFileFilter;
043    import org.apache.commons.io.output.NullOutputStream;
044    
045    /**
046     * General file manipulation utilities.
047     * <p>
048     * Facilities are provided in the following areas:
049     * <ul>
050     * <li>writing to a file
051     * <li>reading from a file
052     * <li>make a directory including parent directories
053     * <li>copying files and directories
054     * <li>deleting files and directories
055     * <li>converting to and from a URL
056     * <li>listing files and directories by filter and extension
057     * <li>comparing file content
058     * <li>file last changed date
059     * <li>calculating a checksum
060     * </ul>
061     * <p>
062     * Origin of code: Excalibur, Alexandria, Commons-Utils
063     *
064     * @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A>
065     * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
066     * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
067     * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph.Reck</a>
068     * @author <a href="mailto:peter@apache.org">Peter Donald</a>
069     * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>
070     * @author Matthew Hawthorne
071     * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
072     * @author Stephen Colebourne
073     * @author Ian Springer
074     * @author Chris Eldredge
075     * @author Jim Harrington
076     * @author Niall Pemberton
077     * @author Sandy McArthur
078     * @version $Id: FileUtils.java 610810 2008-01-10 15:04:49Z niallp $
079     */
080    public class FileUtils {
081    
082        /**
083         * Instances should NOT be constructed in standard programming.
084         */
085        public FileUtils() {
086            super();
087        }
088    
089        /**
090         * The number of bytes in a kilobyte.
091         */
092        public static final long ONE_KB = 1024;
093    
094        /**
095         * The number of bytes in a megabyte.
096         */
097        public static final long ONE_MB = ONE_KB * ONE_KB;
098    
099        /**
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 files) {
239             return (File[]) 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 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 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 files = new java.util.LinkedList();
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 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 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 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].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 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(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 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 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 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 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 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 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 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    }