View Javadoc

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