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.BufferedInputStream;
020import java.io.BufferedOutputStream;
021import java.io.File;
022import java.io.FileFilter;
023import java.io.FileInputStream;
024import java.io.FileNotFoundException;
025import java.io.FileOutputStream;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.InputStreamReader;
029import java.io.OutputStream;
030import java.io.Reader;
031import java.io.UncheckedIOException;
032import java.math.BigInteger;
033import java.net.URL;
034import java.nio.ByteBuffer;
035import java.nio.charset.Charset;
036import java.nio.charset.StandardCharsets;
037import java.nio.charset.UnsupportedCharsetException;
038import java.nio.file.CopyOption;
039import java.nio.file.DirectoryStream;
040import java.nio.file.FileVisitOption;
041import java.nio.file.FileVisitResult;
042import java.nio.file.Files;
043import java.nio.file.LinkOption;
044import java.nio.file.NotDirectoryException;
045import java.nio.file.Path;
046import java.nio.file.StandardCopyOption;
047import java.nio.file.attribute.BasicFileAttributeView;
048import java.nio.file.attribute.BasicFileAttributes;
049import java.nio.file.attribute.FileTime;
050import java.time.Duration;
051import java.time.Instant;
052import java.time.LocalTime;
053import java.time.OffsetDateTime;
054import java.time.OffsetTime;
055import java.time.ZoneId;
056import java.time.chrono.ChronoLocalDate;
057import java.time.chrono.ChronoLocalDateTime;
058import java.time.chrono.ChronoZonedDateTime;
059import java.util.ArrayList;
060import java.util.Collection;
061import java.util.Collections;
062import java.util.Date;
063import java.util.HashSet;
064import java.util.Iterator;
065import java.util.List;
066import java.util.Objects;
067import java.util.Set;
068import java.util.stream.Collectors;
069import java.util.stream.Stream;
070import java.util.zip.CRC32;
071import java.util.zip.CheckedInputStream;
072import java.util.zip.Checksum;
073
074import org.apache.commons.io.file.AccumulatorPathVisitor;
075import org.apache.commons.io.file.Counters;
076import org.apache.commons.io.file.PathFilter;
077import org.apache.commons.io.file.PathUtils;
078import org.apache.commons.io.file.StandardDeleteOption;
079import org.apache.commons.io.filefilter.FileEqualsFileFilter;
080import org.apache.commons.io.filefilter.FileFileFilter;
081import org.apache.commons.io.filefilter.IOFileFilter;
082import org.apache.commons.io.filefilter.SuffixFileFilter;
083import org.apache.commons.io.filefilter.TrueFileFilter;
084import org.apache.commons.io.function.IOConsumer;
085import org.apache.commons.io.function.Uncheck;
086
087/**
088 * General file manipulation utilities.
089 * <p>
090 * Facilities are provided in the following areas:
091 * </p>
092 * <ul>
093 * <li>writing to a file
094 * <li>reading from a file
095 * <li>make a directory including parent directories
096 * <li>copying files and directories
097 * <li>deleting files and directories
098 * <li>converting to and from a URL
099 * <li>listing files and directories by filter and extension
100 * <li>comparing file content
101 * <li>file last changed date
102 * <li>calculating a checksum
103 * </ul>
104 * <p>
105 * Note that a specific charset should be specified whenever possible. Relying on the platform default means that the
106 * code is Locale-dependent. Only use the default if the files are known to always use the platform default.
107 * </p>
108 * <p>
109 * {@link SecurityException} are not documented in the Javadoc.
110 * </p>
111 * <p>
112 * Provenance: Excalibur, Alexandria, Commons-Utils
113 * </p>
114 */
115public class FileUtils {
116
117    private static final String PROTOCOL_FILE = "file";
118
119    /**
120     * The number of bytes in a kilobyte.
121     */
122    public static final long ONE_KB = 1024;
123
124    /**
125     * The number of bytes in a kilobyte.
126     *
127     * @since 2.4
128     */
129    public static final BigInteger ONE_KB_BI = BigInteger.valueOf(ONE_KB);
130
131    /**
132     * The number of bytes in a megabyte.
133     */
134    public static final long ONE_MB = ONE_KB * ONE_KB;
135
136    /**
137     * The number of bytes in a megabyte.
138     *
139     * @since 2.4
140     */
141    public static final BigInteger ONE_MB_BI = ONE_KB_BI.multiply(ONE_KB_BI);
142
143    /**
144     * The number of bytes in a gigabyte.
145     */
146    public static final long ONE_GB = ONE_KB * ONE_MB;
147
148    /**
149     * The number of bytes in a gigabyte.
150     *
151     * @since 2.4
152     */
153    public static final BigInteger ONE_GB_BI = ONE_KB_BI.multiply(ONE_MB_BI);
154
155    /**
156     * The number of bytes in a terabyte.
157     */
158    public static final long ONE_TB = ONE_KB * ONE_GB;
159
160    /**
161     * The number of bytes in a terabyte.
162     *
163     * @since 2.4
164     */
165    public static final BigInteger ONE_TB_BI = ONE_KB_BI.multiply(ONE_GB_BI);
166
167    /**
168     * The number of bytes in a petabyte.
169     */
170    public static final long ONE_PB = ONE_KB * ONE_TB;
171
172    /**
173     * The number of bytes in a petabyte.
174     *
175     * @since 2.4
176     */
177    public static final BigInteger ONE_PB_BI = ONE_KB_BI.multiply(ONE_TB_BI);
178
179    /**
180     * The number of bytes in an exabyte.
181     */
182    public static final long ONE_EB = ONE_KB * ONE_PB;
183
184    /**
185     * The number of bytes in an exabyte.
186     *
187     * @since 2.4
188     */
189    public static final BigInteger ONE_EB_BI = ONE_KB_BI.multiply(ONE_PB_BI);
190
191    /**
192     * The number of bytes in a zettabyte.
193     */
194    public static final BigInteger ONE_ZB = BigInteger.valueOf(ONE_KB).multiply(BigInteger.valueOf(ONE_EB));
195
196    /**
197     * The number of bytes in a yottabyte.
198     */
199    public static final BigInteger ONE_YB = ONE_KB_BI.multiply(ONE_ZB);
200
201    /**
202     * An empty array of type {@link File}.
203     */
204    public static final File[] EMPTY_FILE_ARRAY = {};
205
206    /**
207     * Returns a human-readable version of the file size, where the input represents a specific number of bytes.
208     * <p>
209     * 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
210     * nearest GB boundary.
211     * </p>
212     * <p>
213     * Similarly for the 1MB and 1KB boundaries.
214     * </p>
215     *
216     * @param size the number of bytes
217     * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes)
218     * @throws NullPointerException if the given {@link BigInteger} is {@code null}.
219     * @see <a href="https://issues.apache.org/jira/browse/IO-226">IO-226 - should the rounding be changed?</a>
220     * @since 2.4
221     */
222    // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed?
223    public static String byteCountToDisplaySize(final BigInteger size) {
224        Objects.requireNonNull(size, "size");
225        final String displaySize;
226
227        if (size.divide(ONE_EB_BI).compareTo(BigInteger.ZERO) > 0) {
228            displaySize = size.divide(ONE_EB_BI) + " EB";
229        } else if (size.divide(ONE_PB_BI).compareTo(BigInteger.ZERO) > 0) {
230            displaySize = size.divide(ONE_PB_BI) + " PB";
231        } else if (size.divide(ONE_TB_BI).compareTo(BigInteger.ZERO) > 0) {
232            displaySize = size.divide(ONE_TB_BI) + " TB";
233        } else if (size.divide(ONE_GB_BI).compareTo(BigInteger.ZERO) > 0) {
234            displaySize = size.divide(ONE_GB_BI) + " GB";
235        } else if (size.divide(ONE_MB_BI).compareTo(BigInteger.ZERO) > 0) {
236            displaySize = size.divide(ONE_MB_BI) + " MB";
237        } else if (size.divide(ONE_KB_BI).compareTo(BigInteger.ZERO) > 0) {
238            displaySize = size.divide(ONE_KB_BI) + " KB";
239        } else {
240            displaySize = size + " bytes";
241        }
242        return displaySize;
243    }
244
245    /**
246     * Returns a human-readable version of the file size, where the input represents a specific number of bytes.
247     * <p>
248     * 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
249     * nearest GB boundary.
250     * </p>
251     * <p>
252     * Similarly for the 1MB and 1KB boundaries.
253     * </p>
254     *
255     * @param size the number of bytes
256     * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes)
257     * @see <a href="https://issues.apache.org/jira/browse/IO-226">IO-226 - should the rounding be changed?</a>
258     */
259    // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed?
260    public static String byteCountToDisplaySize(final long size) {
261        return byteCountToDisplaySize(BigInteger.valueOf(size));
262    }
263
264    /**
265     * Returns a human-readable version of the file size, where the input represents a specific number of bytes.
266     * <p>
267     * 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
268     * nearest GB boundary.
269     * </p>
270     * <p>
271     * Similarly for the 1MB and 1KB boundaries.
272     * </p>
273     *
274     * @param size the number of bytes
275     * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes)
276     * @see <a href="https://issues.apache.org/jira/browse/IO-226">IO-226 - should the rounding be changed?</a>
277     * @since 2.12.0
278     */
279    // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed?
280    public static String byteCountToDisplaySize(final Number size) {
281        return byteCountToDisplaySize(size.longValue());
282    }
283
284    /**
285     * Requires that the given {@link File} object
286     * points to an actual file (not a directory) in the file system,
287     * and throws a {@link FileNotFoundException} if it doesn't.
288     * It throws an IllegalArgumentException if the object points to a directory.
289     *
290     * @param file The {@link File} to check.
291     * @param name The parameter name to use in the exception message.
292     * @throws FileNotFoundException if the file does not exist
293     * @throws NullPointerException if the given {@link File} is {@code null}.
294     * @throws IllegalArgumentException if the given {@link File} is not a file.
295     */
296    private static void checkFileExists(final File file, final String name) throws FileNotFoundException {
297        Objects.requireNonNull(file, name);
298        if (!file.isFile()) {
299            if (file.exists()) {
300                throw new IllegalArgumentException("Parameter '" + name + "' is not a file: " + file);
301            }
302            if (!Files.isSymbolicLink(file.toPath())) {
303                throw new FileNotFoundException("Source '" + file + "' does not exist");
304            }
305        }
306    }
307
308    private static File checkIsFile(final File file, final String name) {
309        if (file.isFile()) {
310            return file;
311        }
312        throw new IllegalArgumentException(String.format("Parameter '%s' is not a file: %s", name, file));
313    }
314
315    /**
316     * Computes the checksum of a file using the specified checksum object. Multiple files may be checked using one
317     * {@link Checksum} instance if desired simply by reusing the same checksum object. For example:
318     *
319     * <pre>
320     * long checksum = FileUtils.checksum(file, new CRC32()).getValue();
321     * </pre>
322     *
323     * @param file the file to checksum, must not be {@code null}
324     * @param checksum the checksum object to be used, must not be {@code null}
325     * @return the checksum specified, updated with the content of the file
326     * @throws NullPointerException if the given {@link File} is {@code null}.
327     * @throws NullPointerException if the given {@link Checksum} is {@code null}.
328     * @throws IllegalArgumentException if the given {@link File} is not a file.
329     * @throws FileNotFoundException if the file does not exist
330     * @throws IOException if an IO error occurs reading the file.
331     * @since 1.3
332     */
333    public static Checksum checksum(final File file, final Checksum checksum) throws IOException {
334        checkFileExists(file, PROTOCOL_FILE);
335        Objects.requireNonNull(checksum, "checksum");
336        try (InputStream inputStream = new CheckedInputStream(Files.newInputStream(file.toPath()), checksum)) {
337            IOUtils.consume(inputStream);
338        }
339        return checksum;
340    }
341
342    /**
343     * Computes the checksum of a file using the CRC32 checksum routine.
344     * The value of the checksum is returned.
345     *
346     * @param file the file to checksum, must not be {@code null}
347     * @return the checksum value
348     * @throws NullPointerException if the given {@link File} is {@code null}.
349     * @throws IllegalArgumentException if the given {@link File} does not exist or is not a file.
350     * @throws IOException              if an IO error occurs reading the file.
351     * @since 1.3
352     */
353    public static long checksumCRC32(final File file) throws IOException {
354        return checksum(file, new CRC32()).getValue();
355    }
356
357    /**
358     * Cleans a directory without deleting it.
359     *
360     * @param directory directory to clean
361     * @throws NullPointerException if the given {@link File} is {@code null}.
362     * @throws IllegalArgumentException if directory does not exist or is not a directory.
363     * @throws IOException if an I/O error occurs.
364     * @see #forceDelete(File)
365     */
366    public static void cleanDirectory(final File directory) throws IOException {
367        IOConsumer.forAll(FileUtils::forceDelete, listFiles(directory, null));
368    }
369
370    /**
371     * Cleans a directory without deleting it.
372     *
373     * @param directory directory to clean, must not be {@code null}
374     * @throws NullPointerException if the given {@link File} is {@code null}.
375     * @throws IllegalArgumentException if directory does not exist or is not a directory.
376     * @throws IOException if an I/O error occurs.
377     * @see #forceDeleteOnExit(File)
378     */
379    private static void cleanDirectoryOnExit(final File directory) throws IOException {
380        IOConsumer.forAll(FileUtils::forceDeleteOnExit, listFiles(directory, null));
381    }
382
383    /**
384     * Tests whether the contents of two files are equal.
385     * <p>
386     * This method checks to see if the two files are different lengths or if they point to the same file, before
387     * resorting to byte-by-byte comparison of the contents.
388     * </p>
389     *
390     * @param file1 the first file
391     * @param file2 the second file
392     * @return true if the content of the files are equal or they both don't exist, false otherwise
393     * @throws IllegalArgumentException when an input is not a file.
394     * @throws IOException If an I/O error occurs.
395     * @see PathUtils#fileContentEquals(Path,Path)
396     */
397    public static boolean contentEquals(final File file1, final File file2) throws IOException {
398        if (file1 == null && file2 == null) {
399            return true;
400        }
401        if (file1 == null || file2 == null) {
402            return false;
403        }
404        final boolean file1Exists = file1.exists();
405        if (file1Exists != file2.exists()) {
406            return false;
407        }
408
409        if (!file1Exists) {
410            // two not existing files are equal
411            return true;
412        }
413
414        checkIsFile(file1, "file1");
415        checkIsFile(file2, "file2");
416
417        if (file1.length() != file2.length()) {
418            // lengths differ, cannot be equal
419            return false;
420        }
421
422        if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) {
423            // same file
424            return true;
425        }
426
427        return PathUtils.fileContentEquals(file1.toPath(), file2.toPath());
428    }
429
430    /**
431     * Compares the contents of two files to determine if they are equal or not.
432     * <p>
433     * This method checks to see if the two files point to the same file,
434     * before resorting to line-by-line comparison of the contents.
435     * </p>
436     *
437     * @param file1       the first file
438     * @param file2       the second file
439     * @param charsetName the name of the requested charset.
440     *                    May be null, in which case the platform default is used
441     * @return true if the content of the files are equal or neither exists,
442     * false otherwise
443     * @throws IllegalArgumentException when an input is not a file.
444     * @throws IOException in case of an I/O error.
445     * @throws UnsupportedCharsetException If the named charset is unavailable (unchecked exception).
446     * @see IOUtils#contentEqualsIgnoreEOL(Reader, Reader)
447     * @since 2.2
448     */
449    public static boolean contentEqualsIgnoreEOL(final File file1, final File file2, final String charsetName)
450            throws IOException {
451        if (file1 == null && file2 == null) {
452            return true;
453        }
454        if (file1 == null || file2 == null) {
455            return false;
456        }
457        final boolean file1Exists = file1.exists();
458        if (file1Exists != file2.exists()) {
459            return false;
460        }
461
462        if (!file1Exists) {
463            // two not existing files are equal
464            return true;
465        }
466
467        checkFileExists(file1, "file1");
468        checkFileExists(file2, "file2");
469
470        if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) {
471            // same file
472            return true;
473        }
474
475        final Charset charset = Charsets.toCharset(charsetName);
476        try (Reader input1 = new InputStreamReader(Files.newInputStream(file1.toPath()), charset);
477             Reader input2 = new InputStreamReader(Files.newInputStream(file2.toPath()), charset)) {
478            return IOUtils.contentEqualsIgnoreEOL(input1, input2);
479        }
480    }
481
482    /**
483     * Converts a Collection containing {@link File} instances into array
484     * representation. This is to account for the difference between
485     * File.listFiles() and FileUtils.listFiles().
486     *
487     * @param files a Collection containing {@link File} instances
488     * @return an array of {@link File}
489     */
490    public static File[] convertFileCollectionToFileArray(final Collection<File> files) {
491        return files.toArray(EMPTY_FILE_ARRAY);
492    }
493
494    /**
495     * Copies a whole directory to a new location, preserving the file dates.
496     * <p>
497     * This method copies the specified directory and all its child directories and files to the specified destination.
498     * The destination is the new location and name of the directory. That is, copying /home/bar to /tmp/bang
499     * copies the contents of /home/bar into /tmp/bang. It does not create /tmp/bang/bar.
500     * </p>
501     * <p>
502     * The destination directory is created if it does not exist. If the destination directory does exist, then this
503     * method merges the source with the destination, with the source taking precedence.
504     * </p>
505     * <p>
506     * <strong>Note:</strong> This method tries to preserve the file's last
507     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}. However it is
508     * not guaranteed that the operation will succeed. If the modification operation fails, it falls back to
509     * {@link File#setLastModified(long)}. If that fails, the method throws IOException.
510     * </p>
511     * <p>
512     * Symbolic links in the source directory are copied to new symbolic links in the destination
513     * directory that point to the original target. The target of the link is not copied unless
514     * it is also under the source directory. Even if it is under the source directory, the new symbolic
515     * link in the destination points to the original target in the source directory, not to the
516     * newly created copy of the target.
517     * </p>
518     *
519     * @param srcDir an existing directory to copy, must not be {@code null}.
520     * @param destDir the new directory, must not be {@code null}.
521     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
522     * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory,
523     *     the source and the destination directory are the same
524     * @throws FileNotFoundException if the source does not exist.
525     * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed
526     * @since 1.1
527     */
528    public static void copyDirectory(final File srcDir, final File destDir) throws IOException {
529        copyDirectory(srcDir, destDir, true);
530    }
531
532    /**
533     * Copies a whole directory to a new location.
534     * <p>
535     * This method copies the contents of the specified source directory to within the specified destination directory.
536     * </p>
537     * <p>
538     * The destination directory is created if it does not exist. If the destination directory does exist, then this
539     * method merges the source with the destination, with the source taking precedence.
540     * </p>
541     * <p>
542     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the files' last
543     * modified date/times using {@link File#setLastModified(long)}. However it is not guaranteed that those operations
544     * will succeed. If the modification operation fails, the method throws IOException.
545     * </p>
546     *
547     * @param srcDir an existing directory to copy, must not be {@code null}.
548     * @param destDir the new directory, must not be {@code null}.
549     * @param preserveFileDate true if the file date of the copy should be the same as the original.
550     * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory, or
551     *     the source and the destination directory are the same
552     * @throws FileNotFoundException if the source does not exist.
553     * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed
554     * @since 1.1
555     */
556    public static void copyDirectory(final File srcDir, final File destDir, final boolean preserveFileDate)
557        throws IOException {
558        copyDirectory(srcDir, destDir, null, preserveFileDate);
559    }
560
561    /**
562     * Copies a filtered directory to a new location preserving the file dates.
563     * <p>
564     * This method copies the contents of the specified source directory to within the specified destination directory.
565     * </p>
566     * <p>
567     * The destination directory is created if it does not exist. If the destination directory does exist, then this
568     * method merges the source with the destination, with the source taking precedence.
569     * </p>
570     * <p>
571     * <strong>Note:</strong> This method tries to preserve the files' last modified date/times using
572     * {@link File#setLastModified(long)}. However it is not guaranteed that those operations will succeed. If the
573     * modification operation fails, the method throws IOException.
574     * </p>
575     * <b>Example: Copy directories only</b>
576     *
577     * <pre>
578     * // only copy the directory structure
579     * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY);
580     * </pre>
581     *
582     * <b>Example: Copy directories and txt files</b>
583     *
584     * <pre>
585     * // Create a filter for ".txt" files
586     * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
587     * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.INSTANCE, txtSuffixFilter);
588     *
589     * // Create a filter for either directories or ".txt" files
590     * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
591     *
592     * // Copy using the filter
593     * FileUtils.copyDirectory(srcDir, destDir, filter);
594     * </pre>
595     *
596     * @param srcDir an existing directory to copy, must not be {@code null}.
597     * @param destDir the new directory, must not be {@code null}.
598     * @param filter the filter to apply, null means copy all directories and files should be the same as the original.
599     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
600     * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory, or
601     *     the source and the destination directory are the same
602     * @throws FileNotFoundException if the source does not exist.
603     * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed
604     * @since 1.4
605     */
606    public static void copyDirectory(final File srcDir, final File destDir, final FileFilter filter)
607        throws IOException {
608        copyDirectory(srcDir, destDir, filter, true);
609    }
610
611    /**
612     * Copies a filtered directory to a new location.
613     * <p>
614     * This method copies the contents of the specified source directory to within the specified destination directory.
615     * </p>
616     * <p>
617     * The destination directory is created if it does not exist. If the destination directory does exist, then this
618     * method merges the source with the destination, with the source taking precedence.
619     * </p>
620     * <p>
621     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
622     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}. However, it is
623     * not guaranteed that the operation will succeed. If the modification operation fails it falls back to
624     * {@link File#setLastModified(long)}. If that fails, the method throws IOException.
625     * </p>
626     * <b>Example: Copy directories only</b>
627     *
628     * <pre>
629     * // only copy the directory structure
630     * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
631     * </pre>
632     *
633     * <b>Example: Copy directories and txt files</b>
634     *
635     * <pre>
636     * // Create a filter for ".txt" files
637     * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
638     * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.INSTANCE, txtSuffixFilter);
639     *
640     * // Create a filter for either directories or ".txt" files
641     * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
642     *
643     * // Copy using the filter
644     * FileUtils.copyDirectory(srcDir, destDir, filter, false);
645     * </pre>
646     *
647     * @param srcDir an existing directory to copy, must not be {@code null}.
648     * @param destDir the new directory, must not be {@code null}.
649     * @param filter the filter to apply, null means copy all directories and files.
650     * @param preserveFileDate true if the file date of the copy should be the same as the original.
651     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
652     * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory,
653     *     the source and the destination directory are the same, or the destination is not writable
654     * @throws FileNotFoundException if the source does not exist.
655     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
656     * @since 1.4
657     */
658    public static void copyDirectory(final File srcDir, final File destDir, final FileFilter filter, final boolean preserveFileDate) throws IOException {
659        copyDirectory(srcDir, destDir, filter, preserveFileDate, StandardCopyOption.REPLACE_EXISTING, LinkOption.NOFOLLOW_LINKS);
660    }
661
662    /**
663     * Copies a filtered directory to a new location.
664     * <p>
665     * This method copies the contents of the specified source directory to within the specified destination directory.
666     * </p>
667     * <p>
668     * The destination directory is created if it does not exist. If the destination directory does exist, then this
669     * method merges the source with the destination, with the source taking precedence.
670     * </p>
671     * <p>
672     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
673     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}. However, it is
674     * not guaranteed that the operation will succeed. If the modification operation fails it falls back to
675     * {@link File#setLastModified(long)}. If that fails, the method throws IOException.
676     * </p>
677     * <b>Example: Copy directories only</b>
678     *
679     * <pre>
680     * // only copy the directory structure
681     * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
682     * </pre>
683     *
684     * <b>Example: Copy directories and txt files</b>
685     *
686     * <pre>
687     * // Create a filter for ".txt" files
688     * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
689     * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.INSTANCE, txtSuffixFilter);
690     *
691     * // Create a filter for either directories or ".txt" files
692     * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
693     *
694     * // Copy using the filter
695     * FileUtils.copyDirectory(srcDir, destDir, filter, false);
696     * </pre>
697     *
698     * @param srcDir an existing directory to copy, must not be {@code null}
699     * @param destDir the new directory, must not be {@code null}
700     * @param fileFilter the filter to apply, null means copy all directories and files
701     * @param preserveFileDate true if the file date of the copy should be the same as the original
702     * @param copyOptions options specifying how the copy should be done, for example {@link StandardCopyOption}.
703     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
704     * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory, or
705     *     the source and the destination directory are the same
706     * @throws FileNotFoundException if the source does not exist.
707     * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed
708     * @since 2.8.0
709     */
710    public static void copyDirectory(final File srcDir, final File destDir, final FileFilter fileFilter, final boolean preserveFileDate,
711        final CopyOption... copyOptions) throws IOException {
712        Objects.requireNonNull(destDir, "destination");
713        requireDirectoryExists(srcDir, "srcDir");
714        requireCanonicalPathsNotEquals(srcDir, destDir);
715
716        // Cater for destination being directory within the source directory (see IO-141)
717        List<String> exclusionList = null;
718        final String srcDirCanonicalPath = srcDir.getCanonicalPath();
719        final String destDirCanonicalPath = destDir.getCanonicalPath();
720        if (destDirCanonicalPath.startsWith(srcDirCanonicalPath)) {
721            final File[] srcFiles = listFiles(srcDir, fileFilter);
722            if (srcFiles.length > 0) {
723                exclusionList = new ArrayList<>(srcFiles.length);
724                for (final File srcFile : srcFiles) {
725                    exclusionList.add(new File(destDir, srcFile.getName()).getCanonicalPath());
726                }
727            }
728        }
729        doCopyDirectory(srcDir, destDir, fileFilter, exclusionList, preserveFileDate, copyOptions);
730    }
731
732    /**
733     * Copies a directory to within another directory preserving the file dates.
734     * <p>
735     * This method copies the source directory and all its contents to a directory of the same name in the specified
736     * destination directory.
737     * </p>
738     * <p>
739     * The destination directory is created if it does not exist. If the destination directory does exist, then this
740     * method merges the source with the destination, with the source taking precedence.
741     * </p>
742     * <p>
743     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
744     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}. However, it is
745     * not guaranteed that the operation will succeed. If the modification operation fails it falls back to
746     * {@link File#setLastModified(long)} and if that fails, the method throws IOException.
747     * </p>
748     *
749     * @param sourceDir an existing directory to copy, must not be {@code null}.
750     * @param destinationDir the directory to place the copy in, must not be {@code null}.
751     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
752     * @throws IllegalArgumentException if the source or destination is invalid.
753     * @throws FileNotFoundException if the source does not exist.
754     * @throws IOException if an error occurs, the destination is not writable, or setting the last-modified time didn't succeed
755     * @since 1.2
756     */
757    public static void copyDirectoryToDirectory(final File sourceDir, final File destinationDir) throws IOException {
758        Objects.requireNonNull(sourceDir, "sourceDir");
759        requireDirectoryIfExists(destinationDir, "destinationDir");
760        copyDirectory(sourceDir, new File(destinationDir, sourceDir.getName()), true);
761    }
762
763    /**
764     * Copies a file to a new location preserving the file date.
765     * <p>
766     * This method copies the contents of the specified source file to the specified destination file. The directory
767     * holding the destination file is created if it does not exist. If the destination file exists, then this method
768     * overwrites it. A symbolic link is resolved before copying so the new file is not a link.
769     * </p>
770     * <p>
771     * <strong>Note:</strong> This method tries to preserve the file's last modified date/times using
772     * {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}. However, it is not guaranteed that the
773     * operation will succeed. If the modification operation fails, it falls back to
774     * {@link File#setLastModified(long)}, and if that fails, the method throws IOException.
775     * </p>
776     *
777     * @param srcFile an existing file to copy, must not be {@code null}.
778     * @param destFile the new file, must not be {@code null}.
779     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
780     * @throws IOException if source or destination is invalid.
781     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
782     * @throws IOException if the output file length is not the same as the input file length after the copy completes.
783     * @see #copyFileToDirectory(File, File)
784     * @see #copyFile(File, File, boolean)
785     */
786    public static void copyFile(final File srcFile, final File destFile) throws IOException {
787        copyFile(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING);
788    }
789
790    /**
791     * Copies an existing file to a new file location.
792     * <p>
793     * This method copies the contents of the specified source file to the specified destination file. The directory
794     * holding the destination file is created if it does not exist. If the destination file exists, then this method
795     * overwrites it. A symbolic link is resolved before copying so the new file is not a link.
796     * </p>
797     * <p>
798     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
799     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}. However, it is
800     * not guaranteed that the operation will succeed. If the modification operation fails, it falls back to
801     * {@link File#setLastModified(long)}, and if that fails, the method throws IOException.
802     * </p>
803     *
804     * @param srcFile an existing file to copy, must not be {@code null}.
805     * @param destFile the new file, must not be {@code null}.
806     * @param preserveFileDate true if the file date of the copy should be the same as the original.
807     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
808     * @throws IOException if source or destination is invalid.
809     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
810     * @throws IOException if the output file length is not the same as the input file length after the copy completes
811     * @see #copyFile(File, File, boolean, CopyOption...)
812     */
813    public static void copyFile(final File srcFile, final File destFile, final boolean preserveFileDate) throws IOException {
814        copyFile(srcFile, destFile, preserveFileDate, StandardCopyOption.REPLACE_EXISTING);
815    }
816
817    /**
818     * Copies the contents of a file to a new location.
819     * <p>
820     * This method copies the contents of the specified source file to the specified destination file. The directory
821     * holding the destination file is created if it does not exist. If the destination file exists, you can overwrite
822     * it with {@link StandardCopyOption#REPLACE_EXISTING}.
823     * </p>
824     *
825     * <p>
826     * By default, a symbolic link is resolved before copying so the new file is not a link.
827     * To copy symbolic links as links, you can pass {@code LinkOption.NO_FOLLOW_LINKS} as the last argument.
828     * </p>
829     *
830     * <p>
831     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
832     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}. However, it is
833     * not guaranteed that the operation will succeed. If the modification operation fails, it falls back to
834     * {@link File#setLastModified(long)}, and if that fails, the method throws IOException.
835     * </p>
836     *
837     * @param srcFile an existing file to copy, must not be {@code null}.
838     * @param destFile the new file, must not be {@code null}.
839     * @param preserveFileDate true if the file date of the copy should be the same as the original.
840     * @param copyOptions options specifying how the copy should be done, for example {@link StandardCopyOption}.
841     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
842     * @throws FileNotFoundException if the source does not exist.
843     * @throws IllegalArgumentException if {@code srcFile} or {@code destFile} is not a file
844     * @throws IOException if the output file length is not the same as the input file length after the copy completes.
845     * @throws IOException if an I/O error occurs, setting the last-modified time didn't succeed,
846     *     or the destination is not writable
847     * @see #copyFileToDirectory(File, File, boolean)
848     * @since 2.8.0
849     */
850    public static void copyFile(final File srcFile, final File destFile, final boolean preserveFileDate, final CopyOption... copyOptions) throws IOException {
851        Objects.requireNonNull(destFile, "destination");
852        checkFileExists(srcFile, "srcFile");
853        requireCanonicalPathsNotEquals(srcFile, destFile);
854        createParentDirectories(destFile);
855        if (destFile.exists()) {
856            checkFileExists(destFile, "destFile");
857        }
858
859        final Path srcPath = srcFile.toPath();
860
861        Files.copy(srcPath, destFile.toPath(), copyOptions);
862
863        // On Windows, the last modified time is copied by default.
864        if (preserveFileDate && !Files.isSymbolicLink(srcPath) && !setTimes(srcFile, destFile)) {
865            throw new IOException("Cannot set the file time.");
866        }
867    }
868
869    /**
870     * Copies a file to a new location.
871     * <p>
872     * This method copies the contents of the specified source file to the specified destination file. The directory
873     * holding the destination file is created if it does not exist. If the destination file exists, you can overwrite
874     * it if you use {@link StandardCopyOption#REPLACE_EXISTING}.
875     * </p>
876     *
877     * @param srcFile an existing file to copy, must not be {@code null}.
878     * @param destFile the new file, must not be {@code null}.
879     * @param copyOptions options specifying how the copy should be done, for example {@link StandardCopyOption}.
880     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
881     * @throws FileNotFoundException if the source does not exist.
882     * @throws IllegalArgumentException if source is not a file.
883     * @throws IOException if an I/O error occurs.
884     * @see StandardCopyOption
885     * @since 2.9.0
886     */
887    public static void copyFile(final File srcFile, final File destFile, final CopyOption... copyOptions) throws IOException {
888        copyFile(srcFile, destFile, true, copyOptions);
889    }
890
891    /**
892     * Copies bytes from a {@link File} to an {@link OutputStream}.
893     * <p>
894     * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}.
895     * </p>
896     *
897     * @param input  the {@link File} to read.
898     * @param output the {@link OutputStream} to write.
899     * @return the number of bytes copied
900     * @throws NullPointerException if the File is {@code null}.
901     * @throws NullPointerException if the OutputStream is {@code null}.
902     * @throws IOException          if an I/O error occurs.
903     * @since 2.1
904     */
905    public static long copyFile(final File input, final OutputStream output) throws IOException {
906        try (InputStream fis = Files.newInputStream(input.toPath())) {
907            return IOUtils.copyLarge(fis, output);
908        }
909    }
910
911    /**
912     * Copies a file to a directory preserving the file date.
913     * <p>
914     * This method copies the contents of the specified source file to a file of the same name in the specified
915     * destination directory. The destination directory is created if it does not exist. If the destination file exists,
916     * then this method will overwrite it.
917     * </p>
918     * <p>
919     * <strong>Note:</strong> This method tries to preserve the file's last modified date/times using
920     * {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}. However, it is not guaranteed that the
921     * operation will succeed. If the modification operation fails it falls back to
922     * {@link File#setLastModified(long)} and if that fails, the method throws IOException.
923     * </p>
924     *
925     * @param srcFile an existing file to copy, must not be {@code null}.
926     * @param destDir the directory to place the copy in, must not be {@code null}.
927     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
928     * @throws IllegalArgumentException if source or destination is invalid.
929     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
930     * @see #copyFile(File, File, boolean)
931     */
932    public static void copyFileToDirectory(final File srcFile, final File destDir) throws IOException {
933        copyFileToDirectory(srcFile, destDir, true);
934    }
935
936    /**
937     * Copies a file to a directory optionally preserving the file date.
938     * <p>
939     * This method copies the contents of the specified source file to a file of the same name in the specified
940     * destination directory. The destination directory is created if it does not exist. If the destination file exists,
941     * then this method will overwrite it.
942     * </p>
943     * <p>
944     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
945     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}. However, it is
946     * not guaranteed that the operation will succeed. If the modification operation fails it falls back to
947     * {@link File#setLastModified(long)} and if that fails, the method throws IOException.
948     * </p>
949     *
950     * @param sourceFile an existing file to copy, must not be {@code null}.
951     * @param destinationDir the directory to place the copy in, must not be {@code null}.
952     * @param preserveFileDate true if the file date of the copy should be the same as the original.
953     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
954     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
955     * @throws IOException if the output file length is not the same as the input file length after the copy completes.
956     * @see #copyFile(File, File, CopyOption...)
957     * @since 1.3
958     */
959    public static void copyFileToDirectory(final File sourceFile, final File destinationDir, final boolean preserveFileDate) throws IOException {
960        Objects.requireNonNull(sourceFile, "sourceFile");
961        requireDirectoryIfExists(destinationDir, "destinationDir");
962        copyFile(sourceFile, new File(destinationDir, sourceFile.getName()), preserveFileDate);
963    }
964
965    /**
966     * Copies bytes from an {@link InputStream} {@code source} to a file
967     * {@code destination}. The directories up to {@code destination}
968     * will be created if they don't already exist. {@code destination}
969     * will be overwritten if it already exists.
970     * <p>
971     * <em>The {@code source} stream is closed.</em>
972     * </p>
973     * <p>
974     * See {@link #copyToFile(InputStream, File)} for a method that does not close the input stream.
975     * </p>
976     *
977     * @param source      the {@link InputStream} to copy bytes from, must not be {@code null}, will be closed
978     * @param destination the non-directory {@link File} to write bytes to
979     *                    (possibly overwriting), must not be {@code null}
980     * @throws IOException if {@code destination} is a directory
981     * @throws IOException if {@code destination} cannot be written
982     * @throws IOException if {@code destination} needs creating but can't be
983     * @throws IOException if an IO error occurs during copying
984     * @since 2.0
985     */
986    public static void copyInputStreamToFile(final InputStream source, final File destination) throws IOException {
987        try (InputStream inputStream = source) {
988            copyToFile(inputStream, destination);
989        }
990    }
991
992    /**
993     * Copies a file or directory to within another directory preserving the file dates.
994     * <p>
995     * This method copies the source file or directory, along with all its contents, to a directory of the same name in the
996     * specified destination directory.
997     * </p>
998     * <p>
999     * The destination directory is created if it does not exist. If the destination directory does exist, then this method
1000     * merges the source with the destination, with the source taking precedence.
1001     * </p>
1002     * <p>
1003     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
1004     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}. However, it is
1005     * not guaranteed that the operation will succeed. If the modification operation fails it falls back to
1006     * {@link File#setLastModified(long)} and if that fails, the method throws IOException.
1007     * </p>
1008     *
1009     * @param sourceFile an existing file or directory to copy, must not be {@code null}.
1010     * @param destinationDir the directory to place the copy in, must not be {@code null}.
1011     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
1012     * @throws IllegalArgumentException if the source or destination is invalid.
1013     * @throws FileNotFoundException if the source does not exist.
1014     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
1015     * @see #copyDirectoryToDirectory(File, File)
1016     * @see #copyFileToDirectory(File, File)
1017     * @since 2.6
1018     */
1019    public static void copyToDirectory(final File sourceFile, final File destinationDir) throws IOException {
1020        Objects.requireNonNull(sourceFile, "sourceFile");
1021        if (sourceFile.isFile()) {
1022            copyFileToDirectory(sourceFile, destinationDir);
1023        } else if (sourceFile.isDirectory()) {
1024            copyDirectoryToDirectory(sourceFile, destinationDir);
1025        } else {
1026            throw new FileNotFoundException("The source " + sourceFile + " does not exist");
1027        }
1028    }
1029
1030    /**
1031     * Copies a files to a directory preserving each file's date.
1032     * <p>
1033     * This method copies the contents of the specified source files
1034     * to a file of the same name in the specified destination directory.
1035     * The destination directory is created if it does not exist.
1036     * If the destination file exists, then this method will overwrite it.
1037     * </p>
1038     * <p>
1039     * <strong>Note:</strong> This method tries to preserve the file's last
1040     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}. However, it is
1041     * not guaranteed that the operation will succeed. If the modification operation fails it falls back to
1042     * {@link File#setLastModified(long)} and if that fails, the method throws IOException.
1043     * </p>
1044     *
1045     * @param sourceIterable  existing files to copy, must not be {@code null}.
1046     * @param destinationDir  the directory to place the copies in, must not be {@code null}.
1047     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
1048     * @throws IOException if source or destination is invalid.
1049     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
1050     * @see #copyFileToDirectory(File, File)
1051     * @since 2.6
1052     */
1053    public static void copyToDirectory(final Iterable<File> sourceIterable, final File destinationDir) throws IOException {
1054        Objects.requireNonNull(sourceIterable, "sourceIterable");
1055        for (final File src : sourceIterable) {
1056            copyFileToDirectory(src, destinationDir);
1057        }
1058    }
1059
1060    /**
1061     * Copies bytes from an {@link InputStream} source to a {@link File} destination. The directories
1062     * up to {@code destination} will be created if they don't already exist. {@code destination} will be
1063     * overwritten if it already exists. The {@code source} stream is left open, e.g. for use with
1064     * {@link java.util.zip.ZipInputStream ZipInputStream}. See {@link #copyInputStreamToFile(InputStream, File)} for a
1065     * method that closes the input stream.
1066     *
1067     * @param inputStream the {@link InputStream} to copy bytes from, must not be {@code null}
1068     * @param file the non-directory {@link File} to write bytes to (possibly overwriting), must not be
1069     *        {@code null}
1070     * @throws NullPointerException if the InputStream is {@code null}.
1071     * @throws NullPointerException if the File is {@code null}.
1072     * @throws IllegalArgumentException if the file object is a directory.
1073     * @throws IllegalArgumentException if the file is not writable.
1074     * @throws IOException if the directories could not be created.
1075     * @throws IOException if an IO error occurs during copying.
1076     * @since 2.5
1077     */
1078    public static void copyToFile(final InputStream inputStream, final File file) throws IOException {
1079        try (OutputStream out = newOutputStream(file, false)) {
1080            IOUtils.copy(inputStream, out);
1081        }
1082    }
1083
1084    /**
1085     * Copies bytes from the URL {@code source} to a file
1086     * {@code destination}. The directories up to {@code destination}
1087     * will be created if they don't already exist. {@code destination}
1088     * will be overwritten if it already exists.
1089     * <p>
1090     * Warning: this method does not set a connection or read timeout and thus
1091     * might block forever. Use {@link #copyURLToFile(URL, File, int, int)}
1092     * with reasonable timeouts to prevent this.
1093     * </p>
1094     *
1095     * @param source      the {@link URL} to copy bytes from, must not be {@code null}
1096     * @param destination the non-directory {@link File} to write bytes to
1097     *                    (possibly overwriting), must not be {@code null}
1098     * @throws IOException if {@code source} URL cannot be opened
1099     * @throws IOException if {@code destination} is a directory
1100     * @throws IOException if {@code destination} cannot be written
1101     * @throws IOException if {@code destination} needs creating but can't be
1102     * @throws IOException if an IO error occurs during copying
1103     */
1104    public static void copyURLToFile(final URL source, final File destination) throws IOException {
1105        final Path path = destination.toPath();
1106        PathUtils.createParentDirectories(path);
1107        PathUtils.copy(source::openStream, path, StandardCopyOption.REPLACE_EXISTING);
1108    }
1109
1110    /**
1111     * Copies bytes from the URL {@code source} to a file {@code destination}. The directories up to
1112     * {@code destination} will be created if they don't already exist. {@code destination} will be
1113     * overwritten if it already exists.
1114     *
1115     * @param source the {@link URL} to copy bytes from, must not be {@code null}
1116     * @param destination the non-directory {@link File} to write bytes to (possibly overwriting), must not be
1117     *        {@code null}
1118     * @param connectionTimeoutMillis the number of milliseconds until this method will time out if no connection could
1119     *        be established to the {@code source}
1120     * @param readTimeoutMillis the number of milliseconds until this method will time out if no data could be read from
1121     *        the {@code source}
1122     * @throws IOException if {@code source} URL cannot be opened
1123     * @throws IOException if {@code destination} is a directory
1124     * @throws IOException if {@code destination} cannot be written
1125     * @throws IOException if {@code destination} needs creating but can't be
1126     * @throws IOException if an IO error occurs during copying
1127     * @since 2.0
1128     */
1129    public static void copyURLToFile(final URL source, final File destination, final int connectionTimeoutMillis, final int readTimeoutMillis)
1130        throws IOException {
1131        try (CloseableURLConnection urlConnection = CloseableURLConnection.open(source)) {
1132            urlConnection.setConnectTimeout(connectionTimeoutMillis);
1133            urlConnection.setReadTimeout(readTimeoutMillis);
1134            try (InputStream stream = urlConnection.getInputStream()) {
1135                copyInputStreamToFile(stream, destination);
1136            }
1137        }
1138    }
1139
1140    /**
1141     * Creates all parent directories for a File object, including any necessary but non-existent parent directories. If a parent directory already exists or
1142     * is null, nothing happens.
1143     *
1144     * @param file the File that may need parents, may be null.
1145     * @return The parent directory, or {@code null} if the given File does have a parent.
1146     * @throws IOException       if the directory was not created along with all its parent directories.
1147     * @throws SecurityException See {@link File#mkdirs()}.
1148     * @since 2.9.0
1149     */
1150    public static File createParentDirectories(final File file) throws IOException {
1151        return mkdirs(getParentFile(file));
1152    }
1153
1154    /**
1155     * Gets the current directory.
1156     *
1157     * @return the current directory.
1158     * @since 2.12.0
1159     */
1160    public static File current() {
1161        return PathUtils.current().toFile();
1162    }
1163
1164    /**
1165     * Decodes the specified URL as per RFC 3986, i.e. transforms
1166     * percent-encoded octets to characters by decoding with the UTF-8 character
1167     * set. This function is primarily intended for usage with
1168     * {@link java.net.URL} which unfortunately does not enforce proper URLs. As
1169     * such, this method will leniently accept invalid characters or malformed
1170     * percent-encoded octets and simply pass them literally through to the
1171     * result string. Except for rare edge cases, this will make unencoded URLs
1172     * pass through unaltered.
1173     *
1174     * @param url The URL to decode, may be {@code null}.
1175     * @return The decoded URL or {@code null} if the input was
1176     * {@code null}.
1177     */
1178    static String decodeUrl(final String url) {
1179        String decoded = url;
1180        if (url != null && url.indexOf('%') >= 0) {
1181            final int n = url.length();
1182            final StringBuilder builder = new StringBuilder();
1183            final ByteBuffer byteBuffer = ByteBuffer.allocate(n);
1184            for (int i = 0; i < n; ) {
1185                if (url.charAt(i) == '%') {
1186                    try {
1187                        do {
1188                            final byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16);
1189                            byteBuffer.put(octet);
1190                            i += 3;
1191                        } while (i < n && url.charAt(i) == '%');
1192                        continue;
1193                    } catch (final IndexOutOfBoundsException | NumberFormatException ignored) {
1194                        // malformed percent-encoded octet, fall through and
1195                        // append characters literally
1196                    } finally {
1197                        if (byteBuffer.position() > 0) {
1198                            byteBuffer.flip();
1199                            builder.append(StandardCharsets.UTF_8.decode(byteBuffer).toString());
1200                            byteBuffer.clear();
1201                        }
1202                    }
1203                }
1204                builder.append(url.charAt(i++));
1205            }
1206            decoded = builder.toString();
1207        }
1208        return decoded;
1209    }
1210
1211    /**
1212     * Deletes the given File but throws an IOException if it cannot, unlike {@link File#delete()} which returns a
1213     * boolean.
1214     *
1215     * @param file The file to delete.
1216     * @return the given file.
1217     * @throws NullPointerException     if the parameter is {@code null}
1218     * @throws IOException              if the file cannot be deleted.
1219     * @see File#delete()
1220     * @since 2.9.0
1221     */
1222    public static File delete(final File file) throws IOException {
1223        Objects.requireNonNull(file, PROTOCOL_FILE);
1224        Files.delete(file.toPath());
1225        return file;
1226    }
1227
1228    /**
1229     * Deletes a directory recursively.
1230     *
1231     * @param directory directory to delete
1232     * @throws IOException              in case deletion is unsuccessful
1233     * @throws NullPointerException     if the parameter is {@code null}
1234     * @throws IllegalArgumentException if {@code directory} is not a directory
1235     */
1236    public static void deleteDirectory(final File directory) throws IOException {
1237        Objects.requireNonNull(directory, "directory");
1238        if (!directory.exists()) {
1239            return;
1240        }
1241        if (!isSymlink(directory)) {
1242            cleanDirectory(directory);
1243        }
1244        delete(directory);
1245    }
1246
1247    /**
1248     * Schedules a directory recursively for deletion on JVM exit.
1249     *
1250     * @param directory directory to delete, must not be {@code null}
1251     * @throws NullPointerException if the directory is {@code null}
1252     * @throws IOException          in case deletion is unsuccessful
1253     */
1254    private static void deleteDirectoryOnExit(final File directory) throws IOException {
1255        if (!directory.exists()) {
1256            return;
1257        }
1258        directory.deleteOnExit();
1259        if (!isSymlink(directory)) {
1260            cleanDirectoryOnExit(directory);
1261        }
1262    }
1263
1264    /**
1265     * Deletes a file, never throwing an exception. If file is a directory, delete it and all subdirectories.
1266     * <p>
1267     * The difference between File.delete() and this method are:
1268     * </p>
1269     * <ul>
1270     * <li>A directory to be deleted does not have to be empty.</li>
1271     * <li>No exceptions are thrown when a file or directory cannot be deleted.</li>
1272     * </ul>
1273     *
1274     * @param file file or directory to delete, can be {@code null}
1275     * @return {@code true} if the file or directory was deleted, otherwise
1276     * {@code false}
1277     * @since 1.4
1278     */
1279    public static boolean deleteQuietly(final File file) {
1280        if (file == null) {
1281            return false;
1282        }
1283        try {
1284            if (file.isDirectory()) {
1285                cleanDirectory(file);
1286            }
1287        } catch (final Exception ignored) {
1288            // ignore
1289        }
1290
1291        try {
1292            return file.delete();
1293        } catch (final Exception ignored) {
1294            return false;
1295        }
1296    }
1297
1298    /**
1299     * Determines whether the {@code parent} directory contains the {@code child} element (a file or directory).
1300     * <p>
1301     * Files are normalized before comparison.
1302     * </p>
1303     *
1304     * Edge cases:
1305     * <ul>
1306     * <li>A {@code directory} must not be null: if null, throw NullPointerException</li>
1307     * <li>A {@code directory} must be a directory: if not a directory, throw IllegalArgumentException</li>
1308     * <li>A directory does not contain itself: return false</li>
1309     * <li>A null child file is not contained in any parent: return false</li>
1310     * </ul>
1311     *
1312     * @param directory the file to consider as the parent.
1313     * @param child     the file to consider as the child.
1314     * @return true is the candidate leaf is under by the specified composite. False otherwise.
1315     * @throws IOException              if an IO error occurs while checking the files.
1316     * @throws NullPointerException if the parent is {@code null}.
1317     * @throws IllegalArgumentException if the parent is not a directory.
1318     * @see FilenameUtils#directoryContains(String, String)
1319     * @since 2.2
1320     */
1321    public static boolean directoryContains(final File directory, final File child) throws IOException {
1322        requireDirectoryExists(directory, "directory");
1323
1324        if (child == null || !child.exists()) {
1325            return false;
1326        }
1327
1328        // Canonicalize paths (normalizes relative paths)
1329        return FilenameUtils.directoryContains(directory.getCanonicalPath(), child.getCanonicalPath());
1330    }
1331
1332    /**
1333     * Internal copy directory method. Creates all destination parent directories,
1334     * including any necessary but non-existent parent directories.
1335     *
1336     * @param srcDir the validated source directory, must not be {@code null}.
1337     * @param destDir the validated destination directory, must not be {@code null}.
1338     * @param fileFilter the filter to apply, null means copy all directories and files.
1339     * @param exclusionList List of files and directories to exclude from the copy, may be null.
1340     * @param preserveDirDate preserve the directories last modified dates.
1341     * @param copyOptions options specifying how the copy should be done, see {@link StandardCopyOption}.
1342     * @throws IOException if the directory was not created along with all its parent directories.
1343     * @throws IllegalArgumentException if {@code destDir} is not writable
1344     * @throws SecurityException See {@link File#mkdirs()}.
1345     */
1346    private static void doCopyDirectory(final File srcDir, final File destDir, final FileFilter fileFilter, final List<String> exclusionList,
1347        final boolean preserveDirDate, final CopyOption... copyOptions) throws IOException {
1348        // recurse dirs, copy files.
1349        final File[] srcFiles = listFiles(srcDir, fileFilter);
1350        requireDirectoryIfExists(destDir, "destDir");
1351        mkdirs(destDir);
1352        for (final File srcFile : srcFiles) {
1353            final File dstFile = new File(destDir, srcFile.getName());
1354            if (exclusionList == null || !exclusionList.contains(srcFile.getCanonicalPath())) {
1355                if (srcFile.isDirectory()) {
1356                    doCopyDirectory(srcFile, dstFile, fileFilter, exclusionList, preserveDirDate, copyOptions);
1357                } else {
1358                    copyFile(srcFile, dstFile, preserveDirDate, copyOptions);
1359                }
1360            }
1361        }
1362        // Do this last, as the above has probably affected directory metadata
1363        if (preserveDirDate) {
1364            setTimes(srcDir, destDir);
1365        }
1366    }
1367
1368    /**
1369     * Deletes a file or directory. For a directory, delete it and all subdirectories.
1370     * <p>
1371     * The difference between File.delete() and this method are:
1372     * </p>
1373     * <ul>
1374     * <li>The directory does not have to be empty.</li>
1375     * <li>You get an exception when a file or directory cannot be deleted.</li>
1376     * </ul>
1377     *
1378     * @param file file or directory to delete, must not be {@code null}.
1379     * @throws NullPointerException  if the file is {@code null}.
1380     * @throws FileNotFoundException if the file was not found.
1381     * @throws IOException           in case deletion is unsuccessful.
1382     */
1383    public static void forceDelete(final File file) throws IOException {
1384        Objects.requireNonNull(file, PROTOCOL_FILE);
1385
1386        final Counters.PathCounters deleteCounters;
1387        try {
1388            deleteCounters = PathUtils.delete(
1389                    file.toPath(), PathUtils.EMPTY_LINK_OPTION_ARRAY,
1390                    StandardDeleteOption.OVERRIDE_READ_ONLY);
1391        } catch (final IOException ex) {
1392            throw new IOException("Cannot delete file: " + file, ex);
1393        }
1394        if (deleteCounters.getFileCounter().get() < 1 && deleteCounters.getDirectoryCounter().get() < 1) {
1395            // didn't find a file to delete.
1396            throw new FileNotFoundException("File does not exist: " + file);
1397        }
1398    }
1399
1400    /**
1401     * Schedules a file to be deleted when JVM exits.
1402     * If file is directory delete it and all subdirectories.
1403     *
1404     * @param file file or directory to delete, must not be {@code null}.
1405     * @throws NullPointerException if the file is {@code null}.
1406     * @throws IOException          in case deletion is unsuccessful.
1407     */
1408    public static void forceDeleteOnExit(final File file) throws IOException {
1409        Objects.requireNonNull(file, PROTOCOL_FILE);
1410        if (file.isDirectory()) {
1411            deleteDirectoryOnExit(file);
1412        } else {
1413            file.deleteOnExit();
1414        }
1415    }
1416
1417    /**
1418     * Creates all directories for a File object, including any necessary but non-existent parent directories. If the {@code directory} already exists or is
1419     * null, nothing happens.
1420     * <p>
1421     * Calls {@link File#mkdirs()} and throws an {@link IOException} on failure.
1422     * </p>
1423     *
1424     * @param directory the receiver for {@code mkdirs()}. If the {@code directory} already exists or is null, nothing happens.
1425     * @throws IOException       if the directory was not created along with all its parent directories.
1426     * @throws IOException       if the given file object is not a directory.
1427     * @throws SecurityException See {@link File#mkdirs()}.
1428     * @see File#mkdirs()
1429     */
1430    public static void forceMkdir(final File directory) throws IOException {
1431        mkdirs(directory);
1432    }
1433
1434    /**
1435     * Creates all directories for a File object, including any necessary but non-existent parent directories. If the parent directory already exists or is
1436     * null, nothing happens.
1437     * <p>
1438     * Calls {@link File#mkdirs()} for the parent of {@code file}.
1439     * </p>
1440     *
1441     * @param file file with parents to create, must not be {@code null}.
1442     * @throws NullPointerException if the file is {@code null}.
1443     * @throws IOException          if the directory was not created along with all its parent directories.
1444     * @throws SecurityException    See {@link File#mkdirs()}.
1445     * @see File#mkdirs()
1446     * @since 2.5
1447     */
1448    public static void forceMkdirParent(final File file) throws IOException {
1449        forceMkdir(getParentFile(Objects.requireNonNull(file, PROTOCOL_FILE)));
1450    }
1451
1452    /**
1453     * Constructs a file from the set of name elements.
1454     *
1455     * @param directory the parent directory.
1456     * @param names the name elements.
1457     * @return the new file.
1458     * @since 2.1
1459     */
1460    public static File getFile(final File directory, final String... names) {
1461        Objects.requireNonNull(directory, "directory");
1462        Objects.requireNonNull(names, "names");
1463        File file = directory;
1464        for (final String name : names) {
1465            file = new File(file, name);
1466        }
1467        return file;
1468    }
1469
1470    /**
1471     * Constructs a file from the set of name elements.
1472     *
1473     * @param names the name elements.
1474     * @return the file.
1475     * @since 2.1
1476     */
1477    public static File getFile(final String... names) {
1478        Objects.requireNonNull(names, "names");
1479        File file = null;
1480        for (final String name : names) {
1481            if (file == null) {
1482                file = new File(name);
1483            } else {
1484                file = new File(file, name);
1485            }
1486        }
1487        return file;
1488    }
1489
1490    /**
1491     * Gets the parent of the given file. The given file may be null. Note that a file's parent may be null as well.
1492     *
1493     * @param file The file to query, may be null.
1494     * @return The parent file or {@code null}. Note that a file's parent may be null as well.
1495     */
1496    private static File getParentFile(final File file) {
1497        return file == null ? null : file.getParentFile();
1498    }
1499
1500    /**
1501     * Returns a {@link File} representing the system temporary directory.
1502     *
1503     * @return the system temporary directory as a File
1504     * @since 2.0
1505     */
1506    public static File getTempDirectory() {
1507        return new File(getTempDirectoryPath());
1508    }
1509
1510    /**
1511     * Returns the path to the system temporary directory.
1512     *
1513     * WARNING: this method relies on the Java system property 'java.io.tmpdir'
1514     * which may or may not have a trailing file separator.
1515     * This can affect code that uses String processing to manipulate pathnames rather
1516     * than the standard libary methods in classes such as {@link File}
1517     *
1518     * @return the path to the system temporary directory as a String
1519     * @since 2.0
1520     */
1521    public static String getTempDirectoryPath() {
1522        return System.getProperty("java.io.tmpdir");
1523    }
1524
1525    /**
1526     * Returns a {@link File} representing the user's home directory.
1527     *
1528     * @return the user's home directory.
1529     * @since 2.0
1530     */
1531    public static File getUserDirectory() {
1532        return new File(getUserDirectoryPath());
1533    }
1534
1535    /**
1536     * Returns the path to the user's home directory.
1537     *
1538     * @return the path to the user's home directory.
1539     * @since 2.0
1540     */
1541    public static String getUserDirectoryPath() {
1542        return System.getProperty("user.home");
1543    }
1544
1545    /**
1546     * Tests whether the specified {@link File} is a directory or not. Implemented as a
1547     * null-safe delegate to {@link Files#isDirectory(Path path, LinkOption... options)}.
1548     *
1549     * @param   file the path to the file.
1550     * @param   options options indicating how symbolic links are handled
1551     * @return  {@code true} if the file is a directory; {@code false} if
1552     *          the path is null, the file does not exist, is not a directory, or it cannot
1553     *          be determined if the file is a directory or not.
1554     * @throws SecurityException     In the case of the default provider, and a security manager is installed, the
1555     *                               {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read
1556     *                               access to the directory.
1557     * @since 2.9.0
1558     */
1559    public static boolean isDirectory(final File file, final LinkOption... options) {
1560        return file != null && Files.isDirectory(file.toPath(), options);
1561    }
1562
1563    /**
1564     * Tests whether the directory is empty.
1565     *
1566     * @param directory the directory to query.
1567     * @return whether the directory is empty.
1568     * @throws IOException if an I/O error occurs.
1569     * @throws NotDirectoryException if the file could not otherwise be opened because it is not a directory
1570     *                               <em>(optional specific exception)</em>.
1571     * @since 2.9.0
1572     */
1573    public static boolean isEmptyDirectory(final File directory) throws IOException {
1574        return PathUtils.isEmptyDirectory(directory.toPath());
1575    }
1576
1577    /**
1578     * Tests if the specified {@link File} is newer than the specified {@link ChronoLocalDate}
1579     * at the end of day.
1580     *
1581     * <p>Note: The input date is assumed to be in the system default time-zone with the time
1582     * part set to the current time. To use a non-default time-zone use the method
1583     * {@link #isFileNewer(File, ChronoLocalDateTime, ZoneId)
1584     * isFileNewer(file, chronoLocalDate.atTime(LocalTime.now(zoneId)), zoneId)} where
1585     * {@code zoneId} is a valid {@link ZoneId}.
1586     *
1587     * @param file            the {@link File} of which the modification date must be compared.
1588     * @param chronoLocalDate the date reference.
1589     * @return true if the {@link File} exists and has been modified after the given
1590     * {@link ChronoLocalDate} at the current time.
1591     * @throws UncheckedIOException if an I/O error occurs
1592     * @throws NullPointerException if the file or local date is {@code null}.
1593     * @since 2.8.0
1594     */
1595    public static boolean isFileNewer(final File file, final ChronoLocalDate chronoLocalDate) {
1596        return isFileNewer(file, chronoLocalDate, LocalTime.MAX);
1597    }
1598
1599    /**
1600     * Tests if the specified {@link File} is newer than the specified {@link ChronoLocalDate}
1601     * at the specified time.
1602     *
1603     * <p>Note: The input date and time are assumed to be in the system default time-zone. To use a
1604     * non-default time-zone use the method {@link #isFileNewer(File, ChronoLocalDateTime, ZoneId)
1605     * isFileNewer(file, chronoLocalDate.atTime(localTime), zoneId)} where {@code zoneId} is a valid
1606     * {@link ZoneId}.
1607     *
1608     * @param file            the {@link File} of which the modification date must be compared.
1609     * @param chronoLocalDate the date reference.
1610     * @param localTime       the time reference.
1611     * @return true if the {@link File} exists and has been modified after the given
1612     * {@link ChronoLocalDate} at the given time.
1613     * @throws UncheckedIOException if an I/O error occurs
1614     * @throws NullPointerException if the file, local date or zone ID is {@code null}.
1615     * @since 2.8.0
1616     */
1617    public static boolean isFileNewer(final File file, final ChronoLocalDate chronoLocalDate, final LocalTime localTime) {
1618        Objects.requireNonNull(chronoLocalDate, "chronoLocalDate");
1619        Objects.requireNonNull(localTime, "localTime");
1620        return isFileNewer(file, chronoLocalDate.atTime(localTime));
1621    }
1622
1623    /**
1624     * Tests if the specified {@link File} is newer than the specified {@link ChronoLocalDate} at the specified
1625     * {@link OffsetTime}.
1626     *
1627     * @param file the {@link File} of which the modification date must be compared
1628     * @param chronoLocalDate the date reference
1629     * @param offsetTime the time reference
1630     * @return true if the {@link File} exists and has been modified after the given {@link ChronoLocalDate} at the given
1631     *         {@link OffsetTime}.
1632     * @throws UncheckedIOException if an I/O error occurs
1633     * @throws NullPointerException if the file, local date or zone ID is {@code null}
1634     * @since 2.12.0
1635     */
1636    public static boolean isFileNewer(final File file, final ChronoLocalDate chronoLocalDate, final OffsetTime offsetTime) {
1637        Objects.requireNonNull(chronoLocalDate, "chronoLocalDate");
1638        Objects.requireNonNull(offsetTime, "offsetTime");
1639        return isFileNewer(file, chronoLocalDate.atTime(offsetTime.toLocalTime()));
1640    }
1641
1642    /**
1643     * Tests if the specified {@link File} is newer than the specified {@link ChronoLocalDateTime}
1644     * at the system-default time zone.
1645     *
1646     * <p>Note: The input date and time is assumed to be in the system default time-zone. To use a
1647     * non-default time-zone use the method {@link #isFileNewer(File, ChronoLocalDateTime, ZoneId)
1648     * isFileNewer(file, chronoLocalDateTime, zoneId)} where {@code zoneId} is a valid
1649     * {@link ZoneId}.
1650     *
1651     * @param file                the {@link File} of which the modification date must be compared.
1652     * @param chronoLocalDateTime the date reference.
1653     * @return true if the {@link File} exists and has been modified after the given
1654     * {@link ChronoLocalDateTime} at the system-default time zone.
1655     * @throws UncheckedIOException if an I/O error occurs
1656     * @throws NullPointerException if the file or local date time is {@code null}.
1657     * @since 2.8.0
1658     */
1659    public static boolean isFileNewer(final File file, final ChronoLocalDateTime<?> chronoLocalDateTime) {
1660        return isFileNewer(file, chronoLocalDateTime, ZoneId.systemDefault());
1661    }
1662
1663    /**
1664     * Tests if the specified {@link File} is newer than the specified {@link ChronoLocalDateTime}
1665     * at the specified {@link ZoneId}.
1666     *
1667     * @param file                the {@link File} of which the modification date must be compared.
1668     * @param chronoLocalDateTime the date reference.
1669     * @param zoneId              the time zone.
1670     * @return true if the {@link File} exists and has been modified after the given
1671     * {@link ChronoLocalDateTime} at the given {@link ZoneId}.
1672     * @throws UncheckedIOException if an I/O error occurs
1673     * @throws NullPointerException if the file, local date time or zone ID is {@code null}.
1674     * @since 2.8.0
1675     */
1676    public static boolean isFileNewer(final File file, final ChronoLocalDateTime<?> chronoLocalDateTime, final ZoneId zoneId) {
1677        Objects.requireNonNull(chronoLocalDateTime, "chronoLocalDateTime");
1678        Objects.requireNonNull(zoneId, "zoneId");
1679        return isFileNewer(file, chronoLocalDateTime.atZone(zoneId));
1680    }
1681
1682    /**
1683     * Tests if the specified {@link File} is newer than the specified {@link ChronoZonedDateTime}.
1684     *
1685     * @param file                the {@link File} of which the modification date must be compared.
1686     * @param chronoZonedDateTime the date reference.
1687     * @return true if the {@link File} exists and has been modified after the given
1688     * {@link ChronoZonedDateTime}.
1689     * @throws NullPointerException if the file or zoned date time is {@code null}.
1690     * @throws UncheckedIOException if an I/O error occurs
1691     * @since 2.8.0
1692     */
1693    public static boolean isFileNewer(final File file, final ChronoZonedDateTime<?> chronoZonedDateTime) {
1694        Objects.requireNonNull(file, PROTOCOL_FILE);
1695        Objects.requireNonNull(chronoZonedDateTime, "chronoZonedDateTime");
1696        return Uncheck.get(() -> PathUtils.isNewer(file.toPath(), chronoZonedDateTime));
1697    }
1698
1699    /**
1700     * Tests if the specified {@link File} is newer than the specified {@link Date}.
1701     *
1702     * @param file the {@link File} of which the modification date must be compared.
1703     * @param date the date reference.
1704     * @return true if the {@link File} exists and has been modified
1705     * after the given {@link Date}.
1706     * @throws UncheckedIOException if an I/O error occurs
1707     * @throws NullPointerException if the file or date is {@code null}.
1708     */
1709    public static boolean isFileNewer(final File file, final Date date) {
1710        Objects.requireNonNull(date, "date");
1711        return isFileNewer(file, date.getTime());
1712    }
1713
1714    /**
1715     * Tests if the specified {@link File} is newer than the reference {@link File}.
1716     *
1717     * @param file      the {@link File} of which the modification date must be compared.
1718     * @param reference the {@link File} of which the modification date is used.
1719     * @return true if the {@link File} exists and has been modified more
1720     * recently than the reference {@link File}.
1721     * @throws NullPointerException if the file or reference file is {@code null}.
1722     * @throws UncheckedIOException if the reference file doesn't exist.
1723     */
1724    public static boolean isFileNewer(final File file, final File reference) {
1725        return Uncheck.get(() -> PathUtils.isNewer(file.toPath(), reference.toPath()));
1726    }
1727
1728    /**
1729     * Tests if the specified {@link File} is newer than the specified {@link FileTime}.
1730     *
1731     * @param file the {@link File} of which the modification date must be compared.
1732     * @param fileTime the file time reference.
1733     * @return true if the {@link File} exists and has been modified after the given {@link FileTime}.
1734     * @throws IOException if an I/O error occurs.
1735     * @throws NullPointerException if the file or local date is {@code null}.
1736     * @since 2.12.0
1737     */
1738    public static boolean isFileNewer(final File file, final FileTime fileTime) throws IOException {
1739        Objects.requireNonNull(file, PROTOCOL_FILE);
1740        return PathUtils.isNewer(file.toPath(), fileTime);
1741    }
1742
1743    /**
1744     * Tests if the specified {@link File} is newer than the specified {@link Instant}.
1745     *
1746     * @param file the {@link File} of which the modification date must be compared.
1747     * @param instant the date reference.
1748     * @return true if the {@link File} exists and has been modified after the given {@link Instant}.
1749     * @throws NullPointerException if the file or instant is {@code null}.
1750     * @throws UncheckedIOException if an I/O error occurs
1751     * @since 2.8.0
1752     */
1753    public static boolean isFileNewer(final File file, final Instant instant) {
1754        Objects.requireNonNull(instant, "instant");
1755        return Uncheck.get(() -> PathUtils.isNewer(file.toPath(), instant));
1756    }
1757
1758    /**
1759     * Tests if the specified {@link File} is newer than the specified time reference.
1760     *
1761     * @param file       the {@link File} of which the modification date must be compared.
1762     * @param timeMillis the time reference measured in milliseconds since the
1763     *                   epoch (00:00:00 GMT, January 1, 1970).
1764     * @return true if the {@link File} exists and has been modified after the given time reference.
1765     * @throws UncheckedIOException if an I/O error occurs
1766     * @throws NullPointerException if the file is {@code null}.
1767     */
1768    public static boolean isFileNewer(final File file, final long timeMillis) {
1769        Objects.requireNonNull(file, PROTOCOL_FILE);
1770        return Uncheck.get(() -> PathUtils.isNewer(file.toPath(), timeMillis));
1771    }
1772
1773    /**
1774     * Tests if the specified {@link File} is newer than the specified {@link OffsetDateTime}.
1775     *
1776     * @param file the {@link File} of which the modification date must be compared
1777     * @param offsetDateTime the date reference
1778     * @return true if the {@link File} exists and has been modified before the given {@link OffsetDateTime}.
1779     * @throws UncheckedIOException if an I/O error occurs
1780     * @throws NullPointerException if the file or zoned date time is {@code null}
1781     * @since 2.12.0
1782     */
1783    public static boolean isFileNewer(final File file, final OffsetDateTime offsetDateTime) {
1784        Objects.requireNonNull(offsetDateTime, "offsetDateTime");
1785        return isFileNewer(file, offsetDateTime.toInstant());
1786    }
1787
1788    /**
1789     * Tests if the specified {@link File} is older than the specified {@link ChronoLocalDate}
1790     * at the end of day.
1791     *
1792     * <p>Note: The input date is assumed to be in the system default time-zone with the time
1793     * part set to the current time. To use a non-default time-zone use the method
1794     * {@link #isFileOlder(File, ChronoLocalDateTime, ZoneId)
1795     * isFileOlder(file, chronoLocalDate.atTime(LocalTime.now(zoneId)), zoneId)} where
1796     * {@code zoneId} is a valid {@link ZoneId}.
1797     *
1798     * @param file            the {@link File} of which the modification date must be compared.
1799     * @param chronoLocalDate the date reference.
1800     * @return true if the {@link File} exists and has been modified before the given
1801     * {@link ChronoLocalDate} at the current time.
1802     * @throws NullPointerException if the file or local date is {@code null}.
1803     * @throws UncheckedIOException if an I/O error occurs
1804     * @see ZoneId#systemDefault()
1805     * @see LocalTime#now()
1806     * @since 2.8.0
1807     */
1808    public static boolean isFileOlder(final File file, final ChronoLocalDate chronoLocalDate) {
1809        return isFileOlder(file, chronoLocalDate, LocalTime.MAX);
1810    }
1811
1812    /**
1813     * Tests if the specified {@link File} is older than the specified {@link ChronoLocalDate}
1814     * at the specified {@link LocalTime}.
1815     *
1816     * <p>Note: The input date and time are assumed to be in the system default time-zone. To use a
1817     * non-default time-zone use the method {@link #isFileOlder(File, ChronoLocalDateTime, ZoneId)
1818     * isFileOlder(file, chronoLocalDate.atTime(localTime), zoneId)} where {@code zoneId} is a valid
1819     * {@link ZoneId}.
1820     *
1821     * @param file            the {@link File} of which the modification date must be compared.
1822     * @param chronoLocalDate the date reference.
1823     * @param localTime       the time reference.
1824     * @return true if the {@link File} exists and has been modified before the
1825     * given {@link ChronoLocalDate} at the specified time.
1826     * @throws UncheckedIOException if an I/O error occurs
1827     * @throws NullPointerException if the file, local date or local time is {@code null}.
1828     * @see ZoneId#systemDefault()
1829     * @since 2.8.0
1830     */
1831    public static boolean isFileOlder(final File file, final ChronoLocalDate chronoLocalDate, final LocalTime localTime) {
1832        Objects.requireNonNull(chronoLocalDate, "chronoLocalDate");
1833        Objects.requireNonNull(localTime, "localTime");
1834        return isFileOlder(file, chronoLocalDate.atTime(localTime));
1835    }
1836
1837    /**
1838     * Tests if the specified {@link File} is older than the specified {@link ChronoLocalDate} at the specified
1839     * {@link OffsetTime}.
1840     *
1841     * @param file the {@link File} of which the modification date must be compared
1842     * @param chronoLocalDate the date reference
1843     * @param offsetTime the time reference
1844     * @return true if the {@link File} exists and has been modified after the given {@link ChronoLocalDate} at the given
1845     *         {@link OffsetTime}.
1846     * @throws NullPointerException if the file, local date or zone ID is {@code null}
1847     * @throws UncheckedIOException if an I/O error occurs
1848     * @since 2.12.0
1849     */
1850    public static boolean isFileOlder(final File file, final ChronoLocalDate chronoLocalDate, final OffsetTime offsetTime) {
1851        Objects.requireNonNull(chronoLocalDate, "chronoLocalDate");
1852        Objects.requireNonNull(offsetTime, "offsetTime");
1853        return isFileOlder(file, chronoLocalDate.atTime(offsetTime.toLocalTime()));
1854    }
1855
1856    /**
1857     * Tests if the specified {@link File} is older than the specified {@link ChronoLocalDateTime}
1858     * at the system-default time zone.
1859     *
1860     * <p>Note: The input date and time is assumed to be in the system default time-zone. To use a
1861     * non-default time-zone use the method {@link #isFileOlder(File, ChronoLocalDateTime, ZoneId)
1862     * isFileOlder(file, chronoLocalDateTime, zoneId)} where {@code zoneId} is a valid
1863     * {@link ZoneId}.
1864     *
1865     * @param file                the {@link File} of which the modification date must be compared.
1866     * @param chronoLocalDateTime the date reference.
1867     * @return true if the {@link File} exists and has been modified before the given
1868     * {@link ChronoLocalDateTime} at the system-default time zone.
1869     * @throws NullPointerException if the file or local date time is {@code null}.
1870     * @throws UncheckedIOException if an I/O error occurs
1871     * @see ZoneId#systemDefault()
1872     * @since 2.8.0
1873     */
1874    public static boolean isFileOlder(final File file, final ChronoLocalDateTime<?> chronoLocalDateTime) {
1875        return isFileOlder(file, chronoLocalDateTime, ZoneId.systemDefault());
1876    }
1877
1878    /**
1879     * Tests if the specified {@link File} is older than the specified {@link ChronoLocalDateTime}
1880     * at the specified {@link ZoneId}.
1881     *
1882     * @param file          the {@link File} of which the modification date must be compared.
1883     * @param chronoLocalDateTime the date reference.
1884     * @param zoneId        the time zone.
1885     * @return true if the {@link File} exists and has been modified before the given
1886     * {@link ChronoLocalDateTime} at the given {@link ZoneId}.
1887     * @throws NullPointerException if the file, local date time or zone ID is {@code null}.
1888     * @throws UncheckedIOException if an I/O error occurs
1889     * @since 2.8.0
1890     */
1891    public static boolean isFileOlder(final File file, final ChronoLocalDateTime<?> chronoLocalDateTime, final ZoneId zoneId) {
1892        Objects.requireNonNull(chronoLocalDateTime, "chronoLocalDateTime");
1893        Objects.requireNonNull(zoneId, "zoneId");
1894        return isFileOlder(file, chronoLocalDateTime.atZone(zoneId));
1895    }
1896
1897    /**
1898     * Tests if the specified {@link File} is older than the specified {@link ChronoZonedDateTime}.
1899     *
1900     * @param file                the {@link File} of which the modification date must be compared.
1901     * @param chronoZonedDateTime the date reference.
1902     * @return true if the {@link File} exists and has been modified before the given
1903     * {@link ChronoZonedDateTime}.
1904     * @throws NullPointerException if the file or zoned date time is {@code null}.
1905     * @throws UncheckedIOException if an I/O error occurs
1906     * @since 2.8.0
1907     */
1908    public static boolean isFileOlder(final File file, final ChronoZonedDateTime<?> chronoZonedDateTime) {
1909        Objects.requireNonNull(chronoZonedDateTime, "chronoZonedDateTime");
1910        return isFileOlder(file, chronoZonedDateTime.toInstant());
1911    }
1912
1913    /**
1914     * Tests if the specified {@link File} is older than the specified {@link Date}.
1915     *
1916     * @param file the {@link File} of which the modification date must be compared.
1917     * @param date the date reference.
1918     * @return true if the {@link File} exists and has been modified before the given {@link Date}.
1919     * @throws NullPointerException if the file or date is {@code null}.
1920     * @throws UncheckedIOException if an I/O error occurs
1921     */
1922    public static boolean isFileOlder(final File file, final Date date) {
1923        Objects.requireNonNull(date, "date");
1924        return isFileOlder(file, date.getTime());
1925    }
1926
1927    /**
1928     * Tests if the specified {@link File} is older than the reference {@link File}.
1929     *
1930     * @param file      the {@link File} of which the modification date must be compared.
1931     * @param reference the {@link File} of which the modification date is used.
1932     * @return true if the {@link File} exists and has been modified before the reference {@link File}.
1933     * @throws NullPointerException if the file or reference file is {@code null}.
1934     * @throws FileNotFoundException if the reference file doesn't exist.
1935     * @throws UncheckedIOException if an I/O error occurs
1936     */
1937    public static boolean isFileOlder(final File file, final File reference) throws FileNotFoundException {
1938        return Uncheck.get(() -> PathUtils.isOlder(file.toPath(), reference.toPath()));
1939    }
1940
1941    /**
1942     * Tests if the specified {@link File} is older than the specified {@link FileTime}.
1943     *
1944     * @param file the {@link File} of which the modification date must be compared.
1945     * @param fileTime the file time reference.
1946     * @return true if the {@link File} exists and has been modified before the given {@link FileTime}.
1947     * @throws IOException if an I/O error occurs.
1948     * @throws NullPointerException if the file or local date is {@code null}.
1949     * @since 2.12.0
1950     */
1951    public static boolean isFileOlder(final File file, final FileTime fileTime) throws IOException {
1952        Objects.requireNonNull(file, PROTOCOL_FILE);
1953        return PathUtils.isOlder(file.toPath(), fileTime);
1954    }
1955
1956    /**
1957     * Tests if the specified {@link File} is older than the specified {@link Instant}.
1958     *
1959     * @param file    the {@link File} of which the modification date must be compared.
1960     * @param instant the date reference.
1961     * @return true if the {@link File} exists and has been modified before the given {@link Instant}.
1962     * @throws NullPointerException if the file or instant is {@code null}.
1963     * @since 2.8.0
1964     */
1965    public static boolean isFileOlder(final File file, final Instant instant) {
1966        Objects.requireNonNull(instant, "instant");
1967        return Uncheck.get(() -> PathUtils.isOlder(file.toPath(), instant));
1968    }
1969
1970    /**
1971     * Tests if the specified {@link File} is older than the specified time reference.
1972     *
1973     * @param file       the {@link File} of which the modification date must be compared.
1974     * @param timeMillis the time reference measured in milliseconds since the
1975     *                   epoch (00:00:00 GMT, January 1, 1970).
1976     * @return true if the {@link File} exists and has been modified before the given time reference.
1977     * @throws NullPointerException if the file is {@code null}.
1978     * @throws UncheckedIOException if an I/O error occurs
1979     */
1980    public static boolean isFileOlder(final File file, final long timeMillis) {
1981        Objects.requireNonNull(file, PROTOCOL_FILE);
1982        return Uncheck.get(() -> PathUtils.isOlder(file.toPath(), timeMillis));
1983    }
1984
1985    /**
1986     * Tests if the specified {@link File} is older than the specified {@link OffsetDateTime}.
1987     *
1988     * @param file the {@link File} of which the modification date must be compared
1989     * @param offsetDateTime the date reference
1990     * @return true if the {@link File} exists and has been modified before the given {@link OffsetDateTime}.
1991     * @throws NullPointerException if the file or zoned date time is {@code null}
1992     * @since 2.12.0
1993     */
1994    public static boolean isFileOlder(final File file, final OffsetDateTime offsetDateTime) {
1995        Objects.requireNonNull(offsetDateTime, "offsetDateTime");
1996        return isFileOlder(file, offsetDateTime.toInstant());
1997    }
1998
1999    /**
2000     * Tests whether the given URL is a file URL.
2001     *
2002     * @param url The URL to test.
2003     * @return Whether the given URL is a file URL.
2004     */
2005    private static boolean isFileProtocol(final URL url) {
2006        return PROTOCOL_FILE.equalsIgnoreCase(url.getProtocol());
2007    }
2008
2009    /**
2010     * Tests whether the specified {@link File} is a regular file or not. Implemented as a
2011     * null-safe delegate to {@link Files#isRegularFile(Path path, LinkOption... options)}.
2012     *
2013     * @param   file the path to the file.
2014     * @param   options options indicating how symbolic links are handled
2015     * @return  {@code true} if the file is a regular file; {@code false} if
2016     *          the path is null, the file does not exist, is not a regular file, or it cannot
2017     *          be determined if the file is a regular file or not.
2018     * @throws SecurityException     In the case of the default provider, and a security manager is installed, the
2019     *                               {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read
2020     *                               access to the directory.
2021     * @since 2.9.0
2022     */
2023    public static boolean isRegularFile(final File file, final LinkOption... options) {
2024        return file != null && Files.isRegularFile(file.toPath(), options);
2025    }
2026
2027    /**
2028     * Tests whether the specified file is a symbolic link rather than an actual file.
2029     * <p>
2030     * This method delegates to {@link Files#isSymbolicLink(Path path)}
2031     * </p>
2032     *
2033     * @param file the file to test.
2034     * @return true if the file is a symbolic link, see {@link Files#isSymbolicLink(Path path)}.
2035     * @since 2.0
2036     * @see Files#isSymbolicLink(Path)
2037     */
2038    public static boolean isSymlink(final File file) {
2039        return file != null && Files.isSymbolicLink(file.toPath());
2040    }
2041
2042    /**
2043     * Iterates over the files in given directory (and optionally
2044     * its subdirectories).
2045     * <p>
2046     * The resulting iterator MUST be consumed in its entirety in order to close its underlying stream.
2047     * </p>
2048     * <p>
2049     * All files found are filtered by an IOFileFilter.
2050     * </p>
2051     *
2052     * @param directory  the directory to search in
2053     * @param fileFilter filter to apply when finding files.
2054     * @param dirFilter  optional filter to apply when finding subdirectories.
2055     *                   If this parameter is {@code null}, subdirectories will not be included in the
2056     *                   search. Use TrueFileFilter.INSTANCE to match all directories.
2057     * @return an iterator of {@link File} for the matching files
2058     * @see org.apache.commons.io.filefilter.FileFilterUtils
2059     * @see org.apache.commons.io.filefilter.NameFileFilter
2060     * @since 1.2
2061     */
2062    public static Iterator<File> iterateFiles(final File directory, final IOFileFilter fileFilter, final IOFileFilter dirFilter) {
2063        return listFiles(directory, fileFilter, dirFilter).iterator();
2064    }
2065
2066    /**
2067     * Iterates over the files in a given directory (and optionally
2068     * its subdirectories) which match an array of extensions.
2069     * <p>
2070     * The resulting iterator MUST be consumed in its entirety in order to close its underlying stream.
2071     * </p>
2072     *
2073     * @param directory  the directory to search in
2074     * @param extensions an array of extensions, for example, {"java","xml"}. If this
2075     *                   parameter is {@code null}, all files are returned.
2076     * @param recursive  if true all subdirectories are searched as well
2077     * @return an iterator of {@link File} with the matching files
2078     * @since 1.2
2079     */
2080    public static Iterator<File> iterateFiles(final File directory, final String[] extensions, final boolean recursive) {
2081        return StreamIterator.iterator(Uncheck.get(() -> streamFiles(directory, recursive, extensions)));
2082    }
2083
2084    /**
2085     * Iterates over the files in given directory (and optionally
2086     * its subdirectories).
2087     * <p>
2088     * The resulting iterator MUST be consumed in its entirety in order to close its underlying stream.
2089     * </p>
2090     * <p>
2091     * All files found are filtered by an IOFileFilter.
2092     * </p>
2093     * <p>
2094     * The resulting iterator includes the subdirectories themselves.
2095     * </p>
2096     *
2097     * @param directory  the directory to search in
2098     * @param fileFilter filter to apply when finding files.
2099     * @param dirFilter  optional filter to apply when finding subdirectories.
2100     *                   If this parameter is {@code null}, subdirectories will not be included in the
2101     *                   search. Use TrueFileFilter.INSTANCE to match all directories.
2102     * @return an iterator of {@link File} for the matching files
2103     * @see org.apache.commons.io.filefilter.FileFilterUtils
2104     * @see org.apache.commons.io.filefilter.NameFileFilter
2105     * @since 2.2
2106     */
2107    public static Iterator<File> iterateFilesAndDirs(final File directory, final IOFileFilter fileFilter, final IOFileFilter dirFilter) {
2108        return listFilesAndDirs(directory, fileFilter, dirFilter).iterator();
2109    }
2110
2111    /**
2112     * Returns the last modification time in milliseconds via
2113     * {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}.
2114     * <p>
2115     * For the best precision, use {@link #lastModifiedFileTime(File)}.
2116     * </p>
2117     * <p>
2118     * Use this method to avoid issues with {@link File#lastModified()} like
2119     * <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a> where {@link File#lastModified()} is
2120     * losing milliseconds (always ends in 000). This bug exists in OpenJDK 8 and 9, and is fixed in 10.
2121     * </p>
2122     *
2123     * @param file The File to query.
2124     * @return See {@link java.nio.file.attribute.FileTime#toMillis()}.
2125     * @throws IOException if an I/O error occurs.
2126     * @since 2.9.0
2127     */
2128    public static long lastModified(final File file) throws IOException {
2129        // https://bugs.openjdk.java.net/browse/JDK-8177809
2130        // File.lastModified() is losing milliseconds (always ends in 000)
2131        // This bug is in OpenJDK 8 and 9, and fixed in 10.
2132        return lastModifiedFileTime(file).toMillis();
2133    }
2134
2135    /**
2136     * Returns the last modification {@link FileTime} via
2137     * {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}.
2138     * <p>
2139     * Use this method to avoid issues with {@link File#lastModified()} like
2140     * <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a> where {@link File#lastModified()} is
2141     * losing milliseconds (always ends in 000). This bug exists in OpenJDK 8 and 9, and is fixed in 10.
2142     * </p>
2143     *
2144     * @param file The File to query.
2145     * @return See {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}.
2146     * @throws IOException if an I/O error occurs.
2147     * @since 2.12.0
2148     */
2149    public static FileTime lastModifiedFileTime(final File file) throws IOException {
2150        // https://bugs.openjdk.java.net/browse/JDK-8177809
2151        // File.lastModified() is losing milliseconds (always ends in 000)
2152        // This bug is in OpenJDK 8 and 9, and fixed in 10.
2153        return Files.getLastModifiedTime(Objects.requireNonNull(file, PROTOCOL_FILE).toPath());
2154    }
2155
2156    /**
2157     * Returns the last modification time in milliseconds via
2158     * {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}.
2159     * <p>
2160     * For the best precision, use {@link #lastModifiedFileTime(File)}.
2161     * </p>
2162     * <p>
2163     * Use this method to avoid issues with {@link File#lastModified()} like
2164     * <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a> where {@link File#lastModified()} is
2165     * losing milliseconds (always ends in 000). This bug exists in OpenJDK 8 and 9, and is fixed in 10.
2166     * </p>
2167     *
2168     * @param file The File to query.
2169     * @return See {@link java.nio.file.attribute.FileTime#toMillis()}.
2170     * @throws UncheckedIOException if an I/O error occurs.
2171     * @since 2.9.0
2172     */
2173    public static long lastModifiedUnchecked(final File file) {
2174        // https://bugs.openjdk.java.net/browse/JDK-8177809
2175        // File.lastModified() is losing milliseconds (always ends in 000)
2176        // This bug is in OpenJDK 8 and 9, and fixed in 10.
2177        return Uncheck.apply(FileUtils::lastModified, file);
2178    }
2179
2180    /**
2181     * Returns an Iterator for the lines in a {@link File} using the default encoding for the VM.
2182     *
2183     * @param file the file to open for input, must not be {@code null}
2184     * @return an Iterator of the lines in the file, never {@code null}
2185     * @throws NullPointerException if file is {@code null}.
2186     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2187     *         other reason cannot be opened for reading.
2188     * @throws IOException if an I/O error occurs.
2189     * @see #lineIterator(File, String)
2190     * @since 1.3
2191     */
2192    public static LineIterator lineIterator(final File file) throws IOException {
2193        return lineIterator(file, null);
2194    }
2195
2196    /**
2197     * Returns an Iterator for the lines in a {@link File}.
2198     * <p>
2199     * This method opens an {@link InputStream} for the file.
2200     * When you have finished with the iterator you should close the stream
2201     * to free internal resources. This can be done by using a try-with-resources block or calling the
2202     * {@link LineIterator#close()} method.
2203     * </p>
2204     * <p>
2205     * The recommended usage pattern is:
2206     * </p>
2207     * <pre>
2208     * LineIterator it = FileUtils.lineIterator(file, StandardCharsets.UTF_8.name());
2209     * try {
2210     *   while (it.hasNext()) {
2211     *     String line = it.nextLine();
2212     *     /// do something with line
2213     *   }
2214     * } finally {
2215     *   LineIterator.closeQuietly(iterator);
2216     * }
2217     * </pre>
2218     * <p>
2219     * If an exception occurs during the creation of the iterator, the
2220     * underlying stream is closed.
2221     * </p>
2222     *
2223     * @param file     the file to open for input, must not be {@code null}
2224     * @param charsetName the name of the requested charset, {@code null} means platform default
2225     * @return a LineIterator for lines in the file, never {@code null}; MUST be closed by the caller.
2226     * @throws NullPointerException if file is {@code null}.
2227     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2228     *         other reason cannot be opened for reading.
2229     * @throws IOException if an I/O error occurs.
2230     * @since 1.2
2231     */
2232    @SuppressWarnings("resource") // Caller closes the result LineIterator.
2233    public static LineIterator lineIterator(final File file, final String charsetName) throws IOException {
2234        InputStream inputStream = null;
2235        try {
2236            inputStream = Files.newInputStream(file.toPath());
2237            return IOUtils.lineIterator(inputStream, charsetName);
2238        } catch (final IOException | RuntimeException ex) {
2239            IOUtils.closeQuietly(inputStream, ex::addSuppressed);
2240            throw ex;
2241        }
2242    }
2243
2244    private static AccumulatorPathVisitor listAccumulate(final File directory, final IOFileFilter fileFilter, final IOFileFilter dirFilter,
2245            final FileVisitOption... options) throws IOException {
2246        final boolean isDirFilterSet = dirFilter != null;
2247        final FileEqualsFileFilter rootDirFilter = new FileEqualsFileFilter(directory);
2248        final PathFilter dirPathFilter = isDirFilterSet ? rootDirFilter.or(dirFilter) : rootDirFilter;
2249        final AccumulatorPathVisitor visitor = new AccumulatorPathVisitor(Counters.noopPathCounters(), fileFilter, dirPathFilter,
2250                (p, e) -> FileVisitResult.CONTINUE);
2251        final Set<FileVisitOption> optionSet = new HashSet<>();
2252        if (options != null) {
2253            Collections.addAll(optionSet, options);
2254        }
2255        Files.walkFileTree(directory.toPath(), optionSet, toMaxDepth(isDirFilterSet), visitor);
2256        return visitor;
2257    }
2258
2259    /**
2260     * Lists files in a directory, asserting that the supplied directory exists and is a directory.
2261     *
2262     * @param directory The directory to list
2263     * @param fileFilter Optional file filter, may be null.
2264     * @return The files in the directory, never {@code null}.
2265     * @throws NullPointerException if directory is {@code null}.
2266     * @throws IllegalArgumentException if {@link directory} exists but is not a directory
2267     * @throws IOException if an I/O error occurs.
2268     */
2269    private static File[] listFiles(final File directory, final FileFilter fileFilter) throws IOException {
2270        requireDirectoryExists(directory, "directory");
2271        final File[] files = fileFilter == null ? directory.listFiles() : directory.listFiles(fileFilter);
2272        if (files == null) {
2273            // null if the directory does not denote a directory, or if an I/O error occurs.
2274            throw new IOException("Unknown I/O error listing contents of directory: " + directory);
2275        }
2276        return files;
2277    }
2278
2279    /**
2280     * Finds files within a given directory (and optionally its
2281     * subdirectories). All files found are filtered by an IOFileFilter.
2282     * <p>
2283     * If your search should recurse into subdirectories you can pass in
2284     * an IOFileFilter for directories. You don't need to bind a
2285     * DirectoryFileFilter (via logical AND) to this filter. This method does
2286     * that for you.
2287     * </p>
2288     * <p>
2289     * An example: If you want to search through all directories called
2290     * "temp" you pass in {@code FileFilterUtils.NameFileFilter("temp")}
2291     * </p>
2292     * <p>
2293     * Another common usage of this method is find files in a directory
2294     * tree but ignoring the directories generated CVS. You can simply pass
2295     * in {@code FileFilterUtils.makeCVSAware(null)}.
2296     * </p>
2297     *
2298     * @param directory  the directory to search in
2299     * @param fileFilter filter to apply when finding files. Must not be {@code null},
2300     *                   use {@link TrueFileFilter#INSTANCE} to match all files in selected directories.
2301     * @param dirFilter  optional filter to apply when finding subdirectories.
2302     *                   If this parameter is {@code null}, subdirectories will not be included in the
2303     *                   search. Use {@link TrueFileFilter#INSTANCE} to match all directories.
2304     * @return a collection of {@link File} with the matching files
2305     * @see org.apache.commons.io.filefilter.FileFilterUtils
2306     * @see org.apache.commons.io.filefilter.NameFileFilter
2307     */
2308    public static Collection<File> listFiles(final File directory, final IOFileFilter fileFilter, final IOFileFilter dirFilter) {
2309        final AccumulatorPathVisitor visitor = Uncheck
2310            .apply(d -> listAccumulate(d, FileFileFilter.INSTANCE.and(fileFilter), dirFilter, FileVisitOption.FOLLOW_LINKS), directory);
2311        return toList(visitor.getFileList().stream().map(Path::toFile));
2312    }
2313
2314    /**
2315     * Lists files within a given directory (and optionally its subdirectories)
2316     * which match an array of extensions.
2317     *
2318     * @param directory  the directory to search in
2319     * @param extensions an array of extensions, for example, {"java","xml"}. If this
2320     *                   parameter is {@code null}, all files are returned.
2321     * @param recursive  if true all subdirectories are searched as well
2322     * @return a collection of {@link File} with the matching files
2323     */
2324    public static Collection<File> listFiles(final File directory, final String[] extensions, final boolean recursive) {
2325        try (Stream<File> fileStream = Uncheck.get(() -> streamFiles(directory, recursive, extensions))) {
2326            return toList(fileStream);
2327        }
2328    }
2329
2330    /**
2331     * Finds files within a given directory (and optionally its
2332     * subdirectories). All files found are filtered by an IOFileFilter.
2333     * <p>
2334     * The resulting collection includes the starting directory and
2335     * any subdirectories that match the directory filter.
2336     * </p>
2337     *
2338     * @param directory  the directory to search in
2339     * @param fileFilter filter to apply when finding files.
2340     * @param dirFilter  optional filter to apply when finding subdirectories.
2341     *                   If this parameter is {@code null}, subdirectories will not be included in the
2342     *                   search. Use TrueFileFilter.INSTANCE to match all directories.
2343     * @return a collection of {@link File} with the matching files
2344     * @see org.apache.commons.io.FileUtils#listFiles
2345     * @see org.apache.commons.io.filefilter.FileFilterUtils
2346     * @see org.apache.commons.io.filefilter.NameFileFilter
2347     * @since 2.2
2348     */
2349    public static Collection<File> listFilesAndDirs(final File directory, final IOFileFilter fileFilter, final IOFileFilter dirFilter) {
2350        final AccumulatorPathVisitor visitor = Uncheck.apply(d -> listAccumulate(d, fileFilter, dirFilter, FileVisitOption.FOLLOW_LINKS),
2351            directory);
2352        final List<Path> list = visitor.getFileList();
2353        list.addAll(visitor.getDirList());
2354        return toList(list.stream().map(Path::toFile));
2355    }
2356
2357    /**
2358     * Calls {@link File#mkdirs()} and throws an {@link IOException} on failure.
2359     * <p>
2360     * Creates all directories for a File object, including any necessary but non-existent parent directories. If the {@code directory} already exists or is
2361     * null, nothing happens.
2362     * </p>
2363     *
2364     * @param directory the receiver for {@code mkdirs()}. If the {@code directory} already exists or is null, nothing happens.
2365     * @return the given directory.
2366     * @throws IOException       if the directory was not created along with all its parent directories.
2367     * @throws IOException       if the given file object is not a directory.
2368     * @throws SecurityException See {@link File#mkdirs()}.
2369     * @see File#mkdirs()
2370     */
2371    private static File mkdirs(final File directory) throws IOException {
2372        if (directory != null && !directory.mkdirs() && !directory.isDirectory()) {
2373            throw new IOException("Cannot create directory '" + directory + "'.");
2374        }
2375        return directory;
2376    }
2377
2378    /**
2379     * Moves a directory.
2380     * <p>
2381     * When the destination directory is on another file system, do a "copy and delete".
2382     * </p>
2383     *
2384     * @param srcDir the directory to be moved.
2385     * @param destDir the destination directory.
2386     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2387     * @throws IllegalArgumentException if {@code srcDir} exists but is not a directory
2388     * @throws FileNotFoundException if the source does not exist.
2389     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
2390     * @since 1.4
2391     */
2392    public static void moveDirectory(final File srcDir, final File destDir) throws IOException {
2393        Objects.requireNonNull(destDir, "destination");
2394        requireDirectoryExists(srcDir, "srcDir");
2395        requireAbsent(destDir, "destDir");
2396        if (!srcDir.renameTo(destDir)) {
2397            if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath() + File.separator)) {
2398                throw new IOException("Cannot move directory: " + srcDir + " to a subdirectory of itself: " + destDir);
2399            }
2400            copyDirectory(srcDir, destDir);
2401            deleteDirectory(srcDir);
2402            if (srcDir.exists()) {
2403                throw new IOException("Failed to delete original directory '" + srcDir +
2404                        "' after copy to '" + destDir + "'");
2405            }
2406        }
2407    }
2408
2409    /**
2410     * Moves a directory to another directory.
2411     * <p>
2412     * If {@code createDestDir} is true, creates all destination parent directories, including any necessary but non-existent parent directories.
2413     * </p>
2414     *
2415     * @param source the directory to be moved.
2416     * @param destDir the destination file.
2417     * @param createDestDir If {@code true} create the destination directory, otherwise if {@code false} throw an
2418     *        IOException.
2419     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2420     * @throws IllegalArgumentException if the source or destination is invalid.
2421     * @throws FileNotFoundException if the source does not exist.
2422     * @throws IOException if the directory was not created along with all its parent directories, if enabled.
2423     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
2424     * @throws SecurityException See {@link File#mkdirs()}.
2425     * @since 1.4
2426     */
2427    public static void moveDirectoryToDirectory(final File source, final File destDir, final boolean createDestDir) throws IOException {
2428        validateMoveParameters(source, destDir);
2429        if (!destDir.isDirectory()) {
2430            if (destDir.exists()) {
2431                throw new IOException("Destination '" + destDir + "' is not a directory");
2432            }
2433            if (!createDestDir) {
2434                throw new FileNotFoundException("Destination directory '" + destDir + "' does not exist [createDestDir=" + false + "]");
2435            }
2436            mkdirs(destDir);
2437        }
2438        moveDirectory(source, new File(destDir, source.getName()));
2439    }
2440
2441    /**
2442     * Moves a file preserving attributes.
2443     * <p>
2444     * Shorthand for {@code moveFile(srcFile, destFile, StandardCopyOption.COPY_ATTRIBUTES)}.
2445     * </p>
2446     * <p>
2447     * When the destination file is on another file system, do a "copy and delete".
2448     * </p>
2449     *
2450     * @param srcFile the file to be moved.
2451     * @param destFile the destination file.
2452     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2453     * @throws FileExistsException if the destination file exists.
2454     * @throws FileNotFoundException if the source file does not exist.
2455     * @throws IllegalArgumentException if {@code srcFile} is a directory
2456     * @throws IOException if an error occurs.
2457     * @since 1.4
2458     */
2459    public static void moveFile(final File srcFile, final File destFile) throws IOException {
2460        moveFile(srcFile, destFile, StandardCopyOption.COPY_ATTRIBUTES);
2461    }
2462
2463    /**
2464     * Moves a file.
2465     * <p>
2466     * When the destination file is on another file system, do a "copy and delete".
2467     * </p>
2468     *
2469     * @param srcFile the file to be moved.
2470     * @param destFile the destination file.
2471     * @param copyOptions Copy options.
2472     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2473     * @throws FileExistsException if the destination file exists.
2474     * @throws FileNotFoundException if the source file does not exist.
2475     * @throws IllegalArgumentException if {@code srcFile} is a directory
2476     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
2477     * @since 2.9.0
2478     */
2479    public static void moveFile(final File srcFile, final File destFile, final CopyOption... copyOptions) throws IOException {
2480        Objects.requireNonNull(destFile, "destination");
2481        checkFileExists(srcFile, "srcFile");
2482        requireAbsent(destFile, "destFile");
2483        final boolean rename = srcFile.renameTo(destFile);
2484        if (!rename) {
2485            // Don't interfere with file date on move, handled by StandardCopyOption.COPY_ATTRIBUTES
2486            copyFile(srcFile, destFile, false, copyOptions);
2487            if (!srcFile.delete()) {
2488                deleteQuietly(destFile);
2489                throw new IOException("Failed to delete original file '" + srcFile + "' after copy to '" + destFile + "'");
2490            }
2491        }
2492    }
2493
2494    /**
2495     * Moves a file into a directory.
2496     * <p>
2497     * If {@code createDestDir} is true, creates all destination parent directories, including any necessary but non-existent parent directories.
2498     * </p>
2499     *
2500     * @param srcFile the file to be moved.
2501     * @param destDir the directory to move the file into
2502     * @param createDestDir if {@code true} create the destination directory. If {@code false} throw an
2503     *        IOException if the destination directory does not already exist.
2504     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2505     * @throws FileExistsException if the destination file exists.
2506     * @throws FileNotFoundException if the source file does not exist.
2507     * @throws IOException if source or destination is invalid.
2508     * @throws IOException if the directory was not created along with all its parent directories, if enabled.
2509     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
2510     * @throws SecurityException See {@link File#mkdirs()}.
2511     * @throws IllegalArgumentException if {@code destDir} exists but is not a directory
2512     * @since 1.4
2513     */
2514    public static void moveFileToDirectory(final File srcFile, final File destDir, final boolean createDestDir) throws IOException {
2515        validateMoveParameters(srcFile, destDir);
2516        if (!destDir.exists() && createDestDir) {
2517            mkdirs(destDir);
2518        }
2519        requireDirectoryExists(destDir, "destDir");
2520        moveFile(srcFile, new File(destDir, srcFile.getName()));
2521    }
2522
2523    /**
2524     * Moves a file or directory into a destination directory.
2525     * <p>
2526     * If {@code createDestDir} is true, creates all destination parent directories, including any necessary but non-existent parent directories.
2527     * </p>
2528     * <p>
2529     * When the destination is on another file system, do a "copy and delete".
2530     * </p>
2531     *
2532     * @param src           the file or directory to be moved.
2533     * @param destDir       the destination directory.
2534     * @param createDestDir if {@code true} create the destination directory. If {@code false} throw an
2535     *        IOException if the destination directory does not already exist.
2536     * @throws NullPointerException  if any of the given {@link File}s are {@code null}.
2537     * @throws FileExistsException   if the directory or file exists in the destination directory.
2538     * @throws FileNotFoundException if the source file does not exist.
2539     * @throws IOException           if source or destination is invalid.
2540     * @throws IOException           if an error occurs or setting the last-modified time didn't succeed.
2541     * @since 1.4
2542     */
2543    public static void moveToDirectory(final File src, final File destDir, final boolean createDestDir) throws IOException {
2544        validateMoveParameters(src, destDir);
2545        if (src.isDirectory()) {
2546            moveDirectoryToDirectory(src, destDir, createDestDir);
2547        } else {
2548            moveFileToDirectory(src, destDir, createDestDir);
2549        }
2550    }
2551
2552    /**
2553     * Creates a new OutputStream by opening or creating a file, returning an output stream that may be used to write bytes
2554     * to the file.
2555     *
2556     * @param append Whether or not to append.
2557     * @param file the File.
2558     * @return a new OutputStream.
2559     * @throws IOException if an I/O error occurs.
2560     * @see PathUtils#newOutputStream(Path, boolean)
2561     * @since 2.12.0
2562     */
2563    public static OutputStream newOutputStream(final File file, final boolean append) throws IOException {
2564        return PathUtils.newOutputStream(Objects.requireNonNull(file, PROTOCOL_FILE).toPath(), append);
2565    }
2566
2567    /**
2568     * Opens a {@link FileInputStream} for the specified file, providing better error messages than simply calling
2569     * {@code new FileInputStream(file)}.
2570     * <p>
2571     * At the end of the method either the stream will be successfully opened, or an exception will have been thrown.
2572     * </p>
2573     * <p>
2574     * An exception is thrown if the file does not exist. An exception is thrown if the file object exists but is a
2575     * directory. An exception is thrown if the file exists but cannot be read.
2576     * </p>
2577     *
2578     * @param file the file to open for input, must not be {@code null}
2579     * @return a new {@link FileInputStream} for the specified file
2580     * @throws NullPointerException if file is {@code null}.
2581     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2582     *         other reason cannot be opened for reading.
2583     * @throws IOException See FileNotFoundException above, FileNotFoundException is a subclass of IOException.
2584     * @since 1.3
2585     */
2586    public static FileInputStream openInputStream(final File file) throws IOException {
2587        Objects.requireNonNull(file, PROTOCOL_FILE);
2588        return new FileInputStream(file);
2589    }
2590
2591    /**
2592     * Opens a {@link FileOutputStream} for the specified file, checking and
2593     * creating the parent directory if it does not exist.
2594     * <p>
2595     * At the end of the method either the stream will be successfully opened,
2596     * or an exception will have been thrown.
2597     * </p>
2598     * <p>
2599     * The parent directory will be created if it does not exist.
2600     * The file will be created if it does not exist.
2601     * An exception is thrown if the file object exists but is a directory.
2602     * An exception is thrown if the file exists but cannot be written to.
2603     * An exception is thrown if the parent directory cannot be created.
2604     * </p>
2605     *
2606     * @param file the file to open for output, must not be {@code null}
2607     * @return a new {@link FileOutputStream} for the specified file
2608     * @throws NullPointerException if the file object is {@code null}.
2609     * @throws IllegalArgumentException if the file object is a directory
2610     * @throws IllegalArgumentException if the file is not writable.
2611     * @throws IOException if the directories could not be created.
2612     * @since 1.3
2613     */
2614    public static FileOutputStream openOutputStream(final File file) throws IOException {
2615        return openOutputStream(file, false);
2616    }
2617
2618    /**
2619     * Opens a {@link FileOutputStream} for the specified file, checking and
2620     * creating the parent directory if it does not exist.
2621     * <p>
2622     * At the end of the method either the stream will be successfully opened,
2623     * or an exception will have been thrown.
2624     * </p>
2625     * <p>
2626     * The parent directory will be created if it does not exist.
2627     * The file will be created if it does not exist.
2628     * An exception is thrown if the file object exists but is a directory.
2629     * An exception is thrown if the file exists but cannot be written to.
2630     * An exception is thrown if the parent directory cannot be created.
2631     * </p>
2632     *
2633     * @param file   the file to open for output, must not be {@code null}
2634     * @param append if {@code true}, then bytes will be added to the
2635     *               end of the file rather than overwriting
2636     * @return a new {@link FileOutputStream} for the specified file
2637     * @throws NullPointerException if the file object is {@code null}.
2638     * @throws IllegalArgumentException if the file object is a directory
2639     * @throws IOException if the directories could not be created, or the file is not writable
2640     * @since 2.1
2641     */
2642    public static FileOutputStream openOutputStream(final File file, final boolean append) throws IOException {
2643        Objects.requireNonNull(file, PROTOCOL_FILE);
2644        if (file.exists()) {
2645            checkIsFile(file, PROTOCOL_FILE);
2646        } else {
2647            createParentDirectories(file);
2648        }
2649        return new FileOutputStream(file, append);
2650    }
2651
2652    /**
2653     * Reads the contents of a file into a byte array.
2654     * The file is always closed.
2655     *
2656     * @param file the file to read, must not be {@code null}
2657     * @return the file contents, never {@code null}
2658     * @throws NullPointerException if file is {@code null}.
2659     * @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a
2660     *         regular file, or for some other reason why the file cannot be opened for reading.
2661     * @since 1.1
2662     */
2663    public static byte[] readFileToByteArray(final File file) throws IOException {
2664        Objects.requireNonNull(file, PROTOCOL_FILE);
2665        return Files.readAllBytes(file.toPath());
2666    }
2667
2668    /**
2669     * Reads the contents of a file into a String using the default encoding for the VM.
2670     * The file is always closed.
2671     *
2672     * @param file the file to read, must not be {@code null}
2673     * @return the file contents, never {@code null}
2674     * @throws NullPointerException if file is {@code null}.
2675     * @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a
2676     *         regular file, or for some other reason why the file cannot be opened for reading.
2677     * @since 1.3.1
2678     * @deprecated Use {@link #readFileToString(File, Charset)} instead (and specify the appropriate encoding)
2679     */
2680    @Deprecated
2681    public static String readFileToString(final File file) throws IOException {
2682        return readFileToString(file, Charset.defaultCharset());
2683    }
2684
2685    /**
2686     * Reads the contents of a file into a String.
2687     * The file is always closed.
2688     *
2689     * @param file     the file to read, must not be {@code null}
2690     * @param charsetName the name of the requested charset, {@code null} means platform default
2691     * @return the file contents, never {@code null}
2692     * @throws NullPointerException if file is {@code null}.
2693     * @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a
2694     *         regular file, or for some other reason why the file cannot be opened for reading.
2695     * @since 2.3
2696     */
2697    public static String readFileToString(final File file, final Charset charsetName) throws IOException {
2698        return IOUtils.toString(() -> Files.newInputStream(file.toPath()), Charsets.toCharset(charsetName));
2699    }
2700
2701    /**
2702     * Reads the contents of a file into a String. The file is always closed.
2703     *
2704     * @param file     the file to read, must not be {@code null}
2705     * @param charsetName the name of the requested charset, {@code null} means platform default
2706     * @return the file contents, never {@code null}
2707     * @throws NullPointerException if file is {@code null}.
2708     * @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a
2709     *         regular file, or for some other reason why the file cannot be opened for reading.
2710     * @throws java.nio.charset.UnsupportedCharsetException if the named charset is unavailable.
2711     * @since 2.3
2712     */
2713    public static String readFileToString(final File file, final String charsetName) throws IOException {
2714        return readFileToString(file, Charsets.toCharset(charsetName));
2715    }
2716
2717    /**
2718     * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM.
2719     * The file is always closed.
2720     *
2721     * @param file the file to read, must not be {@code null}
2722     * @return the list of Strings representing each line in the file, never {@code null}
2723     * @throws NullPointerException if file is {@code null}.
2724     * @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a
2725     *         regular file, or for some other reason why the file cannot be opened for reading.
2726     * @since 1.3
2727     * @deprecated Use {@link #readLines(File, Charset)} instead (and specify the appropriate encoding)
2728     */
2729    @Deprecated
2730    public static List<String> readLines(final File file) throws IOException {
2731        return readLines(file, Charset.defaultCharset());
2732    }
2733
2734    /**
2735     * Reads the contents of a file line by line to a List of Strings.
2736     * The file is always closed.
2737     *
2738     * @param file     the file to read, must not be {@code null}
2739     * @param charset the charset to use, {@code null} means platform default
2740     * @return the list of Strings representing each line in the file, never {@code null}
2741     * @throws NullPointerException if file is {@code null}.
2742     * @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a
2743     *         regular file, or for some other reason why the file cannot be opened for reading.
2744     * @since 2.3
2745     */
2746    public static List<String> readLines(final File file, final Charset charset) throws IOException {
2747        return Files.readAllLines(file.toPath(), charset);
2748    }
2749
2750    /**
2751     * Reads the contents of a file line by line to a List of Strings. The file is always closed.
2752     *
2753     * @param file     the file to read, must not be {@code null}
2754     * @param charsetName the name of the requested charset, {@code null} means platform default
2755     * @return the list of Strings representing each line in the file, never {@code null}
2756     * @throws NullPointerException if file is {@code null}.
2757     * @throws IOException if an I/O error occurs, including when the file does not exist, is a directory rather than a
2758     *         regular file, or for some other reason why the file cannot be opened for reading.
2759     * @throws java.nio.charset.UnsupportedCharsetException if the named charset is unavailable.
2760     * @since 1.1
2761     */
2762    public static List<String> readLines(final File file, final String charsetName) throws IOException {
2763        return readLines(file, Charsets.toCharset(charsetName));
2764    }
2765
2766    private static void requireAbsent(final File file, final String name) throws FileExistsException {
2767        if (file.exists()) {
2768            throw new FileExistsException(String.format("File element in parameter '%s' already exists: '%s'", name, file));
2769        }
2770    }
2771
2772    /**
2773     * Throws IllegalArgumentException if the given files' canonical representations are equal.
2774     *
2775     * @param file1 The first file to compare.
2776     * @param file2 The second file to compare.
2777     * @throws IOException if an I/O error occurs.
2778     * @throws IllegalArgumentException if the given files' canonical representations are equal.
2779     */
2780    private static void requireCanonicalPathsNotEquals(final File file1, final File file2) throws IOException {
2781        final String canonicalPath = file1.getCanonicalPath();
2782        if (canonicalPath.equals(file2.getCanonicalPath())) {
2783            throw new IllegalArgumentException(String
2784                .format("File canonical paths are equal: '%s' (file1='%s', file2='%s')", canonicalPath, file1, file2));
2785        }
2786    }
2787
2788    /**
2789     * Requires that the given {@link File} exists and is a directory.
2790     *
2791     * @param directory The {@link File} to check.
2792     * @param name The parameter name to use in the exception message in case of null input or if the file is not a directory.
2793     * @throws NullPointerException if the given {@link File} is {@code null}.
2794     * @throws FileNotFoundException if the given {@link File} does not exist
2795     * @throws IllegalArgumentException if the given {@link File} exists but is not a directory.
2796     */
2797    private static void requireDirectoryExists(final File directory, final String name) throws FileNotFoundException {
2798        Objects.requireNonNull(directory, name);
2799        if (!directory.isDirectory()) {
2800            if (directory.exists()) {
2801                throw new IllegalArgumentException("Parameter '" + name + "' is not a directory: '" + directory + "'");
2802            }
2803            throw new FileNotFoundException("Directory '" + directory + "' does not exist.");
2804        }
2805    }
2806
2807    /**
2808     * Requires that the given {@link File} is a directory if it exists.
2809     *
2810     * @param directory The {@link File} to check.
2811     * @param name The parameter name to use in the exception message in case of null input.
2812     * @throws NullPointerException if the given {@link File} is {@code null}.
2813     * @throws IllegalArgumentException if the given {@link File} exists but is not a directory.
2814     */
2815    private static void requireDirectoryIfExists(final File directory, final String name) {
2816        Objects.requireNonNull(directory, name);
2817        if (directory.exists() && !directory.isDirectory()) {
2818            throw new IllegalArgumentException("Parameter '" + name + "' is not a directory: '" + directory + "'");
2819        }
2820    }
2821
2822    /**
2823     * Sets file lastModifiedTime, lastAccessTime and creationTime to match source file
2824     *
2825     * @param sourceFile The source file to query.
2826     * @param targetFile The target file or directory to set.
2827     * @return {@code true} if and only if the operation succeeded;
2828     *          {@code false} otherwise
2829     * @throws NullPointerException if sourceFile is {@code null}.
2830     * @throws NullPointerException if targetFile is {@code null}.
2831     */
2832    private static boolean setTimes(final File sourceFile, final File targetFile) {
2833        Objects.requireNonNull(sourceFile, "sourceFile");
2834        Objects.requireNonNull(targetFile, "targetFile");
2835        try {
2836            // Set creation, modified, last accessed to match source file
2837            final BasicFileAttributes srcAttr = Files.readAttributes(sourceFile.toPath(), BasicFileAttributes.class);
2838            final BasicFileAttributeView destAttrView = Files.getFileAttributeView(targetFile.toPath(), BasicFileAttributeView.class);
2839            // null guards are not needed; BasicFileAttributes.setTimes(...) is null safe
2840            destAttrView.setTimes(srcAttr.lastModifiedTime(), srcAttr.lastAccessTime(), srcAttr.creationTime());
2841            return true;
2842        } catch (final IOException ignored) {
2843            // Fallback: Only set modified time to match source file
2844            return targetFile.setLastModified(sourceFile.lastModified());
2845        }
2846
2847        // TODO: (Help!) Determine historically why setLastModified(File, File) needed PathUtils.setLastModifiedTime() if
2848        //  sourceFile.isFile() was true, but needed setLastModifiedTime(File, long) if sourceFile.isFile() was false
2849    }
2850
2851    /**
2852     * Returns the size of the specified file or directory. If the provided
2853     * {@link File} is a regular file, then the file's length is returned.
2854     * If the argument is a directory, then the size of the directory is
2855     * calculated recursively. If a directory or subdirectory is security
2856     * restricted, its size will not be included.
2857     * <p>
2858     * Note that overflow is not detected, and the return value may be negative if
2859     * overflow occurs. See {@link #sizeOfAsBigInteger(File)} for an alternative
2860     * method that does not overflow.
2861     * </p>
2862     *
2863     * @param file the regular file or directory to return the size
2864     *             of (must not be {@code null}).
2865     *
2866     * @return the length of the file, or recursive size of the directory,
2867     * provided (in bytes).
2868     *
2869     * @throws NullPointerException     if the file is {@code null}.
2870     * @throws IllegalArgumentException if the file does not exist.
2871     * @throws UncheckedIOException if an IO error occurs.
2872     * @since 2.0
2873     */
2874    public static long sizeOf(final File file) {
2875        return Uncheck.get(() -> PathUtils.sizeOf(file.toPath()));
2876    }
2877
2878    /**
2879     * Returns the size of the specified file or directory. If the provided
2880     * {@link File} is a regular file, then the file's length is returned.
2881     * If the argument is a directory, then the size of the directory is
2882     * calculated recursively. If a directory or subdirectory is security
2883     * restricted, its size will not be included.
2884     *
2885     * @param file the regular file or directory to return the size
2886     *             of (must not be {@code null}).
2887     *
2888     * @return the length of the file, or recursive size of the directory,
2889     * provided (in bytes).
2890     *
2891     * @throws NullPointerException     if the file is {@code null}.
2892     * @throws IllegalArgumentException if the file does not exist.
2893     * @throws UncheckedIOException if an IO error occurs.
2894     * @since 2.4
2895     */
2896    public static BigInteger sizeOfAsBigInteger(final File file) {
2897        return Uncheck.get(() -> PathUtils.sizeOfAsBigInteger(file.toPath()));
2898    }
2899
2900    /**
2901     * Counts the size of a directory recursively (sum of the length of all files).
2902     * <p>
2903     * Note that overflow is not detected, and the return value may be negative if
2904     * overflow occurs. See {@link #sizeOfDirectoryAsBigInteger(File)} for an alternative
2905     * method that does not overflow.
2906     * </p>
2907     *
2908     * @param directory directory to inspect, must not be {@code null}.
2909     * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total
2910     * is greater than {@link Long#MAX_VALUE}.
2911     * @throws IllegalArgumentException if the given {@link File} exists but is not a directory
2912     * @throws NullPointerException if the directory is {@code null}.
2913     * @throws UncheckedIOException if an IO error occurs.
2914     */
2915    public static long sizeOfDirectory(final File directory) {
2916        try {
2917            requireDirectoryExists(directory, "directory");
2918        } catch (final FileNotFoundException e) {
2919            throw new UncheckedIOException(e);
2920        }
2921        return Uncheck.get(() -> PathUtils.sizeOfDirectory(directory.toPath()));
2922    }
2923
2924    /**
2925     * Counts the size of a directory recursively (sum of the length of all files).
2926     *
2927     * @param directory directory to inspect, must not be {@code null}.
2928     * @return size of directory in bytes, 0 if directory is security restricted.
2929     * @throws IllegalArgumentException if the given {@link File} exists but is not a directory
2930     * @throws NullPointerException if the directory is {@code null}.
2931     * @throws UncheckedIOException if an IO error occurs.
2932     * @since 2.4
2933     */
2934    public static BigInteger sizeOfDirectoryAsBigInteger(final File directory) {
2935        try {
2936            requireDirectoryExists(directory, "directory");
2937        } catch (final FileNotFoundException e) {
2938            throw new UncheckedIOException(e);
2939        }
2940        return Uncheck.get(() -> PathUtils.sizeOfDirectoryAsBigInteger(directory.toPath()));
2941    }
2942
2943    /**
2944     * Streams over the files in a given directory (and optionally its subdirectories) which match an array of extensions.
2945     * <p>
2946     * The returned {@link Stream} may wrap one or more {@link DirectoryStream}s. When you require timely disposal of file system resources, use a
2947     * {@code try}-with-resources block to ensure invocation of the stream's {@link Stream#close()} method after the stream operations are completed. Calling a
2948     * closed stream causes a {@link IllegalStateException}.
2949     * </p>
2950     *
2951     * @param directory  the directory to search in
2952     * @param recursive  if true all subdirectories are searched as well
2953     * @param extensions an array of extensions, for example, {"java","xml"}. If this parameter is {@code null}, all files are returned.
2954     * @return a Stream of {@link File} for matching files.
2955     * @throws IOException if an I/O error is thrown when accessing the starting file.
2956     * @since 2.9.0
2957     */
2958    public static Stream<File> streamFiles(final File directory, final boolean recursive, final String... extensions) throws IOException {
2959        // @formatter:off
2960        final IOFileFilter filter = extensions == null
2961            ? FileFileFilter.INSTANCE
2962            : FileFileFilter.INSTANCE.and(new SuffixFileFilter(toSuffixes(extensions)));
2963        // @formatter:on
2964        return PathUtils.walk(directory.toPath(), filter, toMaxDepth(recursive), false, FileVisitOption.FOLLOW_LINKS).map(Path::toFile);
2965    }
2966
2967    /**
2968     * Converts from a {@link URL} to a {@link File}.
2969     * <p>
2970     * Syntax such as {@code file:///my%20docs/file.txt} will be
2971     * correctly decoded to {@code /my docs/file.txt}.
2972     * UTF-8 is used to decode percent-encoded octets to characters.
2973     * Additionally, malformed percent-encoded octets are handled leniently by
2974     * passing them through literally.
2975     * </p>
2976     *
2977     * @param url the file URL to convert, {@code null} returns {@code null}
2978     * @return the equivalent {@link File} object, or {@code null}
2979     * if the URL's protocol is not {@code file}
2980     */
2981    public static File toFile(final URL url) {
2982        if (url == null || !isFileProtocol(url)) {
2983            return null;
2984        }
2985        final String fileName = url.getFile().replace('/', File.separatorChar);
2986        return new File(decodeUrl(fileName));
2987    }
2988
2989    /**
2990     * Converts each of an array of {@link URL} to a {@link File}.
2991     * <p>
2992     * Returns an array of the same size as the input.
2993     * If the input is {@code null}, an empty array is returned.
2994     * If the input contains {@code null}, the output array contains {@code null} at the same
2995     * index.
2996     * </p>
2997     * <p>
2998     * This method will decode the URL.
2999     * Syntax such as {@code file:///my%20docs/file.txt} will be
3000     * correctly decoded to {@code /my docs/file.txt}.
3001     * </p>
3002     *
3003     * @param urls the file URLs to convert, {@code null} returns empty array
3004     * @return a non-{@code null} array of Files matching the input, with a {@code null} item
3005     * if there was a {@code null} at that index in the input array
3006     * @throws IllegalArgumentException if any file is not a URL file
3007     * @throws IllegalArgumentException if any file is incorrectly encoded
3008     * @since 1.1
3009     */
3010    public static File[] toFiles(final URL... urls) {
3011        if (IOUtils.length(urls) == 0) {
3012            return EMPTY_FILE_ARRAY;
3013        }
3014        final File[] files = new File[urls.length];
3015        for (int i = 0; i < urls.length; i++) {
3016            final URL url = urls[i];
3017            if (url != null) {
3018                if (!isFileProtocol(url)) {
3019                    throw new IllegalArgumentException("Can only convert file URL to a File: " + url);
3020                }
3021                files[i] = toFile(url);
3022            }
3023        }
3024        return files;
3025    }
3026
3027    /**
3028     * Consumes all of the given stream.
3029     * <p>
3030     * When called from a FileTreeWalker, the walker <em>closes</em> the stream because {@link FileTreeWalker#next()} calls {@code top.stream().close()}.
3031     * </p>
3032     *
3033     * @param stream The stream to consume.
3034     * @return a new List.
3035     */
3036    private static List<File> toList(final Stream<File> stream) {
3037        return stream.collect(Collectors.toList());
3038    }
3039
3040    /**
3041     * Converts whether or not to recurse into a recursion max depth.
3042     *
3043     * @param recursive whether or not to recurse
3044     * @return the recursion depth
3045     */
3046    private static int toMaxDepth(final boolean recursive) {
3047        return recursive ? Integer.MAX_VALUE : 1;
3048    }
3049
3050    /**
3051     * Converts an array of file extensions to suffixes.
3052     *
3053     * @param extensions an array of extensions. Format: {"java", "xml"}
3054     * @return an array of suffixes. Format: {".java", ".xml"}
3055     * @throws NullPointerException if the parameter is null
3056     */
3057    private static String[] toSuffixes(final String... extensions) {
3058        return Stream.of(Objects.requireNonNull(extensions, "extensions")).map(e -> "." + e).toArray(String[]::new);
3059    }
3060
3061    /**
3062     * Implements behavior similar to the UNIX "touch" utility. Creates a new file with size 0, or, if the file exists, just
3063     * updates the file's modified time. This method throws an IOException if the last modified date
3064     * of the file cannot be set. It creates parent directories if they do not exist.
3065     *
3066     * @param file the File to touch.
3067     * @throws NullPointerException if the parameter is {@code null}.
3068     * @throws IOException if setting the last-modified time failed or an I/O problem occurs.
3069     */
3070    public static void touch(final File file) throws IOException {
3071        PathUtils.touch(Objects.requireNonNull(file, PROTOCOL_FILE).toPath());
3072    }
3073
3074    /**
3075     * Converts each element of an array of {@link File} to a {@link URL}.
3076     * <p>
3077     * Returns an array of the same size as the input.
3078     * </p>
3079     *
3080     * @param files the files to convert, must not be {@code null}
3081     * @return an array of URLs matching the input
3082     * @throws IOException          if a file cannot be converted
3083     * @throws NullPointerException if any argument is null
3084     */
3085    public static URL[] toURLs(final File... files) throws IOException {
3086        Objects.requireNonNull(files, "files");
3087        final URL[] urls = new URL[files.length];
3088        for (int i = 0; i < urls.length; i++) {
3089            urls[i] = files[i].toURI().toURL();
3090        }
3091        return urls;
3092    }
3093
3094    /**
3095     * Validates the given arguments.
3096     * <ul>
3097     * <li>Throws {@link NullPointerException} if {@code source} is null</li>
3098     * <li>Throws {@link NullPointerException} if {@code destination} is null</li>
3099     * <li>Throws {@link FileNotFoundException} if {@code source} does not exist</li>
3100     * </ul>
3101     *
3102     * @param source      the file or directory to be moved.
3103     * @param destination the destination file or directory.
3104     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
3105     * @throws FileNotFoundException if the source file does not exist.
3106     */
3107    private static void validateMoveParameters(final File source, final File destination) throws FileNotFoundException {
3108        Objects.requireNonNull(source, "source");
3109        Objects.requireNonNull(destination, "destination");
3110        if (!source.exists()) {
3111            throw new FileNotFoundException("Source '" + source + "' does not exist");
3112        }
3113    }
3114
3115    /**
3116     * Waits for the file system to detect a file's presence, with a timeout.
3117     * <p>
3118     * This method repeatedly tests {@link Files#exists(Path, LinkOption...)} until it returns
3119     * true up to the maximum time specified in seconds.
3120     * </p>
3121     *
3122     * @param file    the file to check, must not be {@code null}
3123     * @param seconds the maximum time in seconds to wait
3124     * @return true if file exists
3125     * @throws NullPointerException if the file is {@code null}
3126     */
3127    public static boolean waitFor(final File file, final int seconds) {
3128        Objects.requireNonNull(file, PROTOCOL_FILE);
3129        return PathUtils.waitFor(file.toPath(), Duration.ofSeconds(seconds), PathUtils.EMPTY_LINK_OPTION_ARRAY);
3130    }
3131
3132    /**
3133     * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM.
3134     *
3135     * @param file the file to write
3136     * @param data the content to write to the file
3137     * @throws IOException in case of an I/O error
3138     * @since 2.0
3139     * @deprecated Use {@link #write(File, CharSequence, Charset)} instead (and specify the appropriate encoding)
3140     */
3141    @Deprecated
3142    public static void write(final File file, final CharSequence data) throws IOException {
3143        write(file, data, Charset.defaultCharset(), false);
3144    }
3145
3146    /**
3147     * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM.
3148     *
3149     * @param file   the file to write
3150     * @param data   the content to write to the file
3151     * @param append if {@code true}, then the data will be added to the
3152     *               end of the file rather than overwriting
3153     * @throws IOException in case of an I/O error
3154     * @since 2.1
3155     * @deprecated Use {@link #write(File, CharSequence, Charset, boolean)} instead (and specify the appropriate encoding)
3156     */
3157    @Deprecated
3158    public static void write(final File file, final CharSequence data, final boolean append) throws IOException {
3159        write(file, data, Charset.defaultCharset(), append);
3160    }
3161
3162    /**
3163     * Writes a CharSequence to a file creating the file if it does not exist.
3164     *
3165     * @param file     the file to write
3166     * @param data     the content to write to the file
3167     * @param charset the name of the requested charset, {@code null} means platform default
3168     * @throws IOException in case of an I/O error
3169     * @since 2.3
3170     */
3171    public static void write(final File file, final CharSequence data, final Charset charset) throws IOException {
3172        write(file, data, charset, false);
3173    }
3174
3175    /**
3176     * Writes a CharSequence to a file creating the file if it does not exist.
3177     *
3178     * @param file     the file to write
3179     * @param data     the content to write to the file
3180     * @param charset the charset to use, {@code null} means platform default
3181     * @param append   if {@code true}, then the data will be added to the
3182     *                 end of the file rather than overwriting
3183     * @throws IOException in case of an I/O error
3184     * @since 2.3
3185     */
3186    public static void write(final File file, final CharSequence data, final Charset charset, final boolean append) throws IOException {
3187        writeStringToFile(file, Objects.toString(data, null), charset, append);
3188    }
3189
3190    /**
3191     * Writes a CharSequence to a file creating the file if it does not exist.
3192     *
3193     * @param file     the file to write
3194     * @param data     the content to write to the file
3195     * @param charsetName the name of the requested charset, {@code null} means platform default
3196     * @throws IOException                          in case of an I/O error
3197     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3198     * @since 2.0
3199     */
3200    public static void write(final File file, final CharSequence data, final String charsetName) throws IOException {
3201        write(file, data, charsetName, false);
3202    }
3203
3204    /**
3205     * Writes a CharSequence to a file creating the file if it does not exist.
3206     *
3207     * @param file     the file to write
3208     * @param data     the content to write to the file
3209     * @param charsetName the name of the requested charset, {@code null} means platform default
3210     * @param append   if {@code true}, then the data will be added to the
3211     *                 end of the file rather than overwriting
3212     * @throws IOException                 in case of an I/O error
3213     * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported by the VM
3214     * @since 2.1
3215     */
3216    public static void write(final File file, final CharSequence data, final String charsetName, final boolean append) throws IOException {
3217        write(file, data, Charsets.toCharset(charsetName), append);
3218    }
3219
3220    // Must be called with a directory
3221
3222    /**
3223     * Writes a byte array to a file creating the file if it does not exist.
3224     * The parent directories of the file will be created if they do not exist.
3225     *
3226     * @param file the file to write to
3227     * @param data the content to write to the file
3228     * @throws IOException in case of an I/O error
3229     * @since 1.1
3230     */
3231    public static void writeByteArrayToFile(final File file, final byte[] data) throws IOException {
3232        writeByteArrayToFile(file, data, false);
3233    }
3234
3235    /**
3236     * Writes a byte array to a file creating the file if it does not exist.
3237     *
3238     * @param file   the file to write to
3239     * @param data   the content to write to the file
3240     * @param append if {@code true}, then bytes will be added to the
3241     *               end of the file rather than overwriting
3242     * @throws IOException in case of an I/O error
3243     * @since 2.1
3244     */
3245    public static void writeByteArrayToFile(final File file, final byte[] data, final boolean append) throws IOException {
3246        writeByteArrayToFile(file, data, 0, data.length, append);
3247    }
3248
3249    /**
3250     * Writes {@code len} bytes from the specified byte array starting
3251     * at offset {@code off} to a file, creating the file if it does
3252     * not exist.
3253     *
3254     * @param file the file to write to
3255     * @param data the content to write to the file
3256     * @param off  the start offset in the data
3257     * @param len  the number of bytes to write
3258     * @throws IOException in case of an I/O error
3259     * @since 2.5
3260     */
3261    public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len) throws IOException {
3262        writeByteArrayToFile(file, data, off, len, false);
3263    }
3264
3265    /**
3266     * Writes {@code len} bytes from the specified byte array starting
3267     * at offset {@code off} to a file, creating the file if it does
3268     * not exist.
3269     *
3270     * @param file   the file to write to
3271     * @param data   the content to write to the file
3272     * @param off    the start offset in the data
3273     * @param len    the number of bytes to write
3274     * @param append if {@code true}, then bytes will be added to the
3275     *               end of the file rather than overwriting
3276     * @throws IOException in case of an I/O error
3277     * @since 2.5
3278     */
3279    public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len, final boolean append) throws IOException {
3280        try (OutputStream out = newOutputStream(file, append)) {
3281            out.write(data, off, len);
3282        }
3283    }
3284
3285    /**
3286     * Writes the {@code toString()} value of each item in a collection to
3287     * the specified {@link File} line by line.
3288     * The default VM encoding and the default line ending will be used.
3289     *
3290     * @param file  the file to write to
3291     * @param lines the lines to write, {@code null} entries produce blank lines
3292     * @throws IOException in case of an I/O error
3293     * @since 1.3
3294     */
3295    public static void writeLines(final File file, final Collection<?> lines) throws IOException {
3296        writeLines(file, null, lines, null, false);
3297    }
3298
3299    /**
3300     * Writes the {@code toString()} value of each item in a collection to
3301     * the specified {@link File} line by line.
3302     * The default VM encoding and the default line ending will be used.
3303     *
3304     * @param file   the file to write to
3305     * @param lines  the lines to write, {@code null} entries produce blank lines
3306     * @param append if {@code true}, then the lines will be added to the
3307     *               end of the file rather than overwriting
3308     * @throws IOException in case of an I/O error
3309     * @since 2.1
3310     */
3311    public static void writeLines(final File file, final Collection<?> lines, final boolean append) throws IOException {
3312        writeLines(file, null, lines, null, append);
3313    }
3314
3315    /**
3316     * Writes the {@code toString()} value of each item in a collection to
3317     * the specified {@link File} line by line.
3318     * The default VM encoding and the specified line ending will be used.
3319     *
3320     * @param file       the file to write to
3321     * @param lines      the lines to write, {@code null} entries produce blank lines
3322     * @param lineEnding the line separator to use, {@code null} is system default
3323     * @throws IOException in case of an I/O error
3324     * @since 1.3
3325     */
3326    public static void writeLines(final File file, final Collection<?> lines, final String lineEnding) throws IOException {
3327        writeLines(file, null, lines, lineEnding, false);
3328    }
3329
3330    /**
3331     * Writes the {@code toString()} value of each item in a collection to
3332     * the specified {@link File} line by line.
3333     * The default VM encoding and the specified line ending will be used.
3334     *
3335     * @param file       the file to write to
3336     * @param lines      the lines to write, {@code null} entries produce blank lines
3337     * @param lineEnding the line separator to use, {@code null} is system default
3338     * @param append     if {@code true}, then the lines will be added to the
3339     *                   end of the file rather than overwriting
3340     * @throws IOException in case of an I/O error
3341     * @since 2.1
3342     */
3343    public static void writeLines(final File file, final Collection<?> lines, final String lineEnding, final boolean append) throws IOException {
3344        writeLines(file, null, lines, lineEnding, append);
3345    }
3346
3347    /**
3348     * Writes the {@code toString()} value of each item in a collection to
3349     * the specified {@link File} line by line.
3350     * The specified character encoding and the default line ending will be used.
3351     * The parent directories of the file will be created if they do not exist.
3352     *
3353     * @param file     the file to write to
3354     * @param charsetName the name of the requested charset, {@code null} means platform default
3355     * @param lines    the lines to write, {@code null} entries produce blank lines
3356     * @throws IOException                          in case of an I/O error
3357     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3358     * @since 1.1
3359     */
3360    public static void writeLines(final File file, final String charsetName, final Collection<?> lines) throws IOException {
3361        writeLines(file, charsetName, lines, null, false);
3362    }
3363
3364    /**
3365     * Writes the {@code toString()} value of each item in a collection to
3366     * the specified {@link File} line by line, optionally appending.
3367     * The specified character encoding and the default line ending will be used.
3368     *
3369     * @param file     the file to write to
3370     * @param charsetName the name of the requested charset, {@code null} means platform default
3371     * @param lines    the lines to write, {@code null} entries produce blank lines
3372     * @param append   if {@code true}, then the lines will be added to the
3373     *                 end of the file rather than overwriting
3374     * @throws IOException                          in case of an I/O error
3375     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3376     * @since 2.1
3377     */
3378    public static void writeLines(final File file, final String charsetName, final Collection<?> lines, final boolean append) throws IOException {
3379        writeLines(file, charsetName, lines, null, append);
3380    }
3381
3382    /**
3383     * Writes the {@code toString()} value of each item in a collection to
3384     * the specified {@link File} line by line.
3385     * The specified character encoding and the line ending will be used.
3386     * The parent directories of the file will be created if they do not exist.
3387     *
3388     * @param file       the file to write to
3389     * @param charsetName   the name of the requested charset, {@code null} means platform default
3390     * @param lines      the lines to write, {@code null} entries produce blank lines
3391     * @param lineEnding the line separator to use, {@code null} is system default
3392     * @throws IOException                          in case of an I/O error
3393     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3394     * @since 1.1
3395     */
3396    public static void writeLines(final File file, final String charsetName, final Collection<?> lines, final String lineEnding) throws IOException {
3397        writeLines(file, charsetName, lines, lineEnding, false);
3398    }
3399
3400    /**
3401     * Writes the {@code toString()} value of each item in a collection to
3402     * the specified {@link File} line by line.
3403     * The specified character encoding and the line ending will be used.
3404     *
3405     * @param file       the file to write to
3406     * @param charsetName   the name of the requested charset, {@code null} means platform default
3407     * @param lines      the lines to write, {@code null} entries produce blank lines
3408     * @param lineEnding the line separator to use, {@code null} is system default
3409     * @param append     if {@code true}, then the lines will be added to the
3410     *                   end of the file rather than overwriting
3411     * @throws IOException                          in case of an I/O error
3412     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3413     * @since 2.1
3414     */
3415    public static void writeLines(final File file, final String charsetName, final Collection<?> lines, final String lineEnding, final boolean append)
3416        throws IOException {
3417        try (OutputStream out = new BufferedOutputStream(newOutputStream(file, append))) {
3418            IOUtils.writeLines(lines, lineEnding, out, charsetName);
3419        }
3420    }
3421
3422    /**
3423     * Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
3424     *
3425     * @param file the file to write
3426     * @param data the content to write to the file
3427     * @throws IOException in case of an I/O error
3428     * @deprecated Use {@link #writeStringToFile(File, String, Charset)} instead (and specify the appropriate encoding)
3429     */
3430    @Deprecated
3431    public static void writeStringToFile(final File file, final String data) throws IOException {
3432        writeStringToFile(file, data, Charset.defaultCharset(), false);
3433    }
3434
3435    /**
3436     * Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
3437     *
3438     * @param file   the file to write
3439     * @param data   the content to write to the file
3440     * @param append if {@code true}, then the String will be added to the
3441     *               end of the file rather than overwriting
3442     * @throws IOException in case of an I/O error
3443     * @since 2.1
3444     * @deprecated Use {@link #writeStringToFile(File, String, Charset, boolean)} instead (and specify the appropriate encoding)
3445     */
3446    @Deprecated
3447    public static void writeStringToFile(final File file, final String data, final boolean append) throws IOException {
3448        writeStringToFile(file, data, Charset.defaultCharset(), append);
3449    }
3450
3451    /**
3452     * Writes a String to a file creating the file if it does not exist.
3453     * The parent directories of the file will be created if they do not exist.
3454     *
3455     * @param file     the file to write
3456     * @param data     the content to write to the file
3457     * @param charset the charset to use, {@code null} means platform default
3458     * @throws IOException                          in case of an I/O error
3459     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3460     * @since 2.4
3461     */
3462    public static void writeStringToFile(final File file, final String data, final Charset charset) throws IOException {
3463        writeStringToFile(file, data, charset, false);
3464    }
3465
3466    /**
3467     * Writes a String to a file, creating the file if it does not exist.
3468     * The parent directories of the file are created if they do not exist.
3469     *
3470     * @param file     the file to write
3471     * @param data     the content to write to the file
3472     * @param charset the charset to use, {@code null} means platform default
3473     * @param append   if {@code true}, then the String will be added to the
3474     *                 end of the file rather than overwriting
3475     * @throws IOException in case of an I/O error
3476     * @since 2.3
3477     */
3478    public static void writeStringToFile(final File file, final String data, final Charset charset, final boolean append) throws IOException {
3479        try (OutputStream out = newOutputStream(file, append)) {
3480            IOUtils.write(data, out, charset);
3481        }
3482    }
3483
3484    /**
3485     * Writes a String to a file, creating the file if it does not exist.
3486     * The parent directories of the file are created if they do not exist.
3487     *
3488     * @param file     the file to write
3489     * @param data     the content to write to the file
3490     * @param charsetName the name of the requested charset, {@code null} means platform default
3491     * @throws IOException                          in case of an I/O error
3492     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3493     */
3494    public static void writeStringToFile(final File file, final String data, final String charsetName) throws IOException {
3495        writeStringToFile(file, data, charsetName, false);
3496    }
3497
3498    /**
3499     * Writes a String to a file, creating the file if it does not exist.
3500     * The parent directories of the file are created if they do not exist.
3501     *
3502     * @param file     the file to write
3503     * @param data     the content to write to the file
3504     * @param charsetName the name of the requested charset, {@code null} means platform default
3505     * @param append   if {@code true}, then the String will be added to the
3506     *                 end of the file rather than overwriting
3507     * @throws IOException                 in case of an I/O error
3508     * @throws java.nio.charset.UnsupportedCharsetException if the encoding is not supported by the VM
3509     * @since 2.1
3510     */
3511    public static void writeStringToFile(final File file, final String data, final String charsetName, final boolean append) throws IOException {
3512        writeStringToFile(file, data, Charsets.toCharset(charsetName), append);
3513    }
3514
3515    /**
3516     * Instances should NOT be constructed in standard programming.
3517     *
3518     * @deprecated TODO Make private in 3.0.
3519     */
3520    @Deprecated
3521    public FileUtils() { //NOSONAR
3522        // empty
3523    }
3524
3525}