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 */
017package org.apache.commons.io;
018
019import java.io.BufferedOutputStream;
020import java.io.File;
021import java.io.FileFilter;
022import java.io.FileInputStream;
023import java.io.FileNotFoundException;
024import java.io.FileOutputStream;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.InputStreamReader;
028import java.io.OutputStream;
029import java.io.Reader;
030import java.io.UnsupportedEncodingException;
031import java.math.BigInteger;
032import java.net.URL;
033import java.net.URLConnection;
034import java.nio.ByteBuffer;
035import java.nio.channels.FileChannel;
036import java.nio.charset.Charset;
037import java.nio.charset.UnsupportedCharsetException;
038import java.util.ArrayList;
039import java.util.Collection;
040import java.util.Date;
041import java.util.Iterator;
042import java.util.List;
043import java.util.zip.CRC32;
044import java.util.zip.CheckedInputStream;
045import java.util.zip.Checksum;
046
047import org.apache.commons.io.filefilter.DirectoryFileFilter;
048import org.apache.commons.io.filefilter.FalseFileFilter;
049import org.apache.commons.io.filefilter.FileFilterUtils;
050import org.apache.commons.io.filefilter.IOFileFilter;
051import org.apache.commons.io.filefilter.SuffixFileFilter;
052import org.apache.commons.io.filefilter.TrueFileFilter;
053import org.apache.commons.io.output.NullOutputStream;
054
055/**
056 * General file manipulation utilities.
057 * <p>
058 * Facilities are provided in the following areas:
059 * <ul>
060 * <li>writing to a file
061 * <li>reading from a file
062 * <li>make a directory including parent directories
063 * <li>copying files and directories
064 * <li>deleting files and directories
065 * <li>converting to and from a URL
066 * <li>listing files and directories by filter and extension
067 * <li>comparing file content
068 * <li>file last changed date
069 * <li>calculating a checksum
070 * </ul>
071 * <p>
072 * Origin of code: Excalibur, Alexandria, Commons-Utils
073 *
074 * @version $Id: FileUtils.java 1349509 2012-06-12 20:39:23Z ggregory $
075 */
076public class FileUtils {
077
078    /**
079     * Instances should NOT be constructed in standard programming.
080     */
081    public FileUtils() {
082        super();
083    }
084
085    /**
086     * The number of bytes in a kilobyte.
087     */
088    public static final long ONE_KB = 1024;
089
090    /**
091     * The number of bytes in a kilobyte.
092     * 
093     * @since 2.4
094     */
095    public static final BigInteger ONE_KB_BI = BigInteger.valueOf(ONE_KB);
096
097    /**
098     * The number of bytes in a megabyte.
099     */
100    public static final long ONE_MB = ONE_KB * ONE_KB;
101
102    /**
103     * The number of bytes in a megabyte.
104     * 
105     * @since 2.4
106     */
107    public static final BigInteger ONE_MB_BI = ONE_KB_BI.multiply(ONE_KB_BI);
108
109    /**
110     * The file copy buffer size (30 MB)
111     */
112    private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30;
113
114    /**
115     * The number of bytes in a gigabyte.
116     */
117    public static final long ONE_GB = ONE_KB * ONE_MB;
118
119    /**
120     * The number of bytes in a gigabyte.
121     * 
122     * @since 2.4
123     */
124    public static final BigInteger ONE_GB_BI = ONE_KB_BI.multiply(ONE_MB_BI);
125
126    /**
127     * The number of bytes in a terabyte.
128     */
129    public static final long ONE_TB = ONE_KB * ONE_GB;
130
131    /**
132     * The number of bytes in a terabyte.
133     * 
134     * @since 2.4
135     */
136    public static final BigInteger ONE_TB_BI = ONE_KB_BI.multiply(ONE_GB_BI);
137
138    /**
139     * The number of bytes in a petabyte.
140     */
141    public static final long ONE_PB = ONE_KB * ONE_TB;
142
143    /**
144     * The number of bytes in a petabyte.
145     * 
146     * @since 2.4
147     */
148    public static final BigInteger ONE_PB_BI = ONE_KB_BI.multiply(ONE_TB_BI);
149
150    /**
151     * The number of bytes in an exabyte.
152     */
153    public static final long ONE_EB = ONE_KB * ONE_PB;
154
155    /**
156     * The number of bytes in an exabyte.
157     * 
158     * @since 2.4
159     */
160    public static final BigInteger ONE_EB_BI = ONE_KB_BI.multiply(ONE_PB_BI);
161
162    /**
163     * The number of bytes in a zettabyte.
164     */
165    public static final BigInteger ONE_ZB = BigInteger.valueOf(ONE_KB).multiply(BigInteger.valueOf(ONE_EB));
166
167    /**
168     * The number of bytes in a yottabyte.
169     */
170    public static final BigInteger ONE_YB = ONE_KB_BI.multiply(ONE_ZB);
171
172    /**
173     * An empty array of type <code>File</code>.
174     */
175    public static final File[] EMPTY_FILE_ARRAY = new File[0];
176
177    /**
178     * The UTF-8 character set, used to decode octets in URLs.
179     */
180    private static final Charset UTF8 = Charset.forName("UTF-8");
181
182    //-----------------------------------------------------------------------
183    /**
184     * Construct a file from the set of name elements.
185     * 
186     * @param directory the parent directory
187     * @param names the name elements
188     * @return the file
189     * @since 2.1
190     */
191    public static File getFile(File directory, String... names) {
192        if (directory == null) {
193            throw new NullPointerException("directorydirectory must not be null");
194        }
195        if (names == null) {
196            throw new NullPointerException("names must not be null");
197        }
198        File file = directory;
199        for (String name : names) {
200            file = new File(file, name);
201        }
202        return file;
203    }
204
205    /**
206     * Construct a file from the set of name elements.
207     * 
208     * @param names the name elements
209     * @return the file
210     * @since 2.1
211     */
212    public static File getFile(String... names) {
213        if (names == null) {
214            throw new NullPointerException("names must not be null");
215        }
216        File file = null;
217        for (String name : names) {
218            if (file == null) {
219                file = new File(name);
220            } else {
221                file = new File(file, name);
222            }
223        }
224        return file;
225    }
226
227    /**
228     * Returns the path to the system temporary directory.
229     * 
230     * @return the path to the system temporary directory.
231     * 
232     * @since 2.0
233     */
234    public static String getTempDirectoryPath() {
235        return System.getProperty("java.io.tmpdir");
236    }
237    
238    /**
239     * Returns a {@link File} representing the system temporary directory.
240     * 
241     * @return the system temporary directory. 
242     * 
243     * @since 2.0
244     */
245    public static File getTempDirectory() {
246        return new File(getTempDirectoryPath());
247    }
248    
249    /**
250     * Returns the path to the user's home directory.
251     * 
252     * @return the path to the user's home directory.
253     * 
254     * @since 2.0
255     */
256    public static String getUserDirectoryPath() {
257        return System.getProperty("user.home");
258    }
259    
260    /**
261     * Returns a {@link File} representing the user's home directory.
262     * 
263     * @return the user's home directory.
264     * 
265     * @since 2.0
266     */
267    public static File getUserDirectory() {
268        return new File(getUserDirectoryPath());
269    }
270    
271    //-----------------------------------------------------------------------
272    /**
273     * Opens a {@link FileInputStream} for the specified file, providing better
274     * error messages than simply calling <code>new FileInputStream(file)</code>.
275     * <p>
276     * At the end of the method either the stream will be successfully opened,
277     * or an exception will have been thrown.
278     * <p>
279     * An exception is thrown if the file does not exist.
280     * An exception is thrown if the file object exists but is a directory.
281     * An exception is thrown if the file exists but cannot be read.
282     * 
283     * @param file  the file to open for input, must not be {@code null}
284     * @return a new {@link FileInputStream} for the specified file
285     * @throws FileNotFoundException if the file does not exist
286     * @throws IOException if the file object is a directory
287     * @throws IOException if the file cannot be read
288     * @since 1.3
289     */
290    public static FileInputStream openInputStream(File file) throws IOException {
291        if (file.exists()) {
292            if (file.isDirectory()) {
293                throw new IOException("File '" + file + "' exists but is a directory");
294            }
295            if (file.canRead() == false) {
296                throw new IOException("File '" + file + "' cannot be read");
297            }
298        } else {
299            throw new FileNotFoundException("File '" + file + "' does not exist");
300        }
301        return new FileInputStream(file);
302    }
303
304    //-----------------------------------------------------------------------
305    /**
306     * Opens a {@link FileOutputStream} for the specified file, checking and
307     * creating the parent directory if it does not exist.
308     * <p>
309     * At the end of the method either the stream will be successfully opened,
310     * or an exception will have been thrown.
311     * <p>
312     * The parent directory will be created if it does not exist.
313     * The file will be created if it does not exist.
314     * An exception is thrown if the file object exists but is a directory.
315     * An exception is thrown if the file exists but cannot be written to.
316     * An exception is thrown if the parent directory cannot be created.
317     * 
318     * @param file  the file to open for output, must not be {@code null}
319     * @return a new {@link FileOutputStream} for the specified file
320     * @throws IOException if the file object is a directory
321     * @throws IOException if the file cannot be written to
322     * @throws IOException if a parent directory needs creating but that fails
323     * @since 1.3
324     */
325    public static FileOutputStream openOutputStream(File file) throws IOException {
326        return openOutputStream(file, false);
327    }
328
329    /**
330     * Opens a {@link FileOutputStream} for the specified file, checking and
331     * creating the parent directory if it does not exist.
332     * <p>
333     * At the end of the method either the stream will be successfully opened,
334     * or an exception will have been thrown.
335     * <p>
336     * The parent directory will be created if it does not exist.
337     * The file will be created if it does not exist.
338     * An exception is thrown if the file object exists but is a directory.
339     * An exception is thrown if the file exists but cannot be written to.
340     * An exception is thrown if the parent directory cannot be created.
341     * 
342     * @param file  the file to open for output, must not be {@code null}
343     * @param append if {@code true}, then bytes will be added to the
344     * end of the file rather than overwriting
345     * @return a new {@link FileOutputStream} for the specified file
346     * @throws IOException if the file object is a directory
347     * @throws IOException if the file cannot be written to
348     * @throws IOException if a parent directory needs creating but that fails
349     * @since 2.1
350     */
351    public static FileOutputStream openOutputStream(File file, boolean append) throws IOException {
352        if (file.exists()) {
353            if (file.isDirectory()) {
354                throw new IOException("File '" + file + "' exists but is a directory");
355            }
356            if (file.canWrite() == false) {
357                throw new IOException("File '" + file + "' cannot be written to");
358            }
359        } else {
360            File parent = file.getParentFile();
361            if (parent != null) {
362                if (!parent.mkdirs() && !parent.isDirectory()) {
363                    throw new IOException("Directory '" + parent + "' could not be created");
364                }
365            }
366        }
367        return new FileOutputStream(file, append);
368    }
369
370    //-----------------------------------------------------------------------
371    /**
372     * Returns a human-readable version of the file size, where the input represents a specific number of bytes.
373     * <p>
374     * If the size is over 1GB, the size is returned as the number of whole GB, i.e. the size is rounded down to the
375     * nearest GB boundary.
376     * </p>
377     * <p>
378     * Similarly for the 1MB and 1KB boundaries.
379     * </p>
380     * 
381     * @param size
382     *            the number of bytes
383     * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes)
384     * @see <a href="https://issues.apache.org/jira/browse/IO-226">IO-226 - should the rounding be changed?</a>
385     * @since 2.4
386     */
387    // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed?
388    public static String byteCountToDisplaySize(BigInteger size) {
389        String displaySize;
390
391        if (size.divide(ONE_EB_BI).compareTo(BigInteger.ZERO) > 0) {
392            displaySize = String.valueOf(size.divide(ONE_EB_BI)) + " EB";
393        } else if (size.divide(ONE_PB_BI).compareTo(BigInteger.ZERO) > 0) {
394            displaySize = String.valueOf(size.divide(ONE_PB_BI)) + " PB";
395        } else if (size.divide(ONE_TB_BI).compareTo(BigInteger.ZERO) > 0) {
396            displaySize = String.valueOf(size.divide(ONE_TB_BI)) + " TB";
397        } else if (size.divide(ONE_GB_BI).compareTo(BigInteger.ZERO) > 0) {
398            displaySize = String.valueOf(size.divide(ONE_GB_BI)) + " GB";
399        } else if (size.divide(ONE_MB_BI).compareTo(BigInteger.ZERO) > 0) {
400            displaySize = String.valueOf(size.divide(ONE_MB_BI)) + " MB";
401        } else if (size.divide(ONE_KB_BI).compareTo(BigInteger.ZERO) > 0) {
402            displaySize = String.valueOf(size.divide(ONE_KB_BI)) + " KB";
403        } else {
404            displaySize = String.valueOf(size) + " bytes";
405        }
406        return displaySize;
407    }
408
409    /**
410     * Returns a human-readable version of the file size, where the input represents a specific number of bytes.
411     * <p>
412     * If the size is over 1GB, the size is returned as the number of whole GB, i.e. the size is rounded down to the
413     * nearest GB boundary.
414     * </p>
415     * <p>
416     * Similarly for the 1MB and 1KB boundaries.
417     * </p>
418     * 
419     * @param size
420     *            the number of bytes
421     * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes)
422     * @see <a href="https://issues.apache.org/jira/browse/IO-226">IO-226 - should the rounding be changed?</a>
423     */
424    // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed?
425    public static String byteCountToDisplaySize(long size) {
426        return byteCountToDisplaySize(BigInteger.valueOf(size));
427    }
428
429    //-----------------------------------------------------------------------
430    /**
431     * Implements the same behaviour as the "touch" utility on Unix. It creates
432     * a new file with size 0 or, if the file exists already, it is opened and
433     * closed without modifying it, but updating the file date and time.
434     * <p>
435     * NOTE: As from v1.3, this method throws an IOException if the last
436     * modified date of the file cannot be set. Also, as from v1.3 this method
437     * creates parent directories if they do not exist.
438     *
439     * @param file  the File to touch
440     * @throws IOException If an I/O problem occurs
441     */
442    public static void touch(File file) throws IOException {
443        if (!file.exists()) {
444            OutputStream out = openOutputStream(file);
445            IOUtils.closeQuietly(out);
446        }
447        boolean success = file.setLastModified(System.currentTimeMillis());
448        if (!success) {
449            throw new IOException("Unable to set the last modification time for " + file);
450        }
451    }
452
453    //-----------------------------------------------------------------------
454    /**
455     * Converts a Collection containing java.io.File instanced into array
456     * representation. This is to account for the difference between
457     * File.listFiles() and FileUtils.listFiles().
458     *
459     * @param files  a Collection containing java.io.File instances
460     * @return an array of java.io.File
461     */
462    public static File[] convertFileCollectionToFileArray(Collection<File> files) {
463         return files.toArray(new File[files.size()]);
464    }
465
466    //-----------------------------------------------------------------------
467    /**
468     * Finds files within a given directory (and optionally its
469     * subdirectories). All files found are filtered by an IOFileFilter.
470     *
471     * @param files the collection of files found.
472     * @param directory the directory to search in.
473     * @param filter the filter to apply to files and directories.
474     * @param includeSubDirectories indicates if will include the subdirectories themselves
475     */
476    private static void innerListFiles(Collection<File> files, File directory,
477            IOFileFilter filter, boolean includeSubDirectories) {
478        File[] found = directory.listFiles((FileFilter) filter);
479        
480        if (found != null) {
481            for (File file : found) {
482                if (file.isDirectory()) {
483                    if (includeSubDirectories) {
484                        files.add(file);
485                    }
486                    innerListFiles(files, file, filter, includeSubDirectories);
487                } else {
488                    files.add(file);
489                }
490            }
491        }
492    }
493
494    /**
495     * Finds files within a given directory (and optionally its
496     * subdirectories). All files found are filtered by an IOFileFilter.
497     * <p>
498     * If your search should recurse into subdirectories you can pass in
499     * an IOFileFilter for directories. You don't need to bind a
500     * DirectoryFileFilter (via logical AND) to this filter. This method does
501     * that for you.
502     * <p>
503     * An example: If you want to search through all directories called
504     * "temp" you pass in <code>FileFilterUtils.NameFileFilter("temp")</code>
505     * <p>
506     * Another common usage of this method is find files in a directory
507     * tree but ignoring the directories generated CVS. You can simply pass
508     * in <code>FileFilterUtils.makeCVSAware(null)</code>.
509     *
510     * @param directory  the directory to search in
511     * @param fileFilter  filter to apply when finding files.
512     * @param dirFilter  optional filter to apply when finding subdirectories.
513     * If this parameter is {@code null}, subdirectories will not be included in the
514     * search. Use TrueFileFilter.INSTANCE to match all directories.
515     * @return an collection of java.io.File with the matching files
516     * @see org.apache.commons.io.filefilter.FileFilterUtils
517     * @see org.apache.commons.io.filefilter.NameFileFilter
518     */
519    public static Collection<File> listFiles(
520            File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
521        validateListFilesParameters(directory, fileFilter);
522
523        IOFileFilter effFileFilter = setUpEffectiveFileFilter(fileFilter);
524        IOFileFilter effDirFilter = setUpEffectiveDirFilter(dirFilter);
525
526        //Find files
527        Collection<File> files = new java.util.LinkedList<File>();
528        innerListFiles(files, directory,
529            FileFilterUtils.or(effFileFilter, effDirFilter), false);
530        return files;
531    }
532
533    /**
534     * Validates the given arguments.
535     * <ul>
536     * <li>Throws {@link IllegalArgumentException} if {@code directory} is not a directory</li>
537     * <li>Throws {@link NullPointerException} if {@code fileFilter} is null</li>
538     * </ul>
539     * 
540     * @param directory The File to test
541     * @param fileFilter The IOFileFilter to test
542     */
543    private static void validateListFilesParameters(File directory, IOFileFilter fileFilter) {
544        if (!directory.isDirectory()) {
545            throw new IllegalArgumentException("Parameter 'directory' is not a directory");
546        }
547        if (fileFilter == null) {
548            throw new NullPointerException("Parameter 'fileFilter' is null");
549        }
550    }
551
552    /**
553     * Returns a filter that accepts files in addition to the {@link File} objects accepted by the given filter.
554     * 
555     * @param fileFilter a base filter to add to
556     * @return a filter that accepts files 
557     */
558    private static IOFileFilter setUpEffectiveFileFilter(IOFileFilter fileFilter) {
559        return FileFilterUtils.and(fileFilter, FileFilterUtils.notFileFilter(DirectoryFileFilter.INSTANCE));
560    }
561
562    /**
563     * Returns a filter that accepts directories in addition to the {@link File} objects accepted by the given filter.
564     * 
565     * @param dirFilter a base filter to add to
566     * @return a filter that accepts directories 
567     */
568    private static IOFileFilter setUpEffectiveDirFilter(IOFileFilter dirFilter) {
569        return dirFilter == null ? FalseFileFilter.INSTANCE : FileFilterUtils.and(dirFilter,
570                DirectoryFileFilter.INSTANCE);
571    }
572
573    /**
574     * Finds files within a given directory (and optionally its
575     * subdirectories). All files found are filtered by an IOFileFilter.
576     * <p>
577     * The resulting collection includes the subdirectories themselves.
578     * <p>
579     * @see org.apache.commons.io.FileUtils#listFiles  
580     *
581     * @param directory  the directory to search in
582     * @param fileFilter  filter to apply when finding files.
583     * @param dirFilter  optional filter to apply when finding subdirectories.
584     * If this parameter is {@code null}, subdirectories will not be included in the
585     * search. Use TrueFileFilter.INSTANCE to match all directories.
586     * @return an collection of java.io.File with the matching files
587     * @see org.apache.commons.io.filefilter.FileFilterUtils
588     * @see org.apache.commons.io.filefilter.NameFileFilter
589     * @since 2.2
590     */
591    public static Collection<File> listFilesAndDirs(
592            File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
593        validateListFilesParameters(directory, fileFilter);
594
595        IOFileFilter effFileFilter = setUpEffectiveFileFilter(fileFilter);
596        IOFileFilter effDirFilter = setUpEffectiveDirFilter(dirFilter);
597
598        //Find files
599        Collection<File> files = new java.util.LinkedList<File>();
600        if (directory.isDirectory()) {
601            files.add(directory);
602        }
603        innerListFiles(files, directory,
604            FileFilterUtils.or(effFileFilter, effDirFilter), true);
605        return files;
606    }
607
608    /**
609     * Allows iteration over the files in given directory (and optionally
610     * its subdirectories).
611     * <p>
612     * All files found are filtered by an IOFileFilter. This method is
613     * based on {@link #listFiles(File, IOFileFilter, IOFileFilter)},
614     * which supports Iterable ('foreach' loop).
615     * <p>
616     * @param directory  the directory to search in
617     * @param fileFilter  filter to apply when finding files.
618     * @param dirFilter  optional filter to apply when finding subdirectories.
619     * If this parameter is {@code null}, subdirectories will not be included in the
620     * search. Use TrueFileFilter.INSTANCE to match all directories.
621     * @return an iterator of java.io.File for the matching files
622     * @see org.apache.commons.io.filefilter.FileFilterUtils
623     * @see org.apache.commons.io.filefilter.NameFileFilter
624     * @since 1.2
625     */
626    public static Iterator<File> iterateFiles(
627            File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
628        return listFiles(directory, fileFilter, dirFilter).iterator();
629    }
630
631    /**
632     * Allows iteration over the files in given directory (and optionally
633     * its subdirectories).
634     * <p>
635     * All files found are filtered by an IOFileFilter. This method is
636     * based on {@link #listFilesAndDirs(File, IOFileFilter, IOFileFilter)},
637     * which supports Iterable ('foreach' loop).
638     * <p>
639     * The resulting iterator includes the subdirectories themselves.
640     * 
641     * @param directory  the directory to search in
642     * @param fileFilter  filter to apply when finding files.
643     * @param dirFilter  optional filter to apply when finding subdirectories.
644     * If this parameter is {@code null}, subdirectories will not be included in the
645     * search. Use TrueFileFilter.INSTANCE to match all directories.
646     * @return an iterator of java.io.File for the matching files
647     * @see org.apache.commons.io.filefilter.FileFilterUtils
648     * @see org.apache.commons.io.filefilter.NameFileFilter
649     * @since 2.2
650     */
651    public static Iterator<File> iterateFilesAndDirs(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
652        return listFilesAndDirs(directory, fileFilter, dirFilter).iterator();
653    }
654
655    //-----------------------------------------------------------------------
656    /**
657     * Converts an array of file extensions to suffixes for use
658     * with IOFileFilters.
659     *
660     * @param extensions  an array of extensions. Format: {"java", "xml"}
661     * @return an array of suffixes. Format: {".java", ".xml"}
662     */
663    private static String[] toSuffixes(String[] extensions) {
664        String[] suffixes = new String[extensions.length];
665        for (int i = 0; i < extensions.length; i++) {
666            suffixes[i] = "." + extensions[i];
667        }
668        return suffixes;
669    }
670
671
672    /**
673     * Finds files within a given directory (and optionally its subdirectories)
674     * which match an array of extensions.
675     *
676     * @param directory  the directory to search in
677     * @param extensions  an array of extensions, ex. {"java","xml"}. If this
678     * parameter is {@code null}, all files are returned.
679     * @param recursive  if true all subdirectories are searched as well
680     * @return an collection of java.io.File with the matching files
681     */
682    public static Collection<File> listFiles(
683            File directory, String[] extensions, boolean recursive) {
684        IOFileFilter filter;
685        if (extensions == null) {
686            filter = TrueFileFilter.INSTANCE;
687        } else {
688            String[] suffixes = toSuffixes(extensions);
689            filter = new SuffixFileFilter(suffixes);
690        }
691        return listFiles(directory, filter,
692            recursive ? TrueFileFilter.INSTANCE : FalseFileFilter.INSTANCE);
693    }
694
695    /**
696     * Allows iteration over the files in a given directory (and optionally
697     * its subdirectories) which match an array of extensions. This method
698     * is based on {@link #listFiles(File, String[], boolean)},
699     * which supports Iterable ('foreach' loop).
700     *
701     * @param directory  the directory to search in
702     * @param extensions  an array of extensions, ex. {"java","xml"}. If this
703     * parameter is {@code null}, all files are returned.
704     * @param recursive  if true all subdirectories are searched as well
705     * @return an iterator of java.io.File with the matching files
706     * @since 1.2
707     */
708    public static Iterator<File> iterateFiles(
709            File directory, String[] extensions, boolean recursive) {
710        return listFiles(directory, extensions, recursive).iterator();
711    }
712
713    //-----------------------------------------------------------------------
714    /**
715     * Compares the contents of two files to determine if they are equal or not.
716     * <p>
717     * This method checks to see if the two files are different lengths
718     * or if they point to the same file, before resorting to byte-by-byte
719     * comparison of the contents.
720     * <p>
721     * Code origin: Avalon
722     *
723     * @param file1  the first file
724     * @param file2  the second file
725     * @return true if the content of the files are equal or they both don't
726     * exist, false otherwise
727     * @throws IOException in case of an I/O error
728     */
729    public static boolean contentEquals(File file1, File file2) throws IOException {
730        boolean file1Exists = file1.exists();
731        if (file1Exists != file2.exists()) {
732            return false;
733        }
734
735        if (!file1Exists) {
736            // two not existing files are equal
737            return true;
738        }
739
740        if (file1.isDirectory() || file2.isDirectory()) {
741            // don't want to compare directory contents
742            throw new IOException("Can't compare directories, only files");
743        }
744
745        if (file1.length() != file2.length()) {
746            // lengths differ, cannot be equal
747            return false;
748        }
749
750        if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) {
751            // same file
752            return true;
753        }
754
755        InputStream input1 = null;
756        InputStream input2 = null;
757        try {
758            input1 = new FileInputStream(file1);
759            input2 = new FileInputStream(file2);
760            return IOUtils.contentEquals(input1, input2);
761
762        } finally {
763            IOUtils.closeQuietly(input1);
764            IOUtils.closeQuietly(input2);
765        }
766    }
767
768    //-----------------------------------------------------------------------
769    /**
770     * Compares the contents of two files to determine if they are equal or not.
771     * <p>
772     * This method checks to see if the two files point to the same file, 
773     * before resorting to line-by-line comparison of the contents.
774     * <p>
775     *
776     * @param file1  the first file
777     * @param file2  the second file
778     * @param charsetName the character encoding to be used. 
779     *        May be null, in which case the platform default is used
780     * @return true if the content of the files are equal or neither exists,
781     *         false otherwise
782     * @throws IOException in case of an I/O error
783     * @since 2.2
784     * @see IOUtils#contentEqualsIgnoreEOL(Reader, Reader)
785     */
786    public static boolean contentEqualsIgnoreEOL(File file1, File file2, String charsetName) throws IOException {
787        boolean file1Exists = file1.exists();
788        if (file1Exists != file2.exists()) {
789            return false;
790        }
791
792        if (!file1Exists) {
793            // two not existing files are equal
794            return true;
795        }
796
797        if (file1.isDirectory() || file2.isDirectory()) {
798            // don't want to compare directory contents
799            throw new IOException("Can't compare directories, only files");
800        }
801
802        if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) {
803            // same file
804            return true;
805        }
806
807        Reader input1 = null;
808        Reader input2 = null;
809        try {
810            if (charsetName == null) {
811                input1 = new InputStreamReader(new FileInputStream(file1));
812                input2 = new InputStreamReader(new FileInputStream(file2));
813            } else {
814                input1 = new InputStreamReader(new FileInputStream(file1), charsetName);
815                input2 = new InputStreamReader(new FileInputStream(file2), charsetName);
816            }
817            return IOUtils.contentEqualsIgnoreEOL(input1, input2);
818
819        } finally {
820            IOUtils.closeQuietly(input1);
821            IOUtils.closeQuietly(input2);
822        }
823    }
824
825    //-----------------------------------------------------------------------
826    /**
827     * Convert from a <code>URL</code> to a <code>File</code>.
828     * <p>
829     * From version 1.1 this method will decode the URL.
830     * Syntax such as <code>file:///my%20docs/file.txt</code> will be
831     * correctly decoded to <code>/my docs/file.txt</code>. Starting with version
832     * 1.5, this method uses UTF-8 to decode percent-encoded octets to characters.
833     * Additionally, malformed percent-encoded octets are handled leniently by
834     * passing them through literally.
835     *
836     * @param url  the file URL to convert, {@code null} returns {@code null}
837     * @return the equivalent <code>File</code> object, or {@code null}
838     *  if the URL's protocol is not <code>file</code>
839     */
840    public static File toFile(URL url) {
841        if (url == null || !"file".equalsIgnoreCase(url.getProtocol())) {
842            return null;
843        } else {
844            String filename = url.getFile().replace('/', File.separatorChar);
845            filename = decodeUrl(filename);
846            return new File(filename);
847        }
848    }
849
850    /**
851     * Decodes the specified URL as per RFC 3986, i.e. transforms
852     * percent-encoded octets to characters by decoding with the UTF-8 character
853     * set. This function is primarily intended for usage with
854     * {@link java.net.URL} which unfortunately does not enforce proper URLs. As
855     * such, this method will leniently accept invalid characters or malformed
856     * percent-encoded octets and simply pass them literally through to the
857     * result string. Except for rare edge cases, this will make unencoded URLs
858     * pass through unaltered.
859     * 
860     * @param url  The URL to decode, may be {@code null}.
861     * @return The decoded URL or {@code null} if the input was
862     *         {@code null}.
863     */
864    static String decodeUrl(String url) {
865        String decoded = url;
866        if (url != null && url.indexOf('%') >= 0) {
867            int n = url.length();
868            StringBuffer buffer = new StringBuffer();
869            ByteBuffer bytes = ByteBuffer.allocate(n);
870            for (int i = 0; i < n;) {
871                if (url.charAt(i) == '%') {
872                    try {
873                        do {
874                            byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16);
875                            bytes.put(octet);
876                            i += 3;
877                        } while (i < n && url.charAt(i) == '%');
878                        continue;
879                    } catch (RuntimeException e) {
880                        // malformed percent-encoded octet, fall through and
881                        // append characters literally
882                    } finally {
883                        if (bytes.position() > 0) {
884                            bytes.flip();
885                            buffer.append(UTF8.decode(bytes).toString());
886                            bytes.clear();
887                        }
888                    }
889                }
890                buffer.append(url.charAt(i++));
891            }
892            decoded = buffer.toString();
893        }
894        return decoded;
895    }
896
897    /**
898     * Converts each of an array of <code>URL</code> to a <code>File</code>.
899     * <p>
900     * Returns an array of the same size as the input.
901     * If the input is {@code null}, an empty array is returned.
902     * If the input contains {@code null}, the output array contains {@code null} at the same
903     * index.
904     * <p>
905     * This method will decode the URL.
906     * Syntax such as <code>file:///my%20docs/file.txt</code> will be
907     * correctly decoded to <code>/my docs/file.txt</code>.
908     *
909     * @param urls  the file URLs to convert, {@code null} returns empty array
910     * @return a non-{@code null} array of Files matching the input, with a {@code null} item
911     *  if there was a {@code null} at that index in the input array
912     * @throws IllegalArgumentException if any file is not a URL file
913     * @throws IllegalArgumentException if any file is incorrectly encoded
914     * @since 1.1
915     */
916    public static File[] toFiles(URL[] urls) {
917        if (urls == null || urls.length == 0) {
918            return EMPTY_FILE_ARRAY;
919        }
920        File[] files = new File[urls.length];
921        for (int i = 0; i < urls.length; i++) {
922            URL url = urls[i];
923            if (url != null) {
924                if (url.getProtocol().equals("file") == false) {
925                    throw new IllegalArgumentException(
926                            "URL could not be converted to a File: " + url);
927                }
928                files[i] = toFile(url);
929            }
930        }
931        return files;
932    }
933
934    /**
935     * Converts each of an array of <code>File</code> to a <code>URL</code>.
936     * <p>
937     * Returns an array of the same size as the input.
938     *
939     * @param files  the files to convert, must not be {@code null}
940     * @return an array of URLs matching the input
941     * @throws IOException if a file cannot be converted
942     * @throws NullPointerException if the parameter is null
943     */
944    public static URL[] toURLs(File[] files) throws IOException {
945        URL[] urls = new URL[files.length];
946
947        for (int i = 0; i < urls.length; i++) {
948            urls[i] = files[i].toURI().toURL();
949        }
950
951        return urls;
952    }
953
954    //-----------------------------------------------------------------------
955    /**
956     * Copies a file to a directory preserving the file date.
957     * <p>
958     * This method copies the contents of the specified source file
959     * to a file of the same name in the specified destination directory.
960     * The destination directory is created if it does not exist.
961     * If the destination file exists, then this method will overwrite it.
962     * <p>
963     * <strong>Note:</strong> This method tries to preserve the file's last
964     * modified date/times using {@link File#setLastModified(long)}, however
965     * it is not guaranteed that the operation will succeed.
966     * If the modification operation fails, no indication is provided.
967     *
968     * @param srcFile  an existing file to copy, must not be {@code null}
969     * @param destDir  the directory to place the copy in, must not be {@code null}
970     *
971     * @throws NullPointerException if source or destination is null
972     * @throws IOException if source or destination is invalid
973     * @throws IOException if an IO error occurs during copying
974     * @see #copyFile(File, File, boolean)
975     */
976    public static void copyFileToDirectory(File srcFile, File destDir) throws IOException {
977        copyFileToDirectory(srcFile, destDir, true);
978    }
979
980    /**
981     * Copies a file to a directory optionally preserving the file date.
982     * <p>
983     * This method copies the contents of the specified source file
984     * to a file of the same name in the specified destination directory.
985     * The destination directory is created if it does not exist.
986     * If the destination file exists, then this method will overwrite it.
987     * <p>
988     * <strong>Note:</strong> Setting <code>preserveFileDate</code> to
989     * {@code true} tries to preserve the file's last modified
990     * date/times using {@link File#setLastModified(long)}, however it is
991     * not guaranteed that the operation will succeed.
992     * If the modification operation fails, no indication is provided.
993     *
994     * @param srcFile  an existing file to copy, must not be {@code null}
995     * @param destDir  the directory to place the copy in, must not be {@code null}
996     * @param preserveFileDate  true if the file date of the copy
997     *  should be the same as the original
998     *
999     * @throws NullPointerException if source or destination is {@code null}
1000     * @throws IOException if source or destination is invalid
1001     * @throws IOException if an IO error occurs during copying
1002     * @see #copyFile(File, File, boolean)
1003     * @since 1.3
1004     */
1005    public static void copyFileToDirectory(File srcFile, File destDir, boolean preserveFileDate) throws IOException {
1006        if (destDir == null) {
1007            throw new NullPointerException("Destination must not be null");
1008        }
1009        if (destDir.exists() && destDir.isDirectory() == false) {
1010            throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory");
1011        }
1012        File destFile = new File(destDir, srcFile.getName());
1013        copyFile(srcFile, destFile, preserveFileDate);
1014    }
1015
1016    /**
1017     * Copies a file to a new location preserving the file date.
1018     * <p>
1019     * This method copies the contents of the specified source file to the
1020     * specified destination file. The directory holding the destination file is
1021     * created if it does not exist. If the destination file exists, then this
1022     * method will overwrite it.
1023     * <p>
1024     * <strong>Note:</strong> This method tries to preserve the file's last
1025     * modified date/times using {@link File#setLastModified(long)}, however
1026     * it is not guaranteed that the operation will succeed.
1027     * If the modification operation fails, no indication is provided.
1028     * 
1029     * @param srcFile  an existing file to copy, must not be {@code null}
1030     * @param destFile  the new file, must not be {@code null}
1031     * 
1032     * @throws NullPointerException if source or destination is {@code null}
1033     * @throws IOException if source or destination is invalid
1034     * @throws IOException if an IO error occurs during copying
1035     * @see #copyFileToDirectory(File, File)
1036     */
1037    public static void copyFile(File srcFile, File destFile) throws IOException {
1038        copyFile(srcFile, destFile, true);
1039    }
1040
1041    /**
1042     * Copies a file to a new location.
1043     * <p>
1044     * This method copies the contents of the specified source file
1045     * to the specified destination file.
1046     * The directory holding the destination file is created if it does not exist.
1047     * If the destination file exists, then this method will overwrite it.
1048     * <p>
1049     * <strong>Note:</strong> Setting <code>preserveFileDate</code> to
1050     * {@code true} tries to preserve the file's last modified
1051     * date/times using {@link File#setLastModified(long)}, however it is
1052     * not guaranteed that the operation will succeed.
1053     * If the modification operation fails, no indication is provided.
1054     *
1055     * @param srcFile  an existing file to copy, must not be {@code null}
1056     * @param destFile  the new file, must not be {@code null}
1057     * @param preserveFileDate  true if the file date of the copy
1058     *  should be the same as the original
1059     *
1060     * @throws NullPointerException if source or destination is {@code null}
1061     * @throws IOException if source or destination is invalid
1062     * @throws IOException if an IO error occurs during copying
1063     * @see #copyFileToDirectory(File, File, boolean)
1064     */
1065    public static void copyFile(File srcFile, File destFile,
1066            boolean preserveFileDate) throws IOException {
1067        if (srcFile == null) {
1068            throw new NullPointerException("Source must not be null");
1069        }
1070        if (destFile == null) {
1071            throw new NullPointerException("Destination must not be null");
1072        }
1073        if (srcFile.exists() == false) {
1074            throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
1075        }
1076        if (srcFile.isDirectory()) {
1077            throw new IOException("Source '" + srcFile + "' exists but is a directory");
1078        }
1079        if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) {
1080            throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same");
1081        }
1082        File parentFile = destFile.getParentFile();
1083        if (parentFile != null) {
1084            if (!parentFile.mkdirs() && !parentFile.isDirectory()) {
1085                throw new IOException("Destination '" + parentFile + "' directory cannot be created");
1086            }
1087        }
1088        if (destFile.exists() && destFile.canWrite() == false) {
1089            throw new IOException("Destination '" + destFile + "' exists but is read-only");
1090        }
1091        doCopyFile(srcFile, destFile, preserveFileDate);
1092    }
1093
1094    /**
1095     * Copy bytes from a <code>File</code> to an <code>OutputStream</code>.
1096     * <p>
1097     * This method buffers the input internally, so there is no need to use a <code>BufferedInputStream</code>.
1098     * </p>
1099     * 
1100     * @param input
1101     *            the <code>File</code> to read from
1102     * @param output
1103     *            the <code>OutputStream</code> to write to
1104     * @return the number of bytes copied
1105     * @throws NullPointerException
1106     *             if the input or output is null
1107     * @throws IOException
1108     *             if an I/O error occurs
1109     * @since 2.1
1110     */
1111    public static long copyFile(File input, OutputStream output) throws IOException {
1112        final FileInputStream fis = new FileInputStream(input);
1113        try {
1114            return IOUtils.copyLarge(fis, output);
1115        } finally {
1116            fis.close();
1117        }
1118    }
1119    
1120    /**
1121     * Internal copy file method.
1122     * 
1123     * @param srcFile  the validated source file, must not be {@code null}
1124     * @param destFile  the validated destination file, must not be {@code null}
1125     * @param preserveFileDate  whether to preserve the file date
1126     * @throws IOException if an error occurs
1127     */
1128    private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException {
1129        if (destFile.exists() && destFile.isDirectory()) {
1130            throw new IOException("Destination '" + destFile + "' exists but is a directory");
1131        }
1132
1133        FileInputStream fis = null;
1134        FileOutputStream fos = null;
1135        FileChannel input = null;
1136        FileChannel output = null;
1137        try {
1138            fis = new FileInputStream(srcFile);
1139            fos = new FileOutputStream(destFile);
1140            input  = fis.getChannel();
1141            output = fos.getChannel();
1142            long size = input.size();
1143            long pos = 0;
1144            long count = 0;
1145            while (pos < size) {
1146                count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : size - pos;
1147                pos += output.transferFrom(input, pos, count);
1148            }
1149        } finally {
1150            IOUtils.closeQuietly(output);
1151            IOUtils.closeQuietly(fos);
1152            IOUtils.closeQuietly(input);
1153            IOUtils.closeQuietly(fis);
1154        }
1155
1156        if (srcFile.length() != destFile.length()) {
1157            throw new IOException("Failed to copy full contents from '" +
1158                    srcFile + "' to '" + destFile + "'");
1159        }
1160        if (preserveFileDate) {
1161            destFile.setLastModified(srcFile.lastModified());
1162        }
1163    }
1164
1165    //-----------------------------------------------------------------------
1166    /**
1167     * Copies a directory to within another directory preserving the file dates.
1168     * <p>
1169     * This method copies the source directory and all its contents to a
1170     * directory of the same name in the specified destination directory.
1171     * <p>
1172     * The destination directory is created if it does not exist.
1173     * If the destination directory did exist, then this method merges
1174     * the source with the destination, with the source taking precedence.
1175     * <p>
1176     * <strong>Note:</strong> This method tries to preserve the files' last
1177     * modified date/times using {@link File#setLastModified(long)}, however
1178     * it is not guaranteed that those operations will succeed.
1179     * If the modification operation fails, no indication is provided.
1180     *
1181     * @param srcDir  an existing directory to copy, must not be {@code null}
1182     * @param destDir  the directory to place the copy in, must not be {@code null}
1183     *
1184     * @throws NullPointerException if source or destination is {@code null}
1185     * @throws IOException if source or destination is invalid
1186     * @throws IOException if an IO error occurs during copying
1187     * @since 1.2
1188     */
1189    public static void copyDirectoryToDirectory(File srcDir, File destDir) throws IOException {
1190        if (srcDir == null) {
1191            throw new NullPointerException("Source must not be null");
1192        }
1193        if (srcDir.exists() && srcDir.isDirectory() == false) {
1194            throw new IllegalArgumentException("Source '" + destDir + "' is not a directory");
1195        }
1196        if (destDir == null) {
1197            throw new NullPointerException("Destination must not be null");
1198        }
1199        if (destDir.exists() && destDir.isDirectory() == false) {
1200            throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory");
1201        }
1202        copyDirectory(srcDir, new File(destDir, srcDir.getName()), true);
1203    }
1204
1205    /**
1206     * Copies a whole directory to a new location preserving the file dates.
1207     * <p>
1208     * This method copies the specified directory and all its child
1209     * directories and files to the specified destination.
1210     * The destination is the new location and name of the directory.
1211     * <p>
1212     * The destination directory is created if it does not exist.
1213     * If the destination directory did exist, then this method merges
1214     * the source with the destination, with the source taking precedence.
1215     * <p>
1216     * <strong>Note:</strong> This method tries to preserve the files' last
1217     * modified date/times using {@link File#setLastModified(long)}, however
1218     * it is not guaranteed that those operations will succeed.
1219     * If the modification operation fails, no indication is provided.
1220     *
1221     * @param srcDir  an existing directory to copy, must not be {@code null}
1222     * @param destDir  the new directory, must not be {@code null}
1223     *
1224     * @throws NullPointerException if source or destination is {@code null}
1225     * @throws IOException if source or destination is invalid
1226     * @throws IOException if an IO error occurs during copying
1227     * @since 1.1
1228     */
1229    public static void copyDirectory(File srcDir, File destDir) throws IOException {
1230        copyDirectory(srcDir, destDir, true);
1231    }
1232
1233    /**
1234     * Copies a whole directory to a new location.
1235     * <p>
1236     * This method copies the contents of the specified source directory
1237     * to within the specified destination directory.
1238     * <p>
1239     * The destination directory is created if it does not exist.
1240     * If the destination directory did exist, then this method merges
1241     * the source with the destination, with the source taking precedence.
1242     * <p>
1243     * <strong>Note:</strong> Setting <code>preserveFileDate</code> to
1244     * {@code true} tries to preserve the files' last modified
1245     * date/times using {@link File#setLastModified(long)}, however it is
1246     * not guaranteed that those operations will succeed.
1247     * If the modification operation fails, no indication is provided.
1248     *
1249     * @param srcDir  an existing directory to copy, must not be {@code null}
1250     * @param destDir  the new directory, must not be {@code null}
1251     * @param preserveFileDate  true if the file date of the copy
1252     *  should be the same as the original
1253     *
1254     * @throws NullPointerException if source or destination is {@code null}
1255     * @throws IOException if source or destination is invalid
1256     * @throws IOException if an IO error occurs during copying
1257     * @since 1.1
1258     */
1259    public static void copyDirectory(File srcDir, File destDir,
1260            boolean preserveFileDate) throws IOException {
1261        copyDirectory(srcDir, destDir, null, preserveFileDate);
1262    }
1263
1264    /**
1265     * Copies a filtered directory to a new location preserving the file dates.
1266     * <p>
1267     * This method copies the contents of the specified source directory
1268     * to within the specified destination directory.
1269     * <p>
1270     * The destination directory is created if it does not exist.
1271     * If the destination directory did exist, then this method merges
1272     * the source with the destination, with the source taking precedence.
1273     * <p>
1274     * <strong>Note:</strong> This method tries to preserve the files' last
1275     * modified date/times using {@link File#setLastModified(long)}, however
1276     * it is not guaranteed that those operations will succeed.
1277     * If the modification operation fails, no indication is provided.
1278     *
1279     * <h4>Example: Copy directories only</h4> 
1280     *  <pre>
1281     *  // only copy the directory structure
1282     *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY);
1283     *  </pre>
1284     *
1285     * <h4>Example: Copy directories and txt files</h4>
1286     *  <pre>
1287     *  // Create a filter for ".txt" files
1288     *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
1289     *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
1290     *
1291     *  // Create a filter for either directories or ".txt" files
1292     *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
1293     *
1294     *  // Copy using the filter
1295     *  FileUtils.copyDirectory(srcDir, destDir, filter);
1296     *  </pre>
1297     *
1298     * @param srcDir  an existing directory to copy, must not be {@code null}
1299     * @param destDir  the new directory, must not be {@code null}
1300     * @param filter  the filter to apply, null means copy all directories and files
1301     *  should be the same as the original
1302     *
1303     * @throws NullPointerException if source or destination is {@code null}
1304     * @throws IOException if source or destination is invalid
1305     * @throws IOException if an IO error occurs during copying
1306     * @since 1.4
1307     */
1308    public static void copyDirectory(File srcDir, File destDir,
1309            FileFilter filter) throws IOException {
1310        copyDirectory(srcDir, destDir, filter, true);
1311    }
1312
1313    /**
1314     * Copies a filtered directory to a new location.
1315     * <p>
1316     * This method copies the contents of the specified source directory
1317     * to within the specified destination directory.
1318     * <p>
1319     * The destination directory is created if it does not exist.
1320     * If the destination directory did exist, then this method merges
1321     * the source with the destination, with the source taking precedence.
1322     * <p>
1323     * <strong>Note:</strong> Setting <code>preserveFileDate</code> to
1324     * {@code true} tries to preserve the files' last modified
1325     * date/times using {@link File#setLastModified(long)}, however it is
1326     * not guaranteed that those operations will succeed.
1327     * If the modification operation fails, no indication is provided.
1328     *
1329     * <h4>Example: Copy directories only</h4> 
1330     *  <pre>
1331     *  // only copy the directory structure
1332     *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
1333     *  </pre>
1334     *
1335     * <h4>Example: Copy directories and txt files</h4>
1336     *  <pre>
1337     *  // Create a filter for ".txt" files
1338     *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
1339     *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
1340     *
1341     *  // Create a filter for either directories or ".txt" files
1342     *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
1343     *
1344     *  // Copy using the filter
1345     *  FileUtils.copyDirectory(srcDir, destDir, filter, false);
1346     *  </pre>
1347     * 
1348     * @param srcDir  an existing directory to copy, must not be {@code null}
1349     * @param destDir  the new directory, must not be {@code null}
1350     * @param filter  the filter to apply, null means copy all directories and files
1351     * @param preserveFileDate  true if the file date of the copy
1352     *  should be the same as the original
1353     *
1354     * @throws NullPointerException if source or destination is {@code null}
1355     * @throws IOException if source or destination is invalid
1356     * @throws IOException if an IO error occurs during copying
1357     * @since 1.4
1358     */
1359    public static void copyDirectory(File srcDir, File destDir,
1360            FileFilter filter, boolean preserveFileDate) throws IOException {
1361        if (srcDir == null) {
1362            throw new NullPointerException("Source must not be null");
1363        }
1364        if (destDir == null) {
1365            throw new NullPointerException("Destination must not be null");
1366        }
1367        if (srcDir.exists() == false) {
1368            throw new FileNotFoundException("Source '" + srcDir + "' does not exist");
1369        }
1370        if (srcDir.isDirectory() == false) {
1371            throw new IOException("Source '" + srcDir + "' exists but is not a directory");
1372        }
1373        if (srcDir.getCanonicalPath().equals(destDir.getCanonicalPath())) {
1374            throw new IOException("Source '" + srcDir + "' and destination '" + destDir + "' are the same");
1375        }
1376
1377        // Cater for destination being directory within the source directory (see IO-141)
1378        List<String> exclusionList = null;
1379        if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) {
1380            File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
1381            if (srcFiles != null && srcFiles.length > 0) {
1382                exclusionList = new ArrayList<String>(srcFiles.length);
1383                for (File srcFile : srcFiles) {
1384                    File copiedFile = new File(destDir, srcFile.getName());
1385                    exclusionList.add(copiedFile.getCanonicalPath());
1386                }
1387            }
1388        }
1389        doCopyDirectory(srcDir, destDir, filter, preserveFileDate, exclusionList);
1390    }
1391
1392    /**
1393     * Internal copy directory method.
1394     * 
1395     * @param srcDir  the validated source directory, must not be {@code null}
1396     * @param destDir  the validated destination directory, must not be {@code null}
1397     * @param filter  the filter to apply, null means copy all directories and files
1398     * @param preserveFileDate  whether to preserve the file date
1399     * @param exclusionList  List of files and directories to exclude from the copy, may be null
1400     * @throws IOException if an error occurs
1401     * @since 1.1
1402     */
1403    private static void doCopyDirectory(File srcDir, File destDir, FileFilter filter,
1404            boolean preserveFileDate, List<String> exclusionList) throws IOException {
1405        // recurse
1406        File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
1407        if (srcFiles == null) {  // null if abstract pathname does not denote a directory, or if an I/O error occurs
1408            throw new IOException("Failed to list contents of " + srcDir);
1409        }
1410        if (destDir.exists()) {
1411            if (destDir.isDirectory() == false) {
1412                throw new IOException("Destination '" + destDir + "' exists but is not a directory");
1413            }
1414        } else {
1415            if (!destDir.mkdirs() && !destDir.isDirectory()) {
1416                throw new IOException("Destination '" + destDir + "' directory cannot be created");
1417            }
1418        }
1419        if (destDir.canWrite() == false) {
1420            throw new IOException("Destination '" + destDir + "' cannot be written to");
1421        }
1422        for (File srcFile : srcFiles) {
1423            File dstFile = new File(destDir, srcFile.getName());
1424            if (exclusionList == null || !exclusionList.contains(srcFile.getCanonicalPath())) {
1425                if (srcFile.isDirectory()) {
1426                    doCopyDirectory(srcFile, dstFile, filter, preserveFileDate, exclusionList);
1427                } else {
1428                    doCopyFile(srcFile, dstFile, preserveFileDate);
1429                }
1430            }
1431        }
1432
1433        // Do this last, as the above has probably affected directory metadata
1434        if (preserveFileDate) {
1435            destDir.setLastModified(srcDir.lastModified());
1436        }
1437    }
1438
1439    //-----------------------------------------------------------------------
1440    /**
1441     * Copies bytes from the URL <code>source</code> to a file
1442     * <code>destination</code>. The directories up to <code>destination</code>
1443     * will be created if they don't already exist. <code>destination</code>
1444     * will be overwritten if it already exists.
1445     * <p>
1446     * Warning: this method does not set a connection or read timeout and thus
1447     * might block forever. Use {@link #copyURLToFile(URL, File, int, int)}
1448     * with reasonable timeouts to prevent this.
1449     *
1450     * @param source  the <code>URL</code> to copy bytes from, must not be {@code null}
1451     * @param destination  the non-directory <code>File</code> to write bytes to
1452     *  (possibly overwriting), must not be {@code null}
1453     * @throws IOException if <code>source</code> URL cannot be opened
1454     * @throws IOException if <code>destination</code> is a directory
1455     * @throws IOException if <code>destination</code> cannot be written
1456     * @throws IOException if <code>destination</code> needs creating but can't be
1457     * @throws IOException if an IO error occurs during copying
1458     */
1459    public static void copyURLToFile(URL source, File destination) throws IOException {
1460        InputStream input = source.openStream();
1461        copyInputStreamToFile(input, destination);
1462    }
1463
1464    /**
1465     * Copies bytes from the URL <code>source</code> to a file
1466     * <code>destination</code>. The directories up to <code>destination</code>
1467     * will be created if they don't already exist. <code>destination</code>
1468     * will be overwritten if it already exists.
1469     *
1470     * @param source  the <code>URL</code> to copy bytes from, must not be {@code null}
1471     * @param destination  the non-directory <code>File</code> to write bytes to
1472     *  (possibly overwriting), must not be {@code null}
1473     * @param connectionTimeout the number of milliseconds until this method
1474     *  will timeout if no connection could be established to the <code>source</code>
1475     * @param readTimeout the number of milliseconds until this method will
1476     *  timeout if no data could be read from the <code>source</code> 
1477     * @throws IOException if <code>source</code> URL cannot be opened
1478     * @throws IOException if <code>destination</code> is a directory
1479     * @throws IOException if <code>destination</code> cannot be written
1480     * @throws IOException if <code>destination</code> needs creating but can't be
1481     * @throws IOException if an IO error occurs during copying
1482     * @since 2.0
1483     */
1484    public static void copyURLToFile(URL source, File destination,
1485            int connectionTimeout, int readTimeout) throws IOException {
1486        URLConnection connection = source.openConnection();
1487        connection.setConnectTimeout(connectionTimeout);
1488        connection.setReadTimeout(readTimeout);
1489        InputStream input = connection.getInputStream();
1490        copyInputStreamToFile(input, destination);
1491    }
1492
1493    /**
1494     * Copies bytes from an {@link InputStream} <code>source</code> to a file
1495     * <code>destination</code>. The directories up to <code>destination</code>
1496     * will be created if they don't already exist. <code>destination</code>
1497     * will be overwritten if it already exists.
1498     *
1499     * @param source  the <code>InputStream</code> to copy bytes from, must not be {@code null}
1500     * @param destination  the non-directory <code>File</code> to write bytes to
1501     *  (possibly overwriting), must not be {@code null}
1502     * @throws IOException if <code>destination</code> is a directory
1503     * @throws IOException if <code>destination</code> cannot be written
1504     * @throws IOException if <code>destination</code> needs creating but can't be
1505     * @throws IOException if an IO error occurs during copying
1506     * @since 2.0
1507     */
1508    public static void copyInputStreamToFile(InputStream source, File destination) throws IOException {
1509        try {
1510            FileOutputStream output = openOutputStream(destination);
1511            try {
1512                IOUtils.copy(source, output);
1513                output.close(); // don't swallow close Exception if copy completes normally
1514            } finally {
1515                IOUtils.closeQuietly(output);
1516            }
1517        } finally {
1518            IOUtils.closeQuietly(source);
1519        }
1520    }
1521
1522    //-----------------------------------------------------------------------
1523    /**
1524     * Deletes a directory recursively. 
1525     *
1526     * @param directory  directory to delete
1527     * @throws IOException in case deletion is unsuccessful
1528     */
1529    public static void deleteDirectory(File directory) throws IOException {
1530        if (!directory.exists()) {
1531            return;
1532        }
1533
1534        if (!isSymlink(directory)) {
1535            cleanDirectory(directory);
1536        }
1537
1538        if (!directory.delete()) {
1539            String message =
1540                "Unable to delete directory " + directory + ".";
1541            throw new IOException(message);
1542        }
1543    }
1544
1545    /**
1546     * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories.
1547     * <p>
1548     * The difference between File.delete() and this method are:
1549     * <ul>
1550     * <li>A directory to be deleted does not have to be empty.</li>
1551     * <li>No exceptions are thrown when a file or directory cannot be deleted.</li>
1552     * </ul>
1553     *
1554     * @param file  file or directory to delete, can be {@code null}
1555     * @return {@code true} if the file or directory was deleted, otherwise
1556     * {@code false}
1557     *
1558     * @since 1.4
1559     */
1560    public static boolean deleteQuietly(File file) {
1561        if (file == null) {
1562            return false;
1563        }
1564        try {
1565            if (file.isDirectory()) {
1566                cleanDirectory(file);
1567            }
1568        } catch (Exception ignored) {
1569        }
1570
1571        try {
1572            return file.delete();
1573        } catch (Exception ignored) {
1574            return false;
1575        }
1576    }
1577
1578    /**
1579     * Determines whether the {@code parent} directory contains the {@code child} element (a file or directory).
1580     * <p>
1581     * Files are normalized before comparison.
1582     * </p>
1583     * 
1584     * Edge cases:
1585     * <ul>
1586     * <li>A {@code directory} must not be null: if null, throw IllegalArgumentException</li>
1587     * <li>A {@code directory} must be a directory: if not a directory, throw IllegalArgumentException</li>
1588     * <li>A directory does not contain itself: return false</li>
1589     * <li>A null child file is not contained in any parent: return false</li>
1590     * </ul>
1591     * 
1592     * @param directory
1593     *            the file to consider as the parent.
1594     * @param child
1595     *            the file to consider as the child.
1596     * @return true is the candidate leaf is under by the specified composite. False otherwise.
1597     * @throws IOException
1598     *             if an IO error occurs while checking the files.
1599     * @since 2.2
1600     * @see FilenameUtils#directoryContains(String, String)
1601     */
1602    public static boolean directoryContains(final File directory, final File child) throws IOException {
1603        
1604        // Fail fast against NullPointerException
1605        if (directory == null) {
1606            throw new IllegalArgumentException("Directory must not be null");
1607        }
1608    
1609        if (!directory.isDirectory()) {
1610            throw new IllegalArgumentException("Not a directory: " + directory);
1611        }
1612    
1613        if (child == null) {
1614            return false;
1615        }
1616    
1617        if (!directory.exists() || !child.exists()) {
1618            return false;
1619        }
1620    
1621        // Canonicalize paths (normalizes relative paths)
1622        String canonicalParent = directory.getCanonicalPath();
1623        String canonicalChild = child.getCanonicalPath();
1624    
1625        return FilenameUtils.directoryContains(canonicalParent, canonicalChild);
1626    }
1627
1628    /**
1629     * Cleans a directory without deleting it.
1630     *
1631     * @param directory directory to clean
1632     * @throws IOException in case cleaning is unsuccessful
1633     */
1634    public static void cleanDirectory(File directory) throws IOException {
1635        if (!directory.exists()) {
1636            String message = directory + " does not exist";
1637            throw new IllegalArgumentException(message);
1638        }
1639
1640        if (!directory.isDirectory()) {
1641            String message = directory + " is not a directory";
1642            throw new IllegalArgumentException(message);
1643        }
1644
1645        File[] files = directory.listFiles();
1646        if (files == null) {  // null if security restricted
1647            throw new IOException("Failed to list contents of " + directory);
1648        }
1649
1650        IOException exception = null;
1651        for (File file : files) {
1652            try {
1653                forceDelete(file);
1654            } catch (IOException ioe) {
1655                exception = ioe;
1656            }
1657        }
1658
1659        if (null != exception) {
1660            throw exception;
1661        }
1662    }
1663
1664    //-----------------------------------------------------------------------
1665    /**
1666     * Waits for NFS to propagate a file creation, imposing a timeout.
1667     * <p>
1668     * This method repeatedly tests {@link File#exists()} until it returns
1669     * true up to the maximum time specified in seconds.
1670     *
1671     * @param file  the file to check, must not be {@code null}
1672     * @param seconds  the maximum time in seconds to wait
1673     * @return true if file exists
1674     * @throws NullPointerException if the file is {@code null}
1675     */
1676    public static boolean waitFor(File file, int seconds) {
1677        int timeout = 0;
1678        int tick = 0;
1679        while (!file.exists()) {
1680            if (tick++ >= 10) {
1681                tick = 0;
1682                if (timeout++ > seconds) {
1683                    return false;
1684                }
1685            }
1686            try {
1687                Thread.sleep(100);
1688            } catch (InterruptedException ignore) {
1689                // ignore exception
1690            } catch (Exception ex) {
1691                break;
1692            }
1693        }
1694        return true;
1695    }
1696
1697    //-----------------------------------------------------------------------
1698    /**
1699     * Reads the contents of a file into a String.
1700     * The file is always closed.
1701     *
1702     * @param file  the file to read, must not be {@code null}
1703     * @param encoding  the encoding to use, {@code null} means platform default
1704     * @return the file contents, never {@code null}
1705     * @throws IOException in case of an I/O error
1706     * @since 2.3
1707     */
1708    public static String readFileToString(File file, Charset encoding) throws IOException {
1709        InputStream in = null;
1710        try {
1711            in = openInputStream(file);
1712            return IOUtils.toString(in, Charsets.toCharset(encoding));
1713        } finally {
1714            IOUtils.closeQuietly(in);
1715        }
1716    }
1717
1718    /**
1719     * Reads the contents of a file into a String. The file is always closed.
1720     * 
1721     * @param file
1722     *            the file to read, must not be {@code null}
1723     * @param encoding
1724     *            the encoding to use, {@code null} means platform default
1725     * @return the file contents, never {@code null}
1726     * @throws IOException
1727     *             in case of an I/O error
1728     * @throws UnsupportedCharsetException
1729     *             thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not
1730     *             supported.
1731     * @since 2.3
1732     */
1733    public static String readFileToString(File file, String encoding) throws IOException {
1734        return readFileToString(file, Charsets.toCharset(encoding));
1735    }
1736
1737
1738    /**
1739     * Reads the contents of a file into a String using the default encoding for the VM. 
1740     * The file is always closed.
1741     *
1742     * @param file  the file to read, must not be {@code null}
1743     * @return the file contents, never {@code null}
1744     * @throws IOException in case of an I/O error
1745     * @since 1.3.1
1746     */
1747    public static String readFileToString(File file) throws IOException {
1748        return readFileToString(file, Charset.defaultCharset());
1749    }
1750
1751    /**
1752     * Reads the contents of a file into a byte array.
1753     * The file is always closed.
1754     *
1755     * @param file  the file to read, must not be {@code null}
1756     * @return the file contents, never {@code null}
1757     * @throws IOException in case of an I/O error
1758     * @since 1.1
1759     */
1760    public static byte[] readFileToByteArray(File file) throws IOException {
1761        InputStream in = null;
1762        try {
1763            in = openInputStream(file);
1764            return IOUtils.toByteArray(in, file.length());
1765        } finally {
1766            IOUtils.closeQuietly(in);
1767        }
1768    }
1769
1770    /**
1771     * Reads the contents of a file line by line to a List of Strings.
1772     * The file is always closed.
1773     *
1774     * @param file  the file to read, must not be {@code null}
1775     * @param encoding  the encoding to use, {@code null} means platform default
1776     * @return the list of Strings representing each line in the file, never {@code null}
1777     * @throws IOException in case of an I/O error
1778     * @since 2.3
1779     */
1780    public static List<String> readLines(File file, Charset encoding) throws IOException {
1781        InputStream in = null;
1782        try {
1783            in = openInputStream(file);
1784            return IOUtils.readLines(in, Charsets.toCharset(encoding));
1785        } finally {
1786            IOUtils.closeQuietly(in);
1787        }
1788    }
1789
1790    /**
1791     * Reads the contents of a file line by line to a List of Strings. The file is always closed.
1792     * 
1793     * @param file
1794     *            the file to read, must not be {@code null}
1795     * @param encoding
1796     *            the encoding to use, {@code null} means platform default
1797     * @return the list of Strings representing each line in the file, never {@code null}
1798     * @throws IOException
1799     *             in case of an I/O error
1800     * @throws UnsupportedCharsetException
1801     *             thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not
1802     *             supported.
1803     * @since 1.1
1804     */
1805    public static List<String> readLines(File file, String encoding) throws IOException {
1806        return readLines(file, Charsets.toCharset(encoding));
1807    }
1808
1809    /**
1810     * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM.
1811     * The file is always closed.
1812     *
1813     * @param file  the file to read, must not be {@code null}
1814     * @return the list of Strings representing each line in the file, never {@code null}
1815     * @throws IOException in case of an I/O error
1816     * @since 1.3
1817     */
1818    public static List<String> readLines(File file) throws IOException {
1819        return readLines(file, Charset.defaultCharset());
1820    }
1821
1822    /**
1823     * Returns an Iterator for the lines in a <code>File</code>.
1824     * <p>
1825     * This method opens an <code>InputStream</code> for the file.
1826     * When you have finished with the iterator you should close the stream
1827     * to free internal resources. This can be done by calling the
1828     * {@link LineIterator#close()} or
1829     * {@link LineIterator#closeQuietly(LineIterator)} method.
1830     * <p>
1831     * The recommended usage pattern is:
1832     * <pre>
1833     * LineIterator it = FileUtils.lineIterator(file, "UTF-8");
1834     * try {
1835     *   while (it.hasNext()) {
1836     *     String line = it.nextLine();
1837     *     /// do something with line
1838     *   }
1839     * } finally {
1840     *   LineIterator.closeQuietly(iterator);
1841     * }
1842     * </pre>
1843     * <p>
1844     * If an exception occurs during the creation of the iterator, the
1845     * underlying stream is closed.
1846     *
1847     * @param file  the file to open for input, must not be {@code null}
1848     * @param encoding  the encoding to use, {@code null} means platform default
1849     * @return an Iterator of the lines in the file, never {@code null}
1850     * @throws IOException in case of an I/O error (file closed)
1851     * @since 1.2
1852     */
1853    public static LineIterator lineIterator(File file, String encoding) throws IOException {
1854        InputStream in = null;
1855        try {
1856            in = openInputStream(file);
1857            return IOUtils.lineIterator(in, encoding);
1858        } catch (IOException ex) {
1859            IOUtils.closeQuietly(in);
1860            throw ex;
1861        } catch (RuntimeException ex) {
1862            IOUtils.closeQuietly(in);
1863            throw ex;
1864        }
1865    }
1866
1867    /**
1868     * Returns an Iterator for the lines in a <code>File</code> using the default encoding for the VM.
1869     *
1870     * @param file  the file to open for input, must not be {@code null}
1871     * @return an Iterator of the lines in the file, never {@code null}
1872     * @throws IOException in case of an I/O error (file closed)
1873     * @since 1.3
1874     * @see #lineIterator(File, String)
1875     */
1876    public static LineIterator lineIterator(File file) throws IOException {
1877        return lineIterator(file, null);
1878    }
1879
1880    //-----------------------------------------------------------------------
1881    /**
1882     * Writes a String to a file creating the file if it does not exist.
1883     *
1884     * NOTE: As from v1.3, the parent directories of the file will be created
1885     * if they do not exist.
1886     *
1887     * @param file  the file to write
1888     * @param data  the content to write to the file
1889     * @param encoding  the encoding to use, {@code null} means platform default
1890     * @throws IOException in case of an I/O error
1891     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
1892     * @since 2.4
1893     */
1894    public static void writeStringToFile(File file, String data, Charset encoding) throws IOException {
1895        writeStringToFile(file, data, encoding, false);
1896    }
1897
1898    /**
1899     * Writes a String to a file creating the file if it does not exist.
1900     *
1901     * NOTE: As from v1.3, the parent directories of the file will be created
1902     * if they do not exist.
1903     *
1904     * @param file  the file to write
1905     * @param data  the content to write to the file
1906     * @param encoding  the encoding to use, {@code null} means platform default
1907     * @throws IOException in case of an I/O error
1908     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
1909     */
1910    public static void writeStringToFile(File file, String data, String encoding) throws IOException {
1911        writeStringToFile(file, data, encoding, false);
1912    }
1913
1914    /**
1915     * Writes a String to a file creating the file if it does not exist.
1916     *
1917     * @param file  the file to write
1918     * @param data  the content to write to the file
1919     * @param encoding  the encoding to use, {@code null} means platform default
1920     * @param append if {@code true}, then the String will be added to the
1921     * end of the file rather than overwriting
1922     * @throws IOException in case of an I/O error
1923     * @since 2.3
1924     */
1925    public static void writeStringToFile(File file, String data, Charset encoding, boolean append) throws IOException {
1926        OutputStream out = null;
1927        try {
1928            out = openOutputStream(file, append);
1929            IOUtils.write(data, out, encoding);
1930            out.close(); // don't swallow close Exception if copy completes normally
1931        } finally {
1932            IOUtils.closeQuietly(out);
1933        }
1934    }
1935
1936    /**
1937     * Writes a String to a file creating the file if it does not exist.
1938     *
1939     * @param file  the file to write
1940     * @param data  the content to write to the file
1941     * @param encoding  the encoding to use, {@code null} means platform default
1942     * @param append if {@code true}, then the String will be added to the
1943     * end of the file rather than overwriting
1944     * @throws IOException in case of an I/O error
1945     * @throws UnsupportedCharsetException
1946     *             thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not
1947     *             supported by the VM
1948     * @since 2.1
1949     */
1950    public static void writeStringToFile(File file, String data, String encoding, boolean append) throws IOException {
1951        writeStringToFile(file, data, Charsets.toCharset(encoding), append);
1952    }
1953
1954    /**
1955     * Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
1956     * 
1957     * @param file  the file to write
1958     * @param data  the content to write to the file
1959     * @throws IOException in case of an I/O error
1960     */
1961    public static void writeStringToFile(File file, String data) throws IOException {
1962        writeStringToFile(file, data, Charset.defaultCharset(), false);
1963    }
1964
1965    /**
1966     * Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
1967     * 
1968     * @param file  the file to write
1969     * @param data  the content to write to the file
1970     * @param append if {@code true}, then the String will be added to the
1971     * end of the file rather than overwriting
1972     * @throws IOException in case of an I/O error
1973     * @since 2.1
1974     */
1975    public static void writeStringToFile(File file, String data, boolean append) throws IOException {
1976        writeStringToFile(file, data, Charset.defaultCharset(), append);
1977    }
1978
1979    /**
1980     * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM.
1981     * 
1982     * @param file  the file to write
1983     * @param data  the content to write to the file
1984     * @throws IOException in case of an I/O error
1985     * @since 2.0
1986     */
1987    public static void write(File file, CharSequence data) throws IOException {
1988        write(file, data, Charset.defaultCharset(), false);
1989    }
1990
1991    /**
1992     * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM.
1993     * 
1994     * @param file  the file to write
1995     * @param data  the content to write to the file
1996     * @param append if {@code true}, then the data will be added to the
1997     * end of the file rather than overwriting
1998     * @throws IOException in case of an I/O error
1999     * @since 2.1
2000     */
2001    public static void write(File file, CharSequence data, boolean append) throws IOException {
2002        write(file, data, Charset.defaultCharset(), append);
2003    }
2004
2005    /**
2006     * Writes a CharSequence to a file creating the file if it does not exist.
2007     *
2008     * @param file  the file to write
2009     * @param data  the content to write to the file
2010     * @param encoding  the encoding to use, {@code null} means platform default
2011     * @throws IOException in case of an I/O error
2012     * @since 2.3
2013     */
2014    public static void write(File file, CharSequence data, Charset encoding) throws IOException {
2015        write(file, data, encoding, false);
2016    }
2017
2018    /**
2019     * Writes a CharSequence to a file creating the file if it does not exist.
2020     *
2021     * @param file  the file to write
2022     * @param data  the content to write to the file
2023     * @param encoding  the encoding to use, {@code null} means platform default
2024     * @throws IOException in case of an I/O error
2025     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
2026     * @since 2.0
2027     */
2028    public static void write(File file, CharSequence data, String encoding) throws IOException {
2029        write(file, data, encoding, false);
2030    }
2031
2032    /**
2033     * Writes a CharSequence to a file creating the file if it does not exist.
2034     *
2035     * @param file  the file to write
2036     * @param data  the content to write to the file
2037     * @param encoding  the encoding to use, {@code null} means platform default
2038     * @param append if {@code true}, then the data will be added to the
2039     * end of the file rather than overwriting
2040     * @throws IOException in case of an I/O error
2041     * @since 2.3
2042     */
2043    public static void write(File file, CharSequence data, Charset encoding, boolean append) throws IOException {
2044        String str = data == null ? null : data.toString();
2045        writeStringToFile(file, str, encoding, append);
2046    }
2047
2048    /**
2049     * Writes a CharSequence to a file creating the file if it does not exist.
2050     *
2051     * @param file  the file to write
2052     * @param data  the content to write to the file
2053     * @param encoding  the encoding to use, {@code null} means platform default
2054     * @param append if {@code true}, then the data will be added to the
2055     * end of the file rather than overwriting
2056     * @throws IOException in case of an I/O error
2057     * @throws UnsupportedCharsetException
2058     *             thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not
2059     *             supported by the VM
2060     * @since IO 2.1
2061     */
2062    public static void write(File file, CharSequence data, String encoding, boolean append) throws IOException {
2063        write(file, data, Charsets.toCharset(encoding), append);
2064    }
2065
2066    /**
2067     * Writes a byte array to a file creating the file if it does not exist.
2068     * <p>
2069     * NOTE: As from v1.3, the parent directories of the file will be created
2070     * if they do not exist.
2071     *
2072     * @param file  the file to write to
2073     * @param data  the content to write to the file
2074     * @throws IOException in case of an I/O error
2075     * @since 1.1
2076     */
2077    public static void writeByteArrayToFile(File file, byte[] data) throws IOException {
2078        writeByteArrayToFile(file, data, false);
2079    }
2080
2081    /**
2082     * Writes a byte array to a file creating the file if it does not exist.
2083     *
2084     * @param file  the file to write to
2085     * @param data  the content to write to the file
2086     * @param append if {@code true}, then bytes will be added to the
2087     * end of the file rather than overwriting
2088     * @throws IOException in case of an I/O error
2089     * @since IO 2.1
2090     */
2091    public static void writeByteArrayToFile(File file, byte[] data, boolean append) throws IOException {
2092        OutputStream out = null;
2093        try {
2094            out = openOutputStream(file, append);
2095            out.write(data);
2096            out.close(); // don't swallow close Exception if copy completes normally
2097        } finally {
2098            IOUtils.closeQuietly(out);
2099        }
2100    }
2101
2102    /**
2103     * Writes the <code>toString()</code> value of each item in a collection to
2104     * the specified <code>File</code> line by line.
2105     * The specified character encoding and the default line ending will be used.
2106     * <p>
2107     * NOTE: As from v1.3, the parent directories of the file will be created
2108     * if they do not exist.
2109     *
2110     * @param file  the file to write to
2111     * @param encoding  the encoding to use, {@code null} means platform default
2112     * @param lines  the lines to write, {@code null} entries produce blank lines
2113     * @throws IOException in case of an I/O error
2114     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
2115     * @since 1.1
2116     */
2117    public static void writeLines(File file, String encoding, Collection<?> lines) throws IOException {
2118        writeLines(file, encoding, lines, null, false);
2119    }
2120
2121    /**
2122     * Writes the <code>toString()</code> value of each item in a collection to
2123     * the specified <code>File</code> line by line, optionally appending.
2124     * The specified character encoding and the default line ending will be used.
2125     *
2126     * @param file  the file to write to
2127     * @param encoding  the encoding to use, {@code null} means platform default
2128     * @param lines  the lines to write, {@code null} entries produce blank lines
2129     * @param append if {@code true}, then the lines will be added to the
2130     * end of the file rather than overwriting
2131     * @throws IOException in case of an I/O error
2132     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
2133     * @since 2.1
2134     */
2135    public static void writeLines(File file, String encoding, Collection<?> lines, boolean append) throws IOException {
2136        writeLines(file, encoding, lines, null, append);
2137    }
2138
2139    /**
2140     * Writes the <code>toString()</code> value of each item in a collection to
2141     * the specified <code>File</code> line by line.
2142     * The default VM encoding and the default line ending will be used.
2143     *
2144     * @param file  the file to write to
2145     * @param lines  the lines to write, {@code null} entries produce blank lines
2146     * @throws IOException in case of an I/O error
2147     * @since 1.3
2148     */
2149    public static void writeLines(File file, Collection<?> lines) throws IOException {
2150        writeLines(file, null, lines, null, false);
2151    }
2152    
2153    /**
2154     * Writes the <code>toString()</code> value of each item in a collection to
2155     * the specified <code>File</code> line by line.
2156     * The default VM encoding and the default line ending will be used.
2157     *
2158     * @param file  the file to write to
2159     * @param lines  the lines to write, {@code null} entries produce blank lines
2160     * @param append if {@code true}, then the lines will be added to the
2161     * end of the file rather than overwriting
2162     * @throws IOException in case of an I/O error
2163     * @since 2.1
2164     */
2165    public static void writeLines(File file, Collection<?> lines, boolean append) throws IOException {
2166        writeLines(file, null, lines, null, append);
2167    }
2168
2169    /**
2170     * Writes the <code>toString()</code> value of each item in a collection to
2171     * the specified <code>File</code> line by line.
2172     * The specified character encoding and the line ending will be used.
2173     * <p>
2174     * NOTE: As from v1.3, the parent directories of the file will be created
2175     * if they do not exist.
2176     *
2177     * @param file  the file to write to
2178     * @param encoding  the encoding to use, {@code null} means platform default
2179     * @param lines  the lines to write, {@code null} entries produce blank lines
2180     * @param lineEnding  the line separator to use, {@code null} is system default
2181     * @throws IOException in case of an I/O error
2182     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
2183     * @since 1.1
2184     */
2185    public static void writeLines(File file, String encoding, Collection<?> lines, String lineEnding)
2186        throws IOException {
2187        writeLines(file, encoding, lines, lineEnding, false);
2188    }
2189
2190    /**
2191     * Writes the <code>toString()</code> value of each item in a collection to
2192     * the specified <code>File</code> line by line.
2193     * The specified character encoding and the line ending will be used.
2194     *
2195     * @param file  the file to write to
2196     * @param encoding  the encoding to use, {@code null} means platform default
2197     * @param lines  the lines to write, {@code null} entries produce blank lines
2198     * @param lineEnding  the line separator to use, {@code null} is system default
2199     * @param append if {@code true}, then the lines will be added to the
2200     * end of the file rather than overwriting
2201     * @throws IOException in case of an I/O error
2202     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
2203     * @since 2.1
2204     */
2205    public static void writeLines(File file, String encoding, Collection<?> lines, String lineEnding, boolean append)
2206            throws IOException {
2207        FileOutputStream out = null;
2208        try {
2209            out = openOutputStream(file, append);
2210            final BufferedOutputStream buffer = new BufferedOutputStream(out);
2211            IOUtils.writeLines(lines, lineEnding, buffer, encoding);
2212            buffer.flush();
2213            out.close(); // don't swallow close Exception if copy completes normally
2214        } finally {
2215            IOUtils.closeQuietly(out);
2216        }
2217    }
2218
2219    /**
2220     * Writes the <code>toString()</code> value of each item in a collection to
2221     * the specified <code>File</code> line by line.
2222     * The default VM encoding and the specified line ending will be used.
2223     *
2224     * @param file  the file to write to
2225     * @param lines  the lines to write, {@code null} entries produce blank lines
2226     * @param lineEnding  the line separator to use, {@code null} is system default
2227     * @throws IOException in case of an I/O error
2228     * @since 1.3
2229     */
2230    public static void writeLines(File file, Collection<?> lines, String lineEnding) throws IOException {
2231        writeLines(file, null, lines, lineEnding, false);
2232    }
2233
2234    /**
2235     * Writes the <code>toString()</code> value of each item in a collection to
2236     * the specified <code>File</code> line by line.
2237     * The default VM encoding and the specified line ending will be used.
2238     *
2239     * @param file  the file to write to
2240     * @param lines  the lines to write, {@code null} entries produce blank lines
2241     * @param lineEnding  the line separator to use, {@code null} is system default
2242     * @param append if {@code true}, then the lines will be added to the
2243     * end of the file rather than overwriting
2244     * @throws IOException in case of an I/O error
2245     * @since 2.1
2246     */
2247    public static void writeLines(File file, Collection<?> lines, String lineEnding, boolean append)
2248        throws IOException {
2249        writeLines(file, null, lines, lineEnding, append);
2250    }
2251
2252    //-----------------------------------------------------------------------
2253    /**
2254     * Deletes a file. If file is a directory, delete it and all sub-directories.
2255     * <p>
2256     * The difference between File.delete() and this method are:
2257     * <ul>
2258     * <li>A directory to be deleted does not have to be empty.</li>
2259     * <li>You get exceptions when a file or directory cannot be deleted.
2260     *      (java.io.File methods returns a boolean)</li>
2261     * </ul>
2262     *
2263     * @param file  file or directory to delete, must not be {@code null}
2264     * @throws NullPointerException if the directory is {@code null}
2265     * @throws FileNotFoundException if the file was not found
2266     * @throws IOException in case deletion is unsuccessful
2267     */
2268    public static void forceDelete(File file) throws IOException {
2269        if (file.isDirectory()) {
2270            deleteDirectory(file);
2271        } else {
2272            boolean filePresent = file.exists();
2273            if (!file.delete()) {
2274                if (!filePresent){
2275                    throw new FileNotFoundException("File does not exist: " + file);
2276                }
2277                String message =
2278                    "Unable to delete file: " + file;
2279                throw new IOException(message);
2280            }
2281        }
2282    }
2283
2284    /**
2285     * Schedules a file to be deleted when JVM exits.
2286     * If file is directory delete it and all sub-directories.
2287     *
2288     * @param file  file or directory to delete, must not be {@code null}
2289     * @throws NullPointerException if the file is {@code null}
2290     * @throws IOException in case deletion is unsuccessful
2291     */
2292    public static void forceDeleteOnExit(File file) throws IOException {
2293        if (file.isDirectory()) {
2294            deleteDirectoryOnExit(file);
2295        } else {
2296            file.deleteOnExit();
2297        }
2298    }
2299
2300    /**
2301     * Schedules a directory recursively for deletion on JVM exit.
2302     *
2303     * @param directory  directory to delete, must not be {@code null}
2304     * @throws NullPointerException if the directory is {@code null}
2305     * @throws IOException in case deletion is unsuccessful
2306     */
2307    private static void deleteDirectoryOnExit(File directory) throws IOException {
2308        if (!directory.exists()) {
2309            return;
2310        }
2311
2312        directory.deleteOnExit();
2313        if (!isSymlink(directory)) {
2314            cleanDirectoryOnExit(directory);
2315        }
2316    }
2317
2318    /**
2319     * Cleans a directory without deleting it.
2320     *
2321     * @param directory  directory to clean, must not be {@code null}
2322     * @throws NullPointerException if the directory is {@code null}
2323     * @throws IOException in case cleaning is unsuccessful
2324     */
2325    private static void cleanDirectoryOnExit(File directory) throws IOException {
2326        if (!directory.exists()) {
2327            String message = directory + " does not exist";
2328            throw new IllegalArgumentException(message);
2329        }
2330
2331        if (!directory.isDirectory()) {
2332            String message = directory + " is not a directory";
2333            throw new IllegalArgumentException(message);
2334        }
2335
2336        File[] files = directory.listFiles();
2337        if (files == null) {  // null if security restricted
2338            throw new IOException("Failed to list contents of " + directory);
2339        }
2340
2341        IOException exception = null;
2342        for (File file : files) {
2343            try {
2344                forceDeleteOnExit(file);
2345            } catch (IOException ioe) {
2346                exception = ioe;
2347            }
2348        }
2349
2350        if (null != exception) {
2351            throw exception;
2352        }
2353    }
2354
2355    /**
2356     * Makes a directory, including any necessary but nonexistent parent
2357     * directories. If a file already exists with specified name but it is
2358     * not a directory then an IOException is thrown.
2359     * If the directory cannot be created (or does not already exist)
2360     * then an IOException is thrown.
2361     *
2362     * @param directory  directory to create, must not be {@code null}
2363     * @throws NullPointerException if the directory is {@code null}
2364     * @throws IOException if the directory cannot be created or the file already exists but is not a directory
2365     */
2366    public static void forceMkdir(File directory) throws IOException {
2367        if (directory.exists()) {
2368            if (!directory.isDirectory()) {
2369                String message =
2370                    "File "
2371                        + directory
2372                        + " exists and is "
2373                        + "not a directory. Unable to create directory.";
2374                throw new IOException(message);
2375            }
2376        } else {
2377            if (!directory.mkdirs()) {
2378                // Double-check that some other thread or process hasn't made
2379                // the directory in the background
2380                if (!directory.isDirectory())
2381                {
2382                    String message =
2383                        "Unable to create directory " + directory;
2384                    throw new IOException(message);
2385                }
2386            }
2387        }
2388    }
2389
2390    //-----------------------------------------------------------------------
2391    /**
2392     * Returns the size of the specified file or directory. If the provided 
2393     * {@link File} is a regular file, then the file's length is returned.
2394     * If the argument is a directory, then the size of the directory is
2395     * calculated recursively. If a directory or subdirectory is security 
2396     * restricted, its size will not be included.
2397     * 
2398     * @param file the regular file or directory to return the size 
2399     *        of (must not be {@code null}).
2400     * 
2401     * @return the length of the file, or recursive size of the directory, 
2402     *         provided (in bytes).
2403     * 
2404     * @throws NullPointerException if the file is {@code null}
2405     * @throws IllegalArgumentException if the file does not exist.
2406     *         
2407     * @since 2.0
2408     */
2409    public static long sizeOf(File file) {
2410
2411        if (!file.exists()) {
2412            String message = file + " does not exist";
2413            throw new IllegalArgumentException(message);
2414        }
2415
2416        if (file.isDirectory()) {
2417            return sizeOfDirectory(file);
2418        } else {
2419            return file.length();
2420        }
2421
2422    }
2423
2424    /**
2425     * Returns the size of the specified file or directory. If the provided 
2426     * {@link File} is a regular file, then the file's length is returned.
2427     * If the argument is a directory, then the size of the directory is
2428     * calculated recursively. If a directory or subdirectory is security 
2429     * restricted, its size will not be included.
2430     * 
2431     * @param file the regular file or directory to return the size 
2432     *        of (must not be {@code null}).
2433     * 
2434     * @return the length of the file, or recursive size of the directory, 
2435     *         provided (in bytes).
2436     * 
2437     * @throws NullPointerException if the file is {@code null}
2438     * @throws IllegalArgumentException if the file does not exist.
2439     *         
2440     * @since 2.4
2441     */
2442    public static BigInteger sizeOfAsBigInteger(File file) {
2443
2444        if (!file.exists()) {
2445            String message = file + " does not exist";
2446            throw new IllegalArgumentException(message);
2447        }
2448
2449        if (file.isDirectory()) {
2450            return sizeOfDirectoryAsBigInteger(file);
2451        } else {
2452            return BigInteger.valueOf(file.length());
2453        }
2454
2455    }
2456
2457    /**
2458     * Counts the size of a directory recursively (sum of the length of all files).
2459     * 
2460     * @param directory
2461     *            directory to inspect, must not be {@code null}
2462     * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total
2463     *         is greater than {@link Long#MAX_VALUE}.
2464     * @throws NullPointerException
2465     *             if the directory is {@code null}
2466     */
2467    public static long sizeOfDirectory(File directory) {
2468        checkDirectory(directory);
2469
2470        final File[] files = directory.listFiles();
2471        if (files == null) {  // null if security restricted
2472            return 0L;
2473        }
2474        long size = 0;
2475
2476        for (final File file : files) {
2477            try {
2478                if (!isSymlink(file)) {
2479                    size += sizeOf(file);
2480                    if (size < 0) {
2481                        break;
2482                    }
2483                }
2484            } catch (IOException ioe) {
2485                // Ignore exceptions caught when asking if a File is a symlink.
2486            }
2487        }
2488
2489        return size;
2490    }
2491
2492    /**
2493     * Counts the size of a directory recursively (sum of the length of all files).
2494     * 
2495     * @param directory
2496     *            directory to inspect, must not be {@code null}
2497     * @return size of directory in bytes, 0 if directory is security restricted.
2498     * @throws NullPointerException
2499     *             if the directory is {@code null}
2500     *  @since 2.4
2501     */
2502    public static BigInteger sizeOfDirectoryAsBigInteger(File directory) {
2503        checkDirectory(directory);
2504
2505        final File[] files = directory.listFiles();
2506        if (files == null) {  // null if security restricted
2507            return BigInteger.ZERO;
2508        }
2509        BigInteger size = BigInteger.ZERO;
2510
2511        for (final File file : files) {
2512            try {
2513                if (!isSymlink(file)) {
2514                    size = size.add(BigInteger.valueOf(sizeOf(file)));
2515                }
2516            } catch (IOException ioe) {
2517                // Ignore exceptions caught when asking if a File is a symlink.
2518            }
2519        }
2520
2521        return size;
2522    }
2523
2524    /**
2525     * Checks that the given {@code File} exists and is a directory.
2526     * 
2527     * @param directory The {@code File} to check.
2528     * @throws IllegalArgumentException if the given {@code File} does not exist or is not a directory.
2529     */
2530    private static void checkDirectory(File directory) {
2531        if (!directory.exists()) {
2532            throw new IllegalArgumentException(directory + " does not exist");
2533        }
2534        if (!directory.isDirectory()) {
2535            throw new IllegalArgumentException(directory + " is not a directory");
2536        }
2537    }
2538
2539    //-----------------------------------------------------------------------
2540    /**
2541     * Tests if the specified <code>File</code> is newer than the reference
2542     * <code>File</code>.
2543     *
2544     * @param file  the <code>File</code> of which the modification date must
2545     * be compared, must not be {@code null}
2546     * @param reference  the <code>File</code> of which the modification date
2547     * is used, must not be {@code null}
2548     * @return true if the <code>File</code> exists and has been modified more
2549     * recently than the reference <code>File</code>
2550     * @throws IllegalArgumentException if the file is {@code null}
2551     * @throws IllegalArgumentException if the reference file is {@code null} or doesn't exist
2552     */
2553     public static boolean isFileNewer(File file, File reference) {
2554        if (reference == null) {
2555            throw new IllegalArgumentException("No specified reference file");
2556        }
2557        if (!reference.exists()) {
2558            throw new IllegalArgumentException("The reference file '"
2559                    + reference + "' doesn't exist");
2560        }
2561        return isFileNewer(file, reference.lastModified());
2562    }
2563
2564    /**
2565     * Tests if the specified <code>File</code> is newer than the specified
2566     * <code>Date</code>.
2567     * 
2568     * @param file  the <code>File</code> of which the modification date
2569     * must be compared, must not be {@code null}
2570     * @param date  the date reference, must not be {@code null}
2571     * @return true if the <code>File</code> exists and has been modified
2572     * after the given <code>Date</code>.
2573     * @throws IllegalArgumentException if the file is {@code null}
2574     * @throws IllegalArgumentException if the date is {@code null}
2575     */
2576    public static boolean isFileNewer(File file, Date date) {
2577        if (date == null) {
2578            throw new IllegalArgumentException("No specified date");
2579        }
2580        return isFileNewer(file, date.getTime());
2581    }
2582
2583    /**
2584     * Tests if the specified <code>File</code> is newer than the specified
2585     * time reference.
2586     *
2587     * @param file  the <code>File</code> of which the modification date must
2588     * be compared, must not be {@code null}
2589     * @param timeMillis  the time reference measured in milliseconds since the
2590     * epoch (00:00:00 GMT, January 1, 1970)
2591     * @return true if the <code>File</code> exists and has been modified after
2592     * the given time reference.
2593     * @throws IllegalArgumentException if the file is {@code null}
2594     */
2595     public static boolean isFileNewer(File file, long timeMillis) {
2596        if (file == null) {
2597            throw new IllegalArgumentException("No specified file");
2598        }
2599        if (!file.exists()) {
2600            return false;
2601        }
2602        return file.lastModified() > timeMillis;
2603    }
2604
2605
2606    //-----------------------------------------------------------------------
2607    /**
2608     * Tests if the specified <code>File</code> is older than the reference
2609     * <code>File</code>.
2610     *
2611     * @param file  the <code>File</code> of which the modification date must
2612     * be compared, must not be {@code null}
2613     * @param reference  the <code>File</code> of which the modification date
2614     * is used, must not be {@code null}
2615     * @return true if the <code>File</code> exists and has been modified before
2616     * the reference <code>File</code>
2617     * @throws IllegalArgumentException if the file is {@code null}
2618     * @throws IllegalArgumentException if the reference file is {@code null} or doesn't exist
2619     */
2620     public static boolean isFileOlder(File file, File reference) {
2621        if (reference == null) {
2622            throw new IllegalArgumentException("No specified reference file");
2623        }
2624        if (!reference.exists()) {
2625            throw new IllegalArgumentException("The reference file '"
2626                    + reference + "' doesn't exist");
2627        }
2628        return isFileOlder(file, reference.lastModified());
2629    }
2630
2631    /**
2632     * Tests if the specified <code>File</code> is older than the specified
2633     * <code>Date</code>.
2634     * 
2635     * @param file  the <code>File</code> of which the modification date
2636     * must be compared, must not be {@code null}
2637     * @param date  the date reference, must not be {@code null}
2638     * @return true if the <code>File</code> exists and has been modified
2639     * before the given <code>Date</code>.
2640     * @throws IllegalArgumentException if the file is {@code null}
2641     * @throws IllegalArgumentException if the date is {@code null}
2642     */
2643    public static boolean isFileOlder(File file, Date date) {
2644        if (date == null) {
2645            throw new IllegalArgumentException("No specified date");
2646        }
2647        return isFileOlder(file, date.getTime());
2648    }
2649
2650    /**
2651     * Tests if the specified <code>File</code> is older than the specified
2652     * time reference.
2653     *
2654     * @param file  the <code>File</code> of which the modification date must
2655     * be compared, must not be {@code null}
2656     * @param timeMillis  the time reference measured in milliseconds since the
2657     * epoch (00:00:00 GMT, January 1, 1970)
2658     * @return true if the <code>File</code> exists and has been modified before
2659     * the given time reference.
2660     * @throws IllegalArgumentException if the file is {@code null}
2661     */
2662     public static boolean isFileOlder(File file, long timeMillis) {
2663        if (file == null) {
2664            throw new IllegalArgumentException("No specified file");
2665        }
2666        if (!file.exists()) {
2667            return false;
2668        }
2669        return file.lastModified() < timeMillis;
2670    }
2671
2672    //-----------------------------------------------------------------------
2673    /**
2674     * Computes the checksum of a file using the CRC32 checksum routine.
2675     * The value of the checksum is returned.
2676     *
2677     * @param file  the file to checksum, must not be {@code null}
2678     * @return the checksum value
2679     * @throws NullPointerException if the file or checksum is {@code null}
2680     * @throws IllegalArgumentException if the file is a directory
2681     * @throws IOException if an IO error occurs reading the file
2682     * @since 1.3
2683     */
2684    public static long checksumCRC32(File file) throws IOException {
2685        CRC32 crc = new CRC32();
2686        checksum(file, crc);
2687        return crc.getValue();
2688    }
2689
2690    /**
2691     * Computes the checksum of a file using the specified checksum object.
2692     * Multiple files may be checked using one <code>Checksum</code> instance
2693     * if desired simply by reusing the same checksum object.
2694     * For example:
2695     * <pre>
2696     *   long csum = FileUtils.checksum(file, new CRC32()).getValue();
2697     * </pre>
2698     *
2699     * @param file  the file to checksum, must not be {@code null}
2700     * @param checksum  the checksum object to be used, must not be {@code null}
2701     * @return the checksum specified, updated with the content of the file
2702     * @throws NullPointerException if the file or checksum is {@code null}
2703     * @throws IllegalArgumentException if the file is a directory
2704     * @throws IOException if an IO error occurs reading the file
2705     * @since 1.3
2706     */
2707    public static Checksum checksum(File file, Checksum checksum) throws IOException {
2708        if (file.isDirectory()) {
2709            throw new IllegalArgumentException("Checksums can't be computed on directories");
2710        }
2711        InputStream in = null;
2712        try {
2713            in = new CheckedInputStream(new FileInputStream(file), checksum);
2714            IOUtils.copy(in, new NullOutputStream());
2715        } finally {
2716            IOUtils.closeQuietly(in);
2717        }
2718        return checksum;
2719    }
2720
2721    /**
2722     * Moves a directory.
2723     * <p>
2724     * When the destination directory is on another file system, do a "copy and delete".
2725     *
2726     * @param srcDir the directory to be moved
2727     * @param destDir the destination directory
2728     * @throws NullPointerException if source or destination is {@code null}
2729     * @throws FileExistsException if the destination directory exists
2730     * @throws IOException if source or destination is invalid
2731     * @throws IOException if an IO error occurs moving the file
2732     * @since 1.4
2733     */
2734    public static void moveDirectory(File srcDir, File destDir) throws IOException {
2735        if (srcDir == null) {
2736            throw new NullPointerException("Source must not be null");
2737        }
2738        if (destDir == null) {
2739            throw new NullPointerException("Destination must not be null");
2740        }
2741        if (!srcDir.exists()) {
2742            throw new FileNotFoundException("Source '" + srcDir + "' does not exist");
2743        }
2744        if (!srcDir.isDirectory()) {
2745            throw new IOException("Source '" + srcDir + "' is not a directory");
2746        }
2747        if (destDir.exists()) {
2748            throw new FileExistsException("Destination '" + destDir + "' already exists");
2749        }
2750        boolean rename = srcDir.renameTo(destDir);
2751        if (!rename) {
2752            if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) {
2753                throw new IOException("Cannot move directory: "+srcDir+" to a subdirectory of itself: "+destDir);
2754            }
2755            copyDirectory( srcDir, destDir );
2756            deleteDirectory( srcDir );
2757            if (srcDir.exists()) {
2758                throw new IOException("Failed to delete original directory '" + srcDir +
2759                        "' after copy to '" + destDir + "'");
2760            }
2761        }
2762    }
2763
2764    /**
2765     * Moves a directory to another directory.
2766     *
2767     * @param src the file to be moved
2768     * @param destDir the destination file
2769     * @param createDestDir If {@code true} create the destination directory,
2770     * otherwise if {@code false} throw an IOException
2771     * @throws NullPointerException if source or destination is {@code null}
2772     * @throws FileExistsException if the directory exists in the destination directory
2773     * @throws IOException if source or destination is invalid
2774     * @throws IOException if an IO error occurs moving the file
2775     * @since 1.4
2776     */
2777    public static void moveDirectoryToDirectory(File src, File destDir, boolean createDestDir) throws IOException {
2778        if (src == null) {
2779            throw new NullPointerException("Source must not be null");
2780        }
2781        if (destDir == null) {
2782            throw new NullPointerException("Destination directory must not be null");
2783        }
2784        if (!destDir.exists() && createDestDir) {
2785            destDir.mkdirs();
2786        }
2787        if (!destDir.exists()) {
2788            throw new FileNotFoundException("Destination directory '" + destDir +
2789                    "' does not exist [createDestDir=" + createDestDir +"]");
2790        }
2791        if (!destDir.isDirectory()) {
2792            throw new IOException("Destination '" + destDir + "' is not a directory");
2793        }
2794        moveDirectory(src, new File(destDir, src.getName()));
2795    
2796    }
2797
2798    /**
2799     * Moves a file.
2800     * <p>
2801     * When the destination file is on another file system, do a "copy and delete".
2802     *
2803     * @param srcFile the file to be moved
2804     * @param destFile the destination file
2805     * @throws NullPointerException if source or destination is {@code null}
2806     * @throws FileExistsException if the destination file exists
2807     * @throws IOException if source or destination is invalid
2808     * @throws IOException if an IO error occurs moving the file
2809     * @since 1.4
2810     */
2811    public static void moveFile(File srcFile, File destFile) throws IOException {
2812        if (srcFile == null) {
2813            throw new NullPointerException("Source must not be null");
2814        }
2815        if (destFile == null) {
2816            throw new NullPointerException("Destination must not be null");
2817        }
2818        if (!srcFile.exists()) {
2819            throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
2820        }
2821        if (srcFile.isDirectory()) {
2822            throw new IOException("Source '" + srcFile + "' is a directory");
2823        }
2824        if (destFile.exists()) {
2825            throw new FileExistsException("Destination '" + destFile + "' already exists");
2826        }
2827        if (destFile.isDirectory()) {
2828            throw new IOException("Destination '" + destFile + "' is a directory");
2829        }
2830        boolean rename = srcFile.renameTo(destFile);
2831        if (!rename) {
2832            copyFile( srcFile, destFile );
2833            if (!srcFile.delete()) {
2834                FileUtils.deleteQuietly(destFile);
2835                throw new IOException("Failed to delete original file '" + srcFile +
2836                        "' after copy to '" + destFile + "'");
2837            }
2838        }
2839    }
2840
2841    /**
2842     * Moves a file to a directory.
2843     *
2844     * @param srcFile the file to be moved
2845     * @param destDir the destination file
2846     * @param createDestDir If {@code true} create the destination directory,
2847     * otherwise if {@code false} throw an IOException
2848     * @throws NullPointerException if source or destination is {@code null}
2849     * @throws FileExistsException if the destination file exists
2850     * @throws IOException if source or destination is invalid
2851     * @throws IOException if an IO error occurs moving the file
2852     * @since 1.4
2853     */
2854    public static void moveFileToDirectory(File srcFile, File destDir, boolean createDestDir) throws IOException {
2855        if (srcFile == null) {
2856            throw new NullPointerException("Source must not be null");
2857        }
2858        if (destDir == null) {
2859            throw new NullPointerException("Destination directory must not be null");
2860        }
2861        if (!destDir.exists() && createDestDir) {
2862            destDir.mkdirs();
2863        }
2864        if (!destDir.exists()) {
2865            throw new FileNotFoundException("Destination directory '" + destDir +
2866                    "' does not exist [createDestDir=" + createDestDir +"]");
2867        }
2868        if (!destDir.isDirectory()) {
2869            throw new IOException("Destination '" + destDir + "' is not a directory");
2870        }
2871        moveFile(srcFile, new File(destDir, srcFile.getName()));
2872    }
2873
2874    /**
2875     * Moves a file or directory to the destination directory.
2876     * <p>
2877     * When the destination is on another file system, do a "copy and delete".
2878     *
2879     * @param src the file or directory to be moved
2880     * @param destDir the destination directory 
2881     * @param createDestDir If {@code true} create the destination directory,
2882     * otherwise if {@code false} throw an IOException
2883     * @throws NullPointerException if source or destination is {@code null}
2884     * @throws FileExistsException if the directory or file exists in the destination directory
2885     * @throws IOException if source or destination is invalid
2886     * @throws IOException if an IO error occurs moving the file
2887     * @since 1.4
2888     */
2889    public static void moveToDirectory(File src, File destDir, boolean createDestDir) throws IOException {
2890        if (src == null) {
2891            throw new NullPointerException("Source must not be null");
2892        }
2893        if (destDir == null) {
2894            throw new NullPointerException("Destination must not be null");
2895        }
2896        if (!src.exists()) {
2897            throw new FileNotFoundException("Source '" + src + "' does not exist");
2898        }
2899        if (src.isDirectory()) {
2900            moveDirectoryToDirectory(src, destDir, createDestDir);
2901        } else {
2902            moveFileToDirectory(src, destDir, createDestDir);
2903        }
2904    }
2905
2906    /**
2907     * Determines whether the specified file is a Symbolic Link rather than an actual file.
2908     * <p>
2909     * Will not return true if there is a Symbolic Link anywhere in the path,
2910     * only if the specific file is.
2911     * <p>
2912     * <b>Note:</b> the current implementation always returns {@code false} if the system
2913     * is detected as Windows using {@link FilenameUtils#isSystemWindows()}
2914     * 
2915     * @param file the file to check
2916     * @return true if the file is a Symbolic Link
2917     * @throws IOException if an IO error occurs while checking the file
2918     * @since 2.0
2919     */
2920    public static boolean isSymlink(File file) throws IOException {
2921        if (file == null) {
2922            throw new NullPointerException("File must not be null");
2923        }
2924        if (FilenameUtils.isSystemWindows()) {
2925            return false;
2926        }
2927        File fileInCanonicalDir = null;
2928        if (file.getParent() == null) {
2929            fileInCanonicalDir = file;
2930        } else {
2931            File canonicalDir = file.getParentFile().getCanonicalFile();
2932            fileInCanonicalDir = new File(canonicalDir, file.getName());
2933        }
2934        
2935        if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) {
2936            return false;
2937        } else {
2938            return true;
2939        }
2940    }
2941
2942}