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