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.FileVisitOption;
040import java.nio.file.FileVisitResult;
041import java.nio.file.Files;
042import java.nio.file.LinkOption;
043import java.nio.file.NotDirectoryException;
044import java.nio.file.Path;
045import java.nio.file.StandardCopyOption;
046import java.nio.file.attribute.BasicFileAttributeView;
047import java.nio.file.attribute.BasicFileAttributes;
048import java.nio.file.attribute.FileTime;
049import java.time.Duration;
050import java.time.Instant;
051import java.time.LocalTime;
052import java.time.OffsetDateTime;
053import java.time.OffsetTime;
054import java.time.ZoneId;
055import java.time.chrono.ChronoLocalDate;
056import java.time.chrono.ChronoLocalDateTime;
057import java.time.chrono.ChronoZonedDateTime;
058import java.util.ArrayList;
059import java.util.Collection;
060import java.util.Collections;
061import java.util.Date;
062import java.util.HashSet;
063import java.util.Iterator;
064import java.util.List;
065import java.util.Objects;
066import java.util.Set;
067import java.util.stream.Collectors;
068import java.util.stream.Stream;
069import java.util.zip.CRC32;
070import java.util.zip.CheckedInputStream;
071import java.util.zip.Checksum;
072
073import org.apache.commons.io.file.AccumulatorPathVisitor;
074import org.apache.commons.io.file.Counters;
075import org.apache.commons.io.file.PathFilter;
076import org.apache.commons.io.file.PathUtils;
077import org.apache.commons.io.file.StandardDeleteOption;
078import org.apache.commons.io.filefilter.FileEqualsFileFilter;
079import org.apache.commons.io.filefilter.FileFileFilter;
080import org.apache.commons.io.filefilter.IOFileFilter;
081import org.apache.commons.io.filefilter.SuffixFileFilter;
082import org.apache.commons.io.filefilter.TrueFileFilter;
083import org.apache.commons.io.function.IOConsumer;
084import org.apache.commons.io.function.Uncheck;
085
086/**
087 * General file manipulation utilities.
088 * <p>
089 * Facilities are provided in the following areas:
090 * </p>
091 * <ul>
092 * <li>writing to a file
093 * <li>reading from a file
094 * <li>make a directory including parent directories
095 * <li>copying files and directories
096 * <li>deleting files and directories
097 * <li>converting to and from a URL
098 * <li>listing files and directories by filter and extension
099 * <li>comparing file content
100 * <li>file last changed date
101 * <li>calculating a checksum
102 * </ul>
103 * <p>
104 * Note that a specific charset should be specified whenever possible. Relying on the platform default means that the
105 * code is Locale-dependent. Only use the default if the files are known to always use the platform default.
106 * </p>
107 * <p>
108 * {@link SecurityException} are not documented in the Javadoc.
109 * </p>
110 * <p>
111 * Origin of code: Excalibur, Alexandria, Commons-Utils
112 * </p>
113 */
114public class FileUtils {
115
116    /**
117     * The number of bytes in a kilobyte.
118     */
119    public static final long ONE_KB = 1024;
120
121    /**
122     * The number of bytes in a kilobyte.
123     *
124     * @since 2.4
125     */
126    public static final BigInteger ONE_KB_BI = BigInteger.valueOf(ONE_KB);
127
128    /**
129     * The number of bytes in a megabyte.
130     */
131    public static final long ONE_MB = ONE_KB * ONE_KB;
132
133    /**
134     * The number of bytes in a megabyte.
135     *
136     * @since 2.4
137     */
138    public static final BigInteger ONE_MB_BI = ONE_KB_BI.multiply(ONE_KB_BI);
139
140    /**
141     * The number of bytes in a gigabyte.
142     */
143    public static final long ONE_GB = ONE_KB * ONE_MB;
144
145    /**
146     * The number of bytes in a gigabyte.
147     *
148     * @since 2.4
149     */
150    public static final BigInteger ONE_GB_BI = ONE_KB_BI.multiply(ONE_MB_BI);
151
152    /**
153     * The number of bytes in a terabyte.
154     */
155    public static final long ONE_TB = ONE_KB * ONE_GB;
156
157    /**
158     * The number of bytes in a terabyte.
159     *
160     * @since 2.4
161     */
162    public static final BigInteger ONE_TB_BI = ONE_KB_BI.multiply(ONE_GB_BI);
163
164    /**
165     * The number of bytes in a petabyte.
166     */
167    public static final long ONE_PB = ONE_KB * ONE_TB;
168
169    /**
170     * The number of bytes in a petabyte.
171     *
172     * @since 2.4
173     */
174    public static final BigInteger ONE_PB_BI = ONE_KB_BI.multiply(ONE_TB_BI);
175
176    /**
177     * The number of bytes in an exabyte.
178     */
179    public static final long ONE_EB = ONE_KB * ONE_PB;
180
181    /**
182     * The number of bytes in an exabyte.
183     *
184     * @since 2.4
185     */
186    public static final BigInteger ONE_EB_BI = ONE_KB_BI.multiply(ONE_PB_BI);
187
188    /**
189     * The number of bytes in a zettabyte.
190     */
191    public static final BigInteger ONE_ZB = BigInteger.valueOf(ONE_KB).multiply(BigInteger.valueOf(ONE_EB));
192
193    /**
194     * The number of bytes in a yottabyte.
195     */
196    public static final BigInteger ONE_YB = ONE_KB_BI.multiply(ONE_ZB);
197
198    /**
199     * An empty array of type {@link File}.
200     */
201    public static final File[] EMPTY_FILE_ARRAY = {};
202
203    /**
204     * Returns a human-readable version of the file size, where the input represents a specific number of bytes.
205     * <p>
206     * 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
207     * nearest GB boundary.
208     * </p>
209     * <p>
210     * Similarly for the 1MB and 1KB boundaries.
211     * </p>
212     *
213     * @param size the number of bytes
214     * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes)
215     * @throws NullPointerException if the given {@link BigInteger} is {@code null}.
216     * @see <a href="https://issues.apache.org/jira/browse/IO-226">IO-226 - should the rounding be changed?</a>
217     * @since 2.4
218     */
219    // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed?
220    public static String byteCountToDisplaySize(final BigInteger size) {
221        Objects.requireNonNull(size, "size");
222        final String displaySize;
223
224        if (size.divide(ONE_EB_BI).compareTo(BigInteger.ZERO) > 0) {
225            displaySize = size.divide(ONE_EB_BI) + " EB";
226        } else if (size.divide(ONE_PB_BI).compareTo(BigInteger.ZERO) > 0) {
227            displaySize = size.divide(ONE_PB_BI) + " PB";
228        } else if (size.divide(ONE_TB_BI).compareTo(BigInteger.ZERO) > 0) {
229            displaySize = size.divide(ONE_TB_BI) + " TB";
230        } else if (size.divide(ONE_GB_BI).compareTo(BigInteger.ZERO) > 0) {
231            displaySize = size.divide(ONE_GB_BI) + " GB";
232        } else if (size.divide(ONE_MB_BI).compareTo(BigInteger.ZERO) > 0) {
233            displaySize = size.divide(ONE_MB_BI) + " MB";
234        } else if (size.divide(ONE_KB_BI).compareTo(BigInteger.ZERO) > 0) {
235            displaySize = size.divide(ONE_KB_BI) + " KB";
236        } else {
237            displaySize = size + " bytes";
238        }
239        return displaySize;
240    }
241
242    /**
243     * Returns a human-readable version of the file size, where the input represents a specific number of bytes.
244     * <p>
245     * 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
246     * nearest GB boundary.
247     * </p>
248     * <p>
249     * Similarly for the 1MB and 1KB boundaries.
250     * </p>
251     *
252     * @param size the number of bytes
253     * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes)
254     * @see <a href="https://issues.apache.org/jira/browse/IO-226">IO-226 - should the rounding be changed?</a>
255     */
256    // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed?
257    public static String byteCountToDisplaySize(final long size) {
258        return byteCountToDisplaySize(BigInteger.valueOf(size));
259    }
260
261    /**
262     * Returns a human-readable version of the file size, where the input represents a specific number of bytes.
263     * <p>
264     * 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
265     * nearest GB boundary.
266     * </p>
267     * <p>
268     * Similarly for the 1MB and 1KB boundaries.
269     * </p>
270     *
271     * @param size the number of bytes
272     * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes)
273     * @see <a href="https://issues.apache.org/jira/browse/IO-226">IO-226 - should the rounding be changed?</a>
274     * @since 2.12.0
275     */
276    // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed?
277    public static String byteCountToDisplaySize(final Number size) {
278        return byteCountToDisplaySize(size.longValue());
279    }
280
281    /**
282     * Computes the checksum of a file using the specified checksum object. Multiple files may be checked using one
283     * {@link Checksum} instance if desired simply by reusing the same checksum object. For example:
284     *
285     * <pre>
286     * long checksum = FileUtils.checksum(file, new CRC32()).getValue();
287     * </pre>
288     *
289     * @param file the file to checksum, must not be {@code null}
290     * @param checksum the checksum object to be used, must not be {@code null}
291     * @return the checksum specified, updated with the content of the file
292     * @throws NullPointerException if the given {@link File} is {@code null}.
293     * @throws NullPointerException if the given {@link Checksum} is {@code null}.
294     * @throws IllegalArgumentException if the given {@link File} does not exist or is not a file.
295     * @throws IOException if an IO error occurs reading the file.
296     * @since 1.3
297     */
298    public static Checksum checksum(final File file, final Checksum checksum) throws IOException {
299        requireExistsChecked(file, "file");
300        requireFile(file, "file");
301        Objects.requireNonNull(checksum, "checksum");
302        try (InputStream inputStream = new CheckedInputStream(Files.newInputStream(file.toPath()), checksum)) {
303            IOUtils.consume(inputStream);
304        }
305        return checksum;
306    }
307
308    /**
309     * Computes the checksum of a file using the CRC32 checksum routine.
310     * The value of the checksum is returned.
311     *
312     * @param file the file to checksum, must not be {@code null}
313     * @return the checksum value
314     * @throws NullPointerException if the given {@link File} is {@code null}.
315     * @throws IllegalArgumentException if the given {@link File} does not exist or is not a file.
316     * @throws IOException              if an IO error occurs reading the file.
317     * @since 1.3
318     */
319    public static long checksumCRC32(final File file) throws IOException {
320        return checksum(file, new CRC32()).getValue();
321    }
322
323    /**
324     * Cleans a directory without deleting it.
325     *
326     * @param directory directory to clean
327     * @throws NullPointerException if the given {@link File} is {@code null}.
328     * @throws IllegalArgumentException if directory does not exist or is not a directory.
329     * @throws IOException if an I/O error occurs.
330     * @see #forceDelete(File)
331     */
332    public static void cleanDirectory(final File directory) throws IOException {
333        IOConsumer.forAll(FileUtils::forceDelete, listFiles(directory, null));
334    }
335
336    /**
337     * Cleans a directory without deleting it.
338     *
339     * @param directory directory to clean, must not be {@code null}
340     * @throws NullPointerException if the given {@link File} is {@code null}.
341     * @throws IllegalArgumentException if directory does not exist or is not a directory.
342     * @throws IOException if an I/O error occurs.
343     * @see #forceDeleteOnExit(File)
344     */
345    private static void cleanDirectoryOnExit(final File directory) throws IOException {
346        IOConsumer.forAll(FileUtils::forceDeleteOnExit, listFiles(directory, null));
347    }
348
349    /**
350     * Tests whether the contents of two files are equal.
351     * <p>
352     * This method checks to see if the two files are different lengths or if they point to the same file, before
353     * resorting to byte-by-byte comparison of the contents.
354     * </p>
355     * <p>
356     * Code origin: Avalon
357     * </p>
358     *
359     * @param file1 the first file
360     * @param file2 the second file
361     * @return true if the content of the files are equal or they both don't exist, false otherwise
362     * @throws IllegalArgumentException when an input is not a file.
363     * @throws IOException If an I/O error occurs.
364     * @see org.apache.commons.io.file.PathUtils#fileContentEquals(Path,Path,java.nio.file.LinkOption[],java.nio.file.OpenOption...)
365     */
366    public static boolean contentEquals(final File file1, final File file2) throws IOException {
367        if (file1 == null && file2 == null) {
368            return true;
369        }
370        if (file1 == null || file2 == null) {
371            return false;
372        }
373        final boolean file1Exists = file1.exists();
374        if (file1Exists != file2.exists()) {
375            return false;
376        }
377
378        if (!file1Exists) {
379            // two not existing files are equal
380            return true;
381        }
382
383        requireFile(file1, "file1");
384        requireFile(file2, "file2");
385
386        if (file1.length() != file2.length()) {
387            // lengths differ, cannot be equal
388            return false;
389        }
390
391        if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) {
392            // same file
393            return true;
394        }
395
396        try (InputStream input1 = Files.newInputStream(file1.toPath()); InputStream input2 = Files.newInputStream(file2.toPath())) {
397            return IOUtils.contentEquals(input1, input2);
398        }
399    }
400
401    /**
402     * Compares the contents of two files to determine if they are equal or not.
403     * <p>
404     * This method checks to see if the two files point to the same file,
405     * before resorting to line-by-line comparison of the contents.
406     * </p>
407     *
408     * @param file1       the first file
409     * @param file2       the second file
410     * @param charsetName the name of the requested charset.
411     *                    May be null, in which case the platform default is used
412     * @return true if the content of the files are equal or neither exists,
413     * false otherwise
414     * @throws IllegalArgumentException when an input is not a file.
415     * @throws IOException in case of an I/O error.
416     * @throws UnsupportedCharsetException If the named charset is unavailable (unchecked exception).
417     * @see IOUtils#contentEqualsIgnoreEOL(Reader, Reader)
418     * @since 2.2
419     */
420    public static boolean contentEqualsIgnoreEOL(final File file1, final File file2, final String charsetName)
421            throws IOException {
422        if (file1 == null && file2 == null) {
423            return true;
424        }
425        if (file1 == null || file2 == null) {
426            return false;
427        }
428        final boolean file1Exists = file1.exists();
429        if (file1Exists != file2.exists()) {
430            return false;
431        }
432
433        if (!file1Exists) {
434            // two not existing files are equal
435            return true;
436        }
437
438        requireFile(file1, "file1");
439        requireFile(file2, "file2");
440
441        if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) {
442            // same file
443            return true;
444        }
445
446        final Charset charset = Charsets.toCharset(charsetName);
447        try (Reader input1 = new InputStreamReader(Files.newInputStream(file1.toPath()), charset);
448             Reader input2 = new InputStreamReader(Files.newInputStream(file2.toPath()), charset)) {
449            return IOUtils.contentEqualsIgnoreEOL(input1, input2);
450        }
451    }
452
453    /**
454     * Converts a Collection containing java.io.File instances into array
455     * representation. This is to account for the difference between
456     * File.listFiles() and FileUtils.listFiles().
457     *
458     * @param files a Collection containing java.io.File instances
459     * @return an array of java.io.File
460     */
461    public static File[] convertFileCollectionToFileArray(final Collection<File> files) {
462        return files.toArray(EMPTY_FILE_ARRAY);
463    }
464
465    /**
466     * Copies a whole directory to a new location preserving the file dates.
467     * <p>
468     * This method copies the specified directory and all its child directories and files to the specified destination.
469     * The destination is the new location and name of the directory.
470     * </p>
471     * <p>
472     * The destination directory is created if it does not exist. If the destination directory did exist, then this
473     * method merges the source with the destination, with the source taking precedence.
474     * </p>
475     * <p>
476     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
477     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
478     * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
479     * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
480     * </p>
481     *
482     * @param srcDir an existing directory to copy, must not be {@code null}.
483     * @param destDir the new directory, must not be {@code null}.
484     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
485     * @throws IllegalArgumentException if the source or destination is invalid.
486     * @throws FileNotFoundException if the source does not exist.
487     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
488     * @since 1.1
489     */
490    public static void copyDirectory(final File srcDir, final File destDir) throws IOException {
491        copyDirectory(srcDir, destDir, true);
492    }
493
494    /**
495     * Copies a whole directory to a new location.
496     * <p>
497     * This method copies the contents of the specified source directory to within the specified destination directory.
498     * </p>
499     * <p>
500     * The destination directory is created if it does not exist. If the destination directory did exist, then this
501     * method merges the source with the destination, with the source taking precedence.
502     * </p>
503     * <p>
504     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the files' last
505     * modified date/times using {@link File#setLastModified(long)}, however it is not guaranteed that those operations
506     * will succeed. If the modification operation fails, the methods throws IOException.
507     * </p>
508     *
509     * @param srcDir an existing directory to copy, must not be {@code null}.
510     * @param destDir the new directory, must not be {@code null}.
511     * @param preserveFileDate true if the file date of the copy should be the same as the original.
512     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
513     * @throws IllegalArgumentException if the source or destination is invalid.
514     * @throws FileNotFoundException if the source does not exist.
515     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
516     * @since 1.1
517     */
518    public static void copyDirectory(final File srcDir, final File destDir, final boolean preserveFileDate)
519        throws IOException {
520        copyDirectory(srcDir, destDir, null, preserveFileDate);
521    }
522
523    /**
524     * Copies a filtered directory to a new location preserving the file dates.
525     * <p>
526     * This method copies the contents of the specified source directory to within the specified destination directory.
527     * </p>
528     * <p>
529     * The destination directory is created if it does not exist. If the destination directory did exist, then this
530     * method merges the source with the destination, with the source taking precedence.
531     * </p>
532     * <p>
533     * <strong>Note:</strong> This method tries to preserve the files' last modified date/times using
534     * {@link File#setLastModified(long)}, however it is not guaranteed that those operations will succeed. If the
535     * modification operation fails, the methods throws IOException.
536     * </p>
537     * <b>Example: Copy directories only</b>
538     *
539     * <pre>
540     * // only copy the directory structure
541     * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY);
542     * </pre>
543     *
544     * <b>Example: Copy directories and txt files</b>
545     *
546     * <pre>
547     * // Create a filter for ".txt" files
548     * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
549     * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
550     *
551     * // Create a filter for either directories or ".txt" files
552     * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
553     *
554     * // Copy using the filter
555     * FileUtils.copyDirectory(srcDir, destDir, filter);
556     * </pre>
557     *
558     * @param srcDir an existing directory to copy, must not be {@code null}.
559     * @param destDir the new directory, must not be {@code null}.
560     * @param filter the filter to apply, null means copy all directories and files should be the same as the original.
561     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
562     * @throws IllegalArgumentException if the source or destination is invalid.
563     * @throws FileNotFoundException if the source does not exist.
564     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
565     * @since 1.4
566     */
567    public static void copyDirectory(final File srcDir, final File destDir, final FileFilter filter)
568        throws IOException {
569        copyDirectory(srcDir, destDir, filter, true);
570    }
571
572    /**
573     * Copies a filtered directory to a new location.
574     * <p>
575     * This method copies the contents of the specified source directory to within the specified destination directory.
576     * </p>
577     * <p>
578     * The destination directory is created if it does not exist. If the destination directory did exist, then this
579     * method merges the source with the destination, with the source taking precedence.
580     * </p>
581     * <p>
582     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
583     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
584     * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
585     * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
586     * </p>
587     * <b>Example: Copy directories only</b>
588     *
589     * <pre>
590     * // only copy the directory structure
591     * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
592     * </pre>
593     *
594     * <b>Example: Copy directories and txt files</b>
595     *
596     * <pre>
597     * // Create a filter for ".txt" files
598     * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
599     * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
600     *
601     * // Create a filter for either directories or ".txt" files
602     * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
603     *
604     * // Copy using the filter
605     * FileUtils.copyDirectory(srcDir, destDir, filter, false);
606     * </pre>
607     *
608     * @param srcDir an existing directory to copy, must not be {@code null}.
609     * @param destDir the new directory, must not be {@code null}.
610     * @param filter the filter to apply, null means copy all directories and files.
611     * @param preserveFileDate true if the file date of the copy should be the same as the original.
612     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
613     * @throws IllegalArgumentException if the source or destination is invalid.
614     * @throws FileNotFoundException if the source does not exist.
615     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
616     * @since 1.4
617     */
618    public static void copyDirectory(final File srcDir, final File destDir, final FileFilter filter, final boolean preserveFileDate) throws IOException {
619        copyDirectory(srcDir, destDir, filter, preserveFileDate, StandardCopyOption.REPLACE_EXISTING);
620    }
621
622    /**
623     * Copies a filtered directory to a new location.
624     * <p>
625     * This method copies the contents of the specified source directory to within the specified destination directory.
626     * </p>
627     * <p>
628     * The destination directory is created if it does not exist. If the destination directory did exist, then this
629     * method merges the source with the destination, with the source taking precedence.
630     * </p>
631     * <p>
632     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
633     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
634     * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
635     * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
636     * </p>
637     * <b>Example: Copy directories only</b>
638     *
639     * <pre>
640     * // only copy the directory structure
641     * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
642     * </pre>
643     *
644     * <b>Example: Copy directories and txt files</b>
645     *
646     * <pre>
647     * // Create a filter for ".txt" files
648     * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
649     * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
650     *
651     * // Create a filter for either directories or ".txt" files
652     * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
653     *
654     * // Copy using the filter
655     * FileUtils.copyDirectory(srcDir, destDir, filter, false);
656     * </pre>
657     *
658     * @param srcDir an existing directory to copy, must not be {@code null}
659     * @param destDir the new directory, must not be {@code null}
660     * @param fileFilter the filter to apply, null means copy all directories and files
661     * @param preserveFileDate true if the file date of the copy should be the same as the original
662     * @param copyOptions options specifying how the copy should be done, for example {@link StandardCopyOption}.
663     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
664     * @throws IllegalArgumentException if the source or destination is invalid.
665     * @throws FileNotFoundException if the source does not exist.
666     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
667     * @since 2.8.0
668     */
669    public static void copyDirectory(final File srcDir, final File destDir, final FileFilter fileFilter, final boolean preserveFileDate,
670        final CopyOption... copyOptions) throws IOException {
671        requireFileCopy(srcDir, destDir);
672        requireDirectory(srcDir, "srcDir");
673        requireCanonicalPathsNotEquals(srcDir, destDir);
674
675        // Cater for destination being directory within the source directory (see IO-141)
676        List<String> exclusionList = null;
677        final String srcDirCanonicalPath = srcDir.getCanonicalPath();
678        final String destDirCanonicalPath = destDir.getCanonicalPath();
679        if (destDirCanonicalPath.startsWith(srcDirCanonicalPath)) {
680            final File[] srcFiles = listFiles(srcDir, fileFilter);
681            if (srcFiles.length > 0) {
682                exclusionList = new ArrayList<>(srcFiles.length);
683                for (final File srcFile : srcFiles) {
684                    exclusionList.add(new File(destDir, srcFile.getName()).getCanonicalPath());
685                }
686            }
687        }
688        doCopyDirectory(srcDir, destDir, fileFilter, exclusionList, preserveFileDate, copyOptions);
689    }
690
691    /**
692     * Copies a directory to within another directory preserving the file dates.
693     * <p>
694     * This method copies the source directory and all its contents to a directory of the same name in the specified
695     * destination directory.
696     * </p>
697     * <p>
698     * The destination directory is created if it does not exist. If the destination directory did exist, then this
699     * method merges the source with the destination, with the source taking precedence.
700     * </p>
701     * <p>
702     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
703     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
704     * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
705     * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
706     * </p>
707     *
708     * @param sourceDir an existing directory to copy, must not be {@code null}.
709     * @param destinationDir the directory to place the copy in, must not be {@code null}.
710     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
711     * @throws IllegalArgumentException if the source or destination is invalid.
712     * @throws FileNotFoundException if the source does not exist.
713     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
714     * @since 1.2
715     */
716    public static void copyDirectoryToDirectory(final File sourceDir, final File destinationDir) throws IOException {
717        requireDirectoryIfExists(sourceDir, "sourceDir");
718        requireDirectoryIfExists(destinationDir, "destinationDir");
719        copyDirectory(sourceDir, new File(destinationDir, sourceDir.getName()), true);
720    }
721
722    /**
723     * Copies a file to a new location preserving the file date.
724     * <p>
725     * This method copies the contents of the specified source file to the specified destination file. The directory
726     * holding the destination file is created if it does not exist. If the destination file exists, then this method
727     * will overwrite it.
728     * </p>
729     * <p>
730     * <strong>Note:</strong> This method tries to preserve the file's last modified date/times using
731     * {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is not guaranteed that the
732     * operation will succeed. If the modification operation fails it will fallback to
733     * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
734     * </p>
735     *
736     * @param srcFile an existing file to copy, must not be {@code null}.
737     * @param destFile the new file, must not be {@code null}.
738     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
739     * @throws IOException if source or destination is invalid.
740     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
741     * @throws IOException if the output file length is not the same as the input file length after the copy completes.
742     * @see #copyFileToDirectory(File, File)
743     * @see #copyFile(File, File, boolean)
744     */
745    public static void copyFile(final File srcFile, final File destFile) throws IOException {
746        copyFile(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING);
747    }
748
749    /**
750     * Copies an existing file to a new file location.
751     * <p>
752     * This method copies the contents of the specified source file to the specified destination file. The directory
753     * holding the destination file is created if it does not exist. If the destination file exists, then this method
754     * will overwrite it.
755     * </p>
756     * <p>
757     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
758     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
759     * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
760     * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
761     * </p>
762     *
763     * @param srcFile an existing file to copy, must not be {@code null}.
764     * @param destFile the new file, must not be {@code null}.
765     * @param preserveFileDate true if the file date of the copy should be the same as the original.
766     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
767     * @throws IOException if source or destination is invalid.
768     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
769     * @throws IOException if the output file length is not the same as the input file length after the copy completes
770     * @see #copyFile(File, File, boolean, CopyOption...)
771     */
772    public static void copyFile(final File srcFile, final File destFile, final boolean preserveFileDate) throws IOException {
773        copyFile(srcFile, destFile, preserveFileDate, StandardCopyOption.REPLACE_EXISTING);
774    }
775
776    /**
777     * Copies a file to a new location.
778     * <p>
779     * This method copies the contents of the specified source file to the specified destination file. The directory
780     * holding the destination file is created if it does not exist. If the destination file exists, you can overwrite
781     * it with {@link StandardCopyOption#REPLACE_EXISTING}.
782     * </p>
783     * <p>
784     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
785     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
786     * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
787     * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
788     * </p>
789     *
790     * @param srcFile an existing file to copy, must not be {@code null}.
791     * @param destFile the new file, must not be {@code null}.
792     * @param preserveFileDate true if the file date of the copy should be the same as the original.
793     * @param copyOptions options specifying how the copy should be done, for example {@link StandardCopyOption}..
794     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
795     * @throws FileNotFoundException if the source does not exist.
796     * @throws IllegalArgumentException if source is not a file.
797     * @throws IOException if the output file length is not the same as the input file length after the copy completes.
798     * @throws IOException if an I/O error occurs, or setting the last-modified time didn't succeed.
799     * @see #copyFileToDirectory(File, File, boolean)
800     * @since 2.8.0
801     */
802    public static void copyFile(final File srcFile, final File destFile, final boolean preserveFileDate, final CopyOption... copyOptions) throws IOException {
803        requireFileCopy(srcFile, destFile);
804        requireFile(srcFile, "srcFile");
805        requireCanonicalPathsNotEquals(srcFile, destFile);
806        createParentDirectories(destFile);
807        requireFileIfExists(destFile, "destFile");
808        if (destFile.exists()) {
809            requireCanWrite(destFile, "destFile");
810        }
811        Files.copy(srcFile.toPath(), destFile.toPath(), copyOptions);
812
813        // On Windows, the last modified time is copied by default.
814        if (preserveFileDate && !setTimes(srcFile, destFile)) {
815            throw new IOException("Cannot set the file time.");
816        }
817    }
818
819    /**
820     * Copies a file to a new location.
821     * <p>
822     * This method copies the contents of the specified source file to the specified destination file. The directory
823     * holding the destination file is created if it does not exist. If the destination file exists, you can overwrite
824     * it if you use {@link StandardCopyOption#REPLACE_EXISTING}.
825     * </p>
826     *
827     * @param srcFile an existing file to copy, must not be {@code null}.
828     * @param destFile the new file, must not be {@code null}.
829     * @param copyOptions options specifying how the copy should be done, for example {@link StandardCopyOption}..
830     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
831     * @throws FileNotFoundException if the source does not exist.
832     * @throws IllegalArgumentException if source is not a file.
833     * @throws IOException if an I/O error occurs.
834     * @see StandardCopyOption
835     * @since 2.9.0
836     */
837    public static void copyFile(final File srcFile, final File destFile, final CopyOption... copyOptions) throws IOException {
838        copyFile(srcFile, destFile, true, copyOptions);
839    }
840
841    /**
842     * Copies bytes from a {@link File} to an {@link OutputStream}.
843     * <p>
844     * This method buffers the input internally, so there is no need to use a {@link BufferedInputStream}.
845     * </p>
846     *
847     * @param input  the {@link File} to read.
848     * @param output the {@link OutputStream} to write.
849     * @return the number of bytes copied
850     * @throws NullPointerException if the File is {@code null}.
851     * @throws NullPointerException if the OutputStream is {@code null}.
852     * @throws IOException          if an I/O error occurs.
853     * @since 2.1
854     */
855    public static long copyFile(final File input, final OutputStream output) throws IOException {
856        try (InputStream fis = Files.newInputStream(input.toPath())) {
857            return IOUtils.copyLarge(fis, output);
858        }
859    }
860
861    /**
862     * Copies a file to a directory preserving the file date.
863     * <p>
864     * This method copies the contents of the specified source file to a file of the same name in the specified
865     * destination directory. The destination directory is created if it does not exist. If the destination file exists,
866     * then this method will overwrite it.
867     * </p>
868     * <p>
869     * <strong>Note:</strong> This method tries to preserve the file's last modified date/times using
870     * {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is not guaranteed that the
871     * operation will succeed. If the modification operation fails it will fallback to
872     * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
873     * </p>
874     *
875     * @param srcFile an existing file to copy, must not be {@code null}.
876     * @param destDir the directory to place the copy in, must not be {@code null}.
877     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
878     * @throws IllegalArgumentException if source or destination is invalid.
879     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
880     * @see #copyFile(File, File, boolean)
881     */
882    public static void copyFileToDirectory(final File srcFile, final File destDir) throws IOException {
883        copyFileToDirectory(srcFile, destDir, true);
884    }
885
886    /**
887     * Copies a file to a directory optionally preserving the file date.
888     * <p>
889     * This method copies the contents of the specified source file to a file of the same name in the specified
890     * destination directory. The destination directory is created if it does not exist. If the destination file exists,
891     * then this method will overwrite it.
892     * </p>
893     * <p>
894     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
895     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
896     * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
897     * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
898     * </p>
899     *
900     * @param sourceFile an existing file to copy, must not be {@code null}.
901     * @param destinationDir the directory to place the copy in, must not be {@code null}.
902     * @param preserveFileDate true if the file date of the copy should be the same as the original.
903     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
904     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
905     * @throws IOException if the output file length is not the same as the input file length after the copy completes.
906     * @see #copyFile(File, File, CopyOption...)
907     * @since 1.3
908     */
909    public static void copyFileToDirectory(final File sourceFile, final File destinationDir, final boolean preserveFileDate) throws IOException {
910        Objects.requireNonNull(sourceFile, "sourceFile");
911        requireDirectoryIfExists(destinationDir, "destinationDir");
912        copyFile(sourceFile, new File(destinationDir, sourceFile.getName()), preserveFileDate);
913    }
914
915    /**
916     * Copies bytes from an {@link InputStream} {@code source} to a file
917     * {@code destination}. The directories up to {@code destination}
918     * will be created if they don't already exist. {@code destination}
919     * will be overwritten if it already exists.
920     * <p>
921     * <em>The {@code source} stream is closed.</em>
922     * </p>
923     * <p>
924     * See {@link #copyToFile(InputStream, File)} for a method that does not close the input stream.
925     * </p>
926     *
927     * @param source      the {@link InputStream} to copy bytes from, must not be {@code null}, will be closed
928     * @param destination the non-directory {@link File} to write bytes to
929     *                    (possibly overwriting), must not be {@code null}
930     * @throws IOException if {@code destination} is a directory
931     * @throws IOException if {@code destination} cannot be written
932     * @throws IOException if {@code destination} needs creating but can't be
933     * @throws IOException if an IO error occurs during copying
934     * @since 2.0
935     */
936    public static void copyInputStreamToFile(final InputStream source, final File destination) throws IOException {
937        try (InputStream inputStream = source) {
938            copyToFile(inputStream, destination);
939        }
940    }
941
942    /**
943     * Copies a file or directory to within another directory preserving the file dates.
944     * <p>
945     * This method copies the source file or directory, along all its contents, to a directory of the same name in the
946     * specified destination directory.
947     * </p>
948     * <p>
949     * The destination directory is created if it does not exist. If the destination directory did exist, then this method
950     * merges the source with the destination, with the source taking precedence.
951     * </p>
952     * <p>
953     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
954     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
955     * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
956     * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
957     * </p>
958     *
959     * @param sourceFile an existing file or directory to copy, must not be {@code null}.
960     * @param destinationDir the directory to place the copy in, must not be {@code null}.
961     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
962     * @throws IllegalArgumentException if the source or destination is invalid.
963     * @throws FileNotFoundException if the source does not exist.
964     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
965     * @see #copyDirectoryToDirectory(File, File)
966     * @see #copyFileToDirectory(File, File)
967     * @since 2.6
968     */
969    public static void copyToDirectory(final File sourceFile, final File destinationDir) throws IOException {
970        Objects.requireNonNull(sourceFile, "sourceFile");
971        if (sourceFile.isFile()) {
972            copyFileToDirectory(sourceFile, destinationDir);
973        } else if (sourceFile.isDirectory()) {
974            copyDirectoryToDirectory(sourceFile, destinationDir);
975        } else {
976            throw new FileNotFoundException("The source " + sourceFile + " does not exist");
977        }
978    }
979
980    /**
981     * Copies a files to a directory preserving each file's date.
982     * <p>
983     * This method copies the contents of the specified source files
984     * to a file of the same name in the specified destination directory.
985     * The destination directory is created if it does not exist.
986     * If the destination file exists, then this method will overwrite it.
987     * </p>
988     * <p>
989     * <strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve the file's last
990     * modified date/times using {@link BasicFileAttributeView#setTimes(FileTime, FileTime, FileTime)}, however it is
991     * not guaranteed that the operation will succeed. If the modification operation fails it will fallback to
992     * {@link File#setLastModified(long)} and if that fails, the methods throws IOException.
993     * </p>
994     *
995     * @param sourceIterable  existing files to copy, must not be {@code null}.
996     * @param destinationDir  the directory to place the copies in, must not be {@code null}.
997     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
998     * @throws IOException if source or destination is invalid.
999     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
1000     * @see #copyFileToDirectory(File, File)
1001     * @since 2.6
1002     */
1003    public static void copyToDirectory(final Iterable<File> sourceIterable, final File destinationDir) throws IOException {
1004        Objects.requireNonNull(sourceIterable, "sourceIterable");
1005        for (final File src : sourceIterable) {
1006            copyFileToDirectory(src, destinationDir);
1007        }
1008    }
1009
1010    /**
1011     * Copies bytes from an {@link InputStream} source to a {@link File} destination. The directories
1012     * up to {@code destination} will be created if they don't already exist. {@code destination} will be
1013     * overwritten if it already exists. The {@code source} stream is left open, e.g. for use with
1014     * {@link java.util.zip.ZipInputStream ZipInputStream}. See {@link #copyInputStreamToFile(InputStream, File)} for a
1015     * method that closes the input stream.
1016     *
1017     * @param inputStream the {@link InputStream} to copy bytes from, must not be {@code null}
1018     * @param file the non-directory {@link File} to write bytes to (possibly overwriting), must not be
1019     *        {@code null}
1020     * @throws NullPointerException if the InputStream is {@code null}.
1021     * @throws NullPointerException if the File is {@code null}.
1022     * @throws IllegalArgumentException if the file object is a directory.
1023     * @throws IllegalArgumentException if the file is not writable.
1024     * @throws IOException if the directories could not be created.
1025     * @throws IOException if an IO error occurs during copying.
1026     * @since 2.5
1027     */
1028    public static void copyToFile(final InputStream inputStream, final File file) throws IOException {
1029        try (OutputStream out = newOutputStream(file, false)) {
1030            IOUtils.copy(inputStream, out);
1031        }
1032    }
1033
1034    /**
1035     * Copies bytes from the URL {@code source} to a file
1036     * {@code destination}. The directories up to {@code destination}
1037     * will be created if they don't already exist. {@code destination}
1038     * will be overwritten if it already exists.
1039     * <p>
1040     * Warning: this method does not set a connection or read timeout and thus
1041     * might block forever. Use {@link #copyURLToFile(URL, File, int, int)}
1042     * with reasonable timeouts to prevent this.
1043     * </p>
1044     *
1045     * @param source      the {@link URL} to copy bytes from, must not be {@code null}
1046     * @param destination the non-directory {@link File} to write bytes to
1047     *                    (possibly overwriting), must not be {@code null}
1048     * @throws IOException if {@code source} URL cannot be opened
1049     * @throws IOException if {@code destination} is a directory
1050     * @throws IOException if {@code destination} cannot be written
1051     * @throws IOException if {@code destination} needs creating but can't be
1052     * @throws IOException if an IO error occurs during copying
1053     */
1054    public static void copyURLToFile(final URL source, final File destination) throws IOException {
1055        final Path path = destination.toPath();
1056        PathUtils.createParentDirectories(path);
1057        PathUtils.copy(source::openStream, path, StandardCopyOption.REPLACE_EXISTING);
1058    }
1059
1060    /**
1061     * Copies bytes from the URL {@code source} to a file {@code destination}. The directories up to
1062     * {@code destination} will be created if they don't already exist. {@code destination} will be
1063     * overwritten if it already exists.
1064     *
1065     * @param source the {@link URL} to copy bytes from, must not be {@code null}
1066     * @param destination the non-directory {@link File} to write bytes to (possibly overwriting), must not be
1067     *        {@code null}
1068     * @param connectionTimeoutMillis the number of milliseconds until this method will time out if no connection could
1069     *        be established to the {@code source}
1070     * @param readTimeoutMillis the number of milliseconds until this method will time out if no data could be read from
1071     *        the {@code source}
1072     * @throws IOException if {@code source} URL cannot be opened
1073     * @throws IOException if {@code destination} is a directory
1074     * @throws IOException if {@code destination} cannot be written
1075     * @throws IOException if {@code destination} needs creating but can't be
1076     * @throws IOException if an IO error occurs during copying
1077     * @since 2.0
1078     */
1079    public static void copyURLToFile(final URL source, final File destination, final int connectionTimeoutMillis, final int readTimeoutMillis)
1080        throws IOException {
1081        try (CloseableURLConnection urlConnection = CloseableURLConnection.open(source)) {
1082            urlConnection.setConnectTimeout(connectionTimeoutMillis);
1083            urlConnection.setReadTimeout(readTimeoutMillis);
1084            try (InputStream stream = urlConnection.getInputStream()) {
1085                copyInputStreamToFile(stream, destination);
1086            }
1087        }
1088    }
1089
1090    /**
1091     * Creates all parent directories for a File object, including any necessary but nonexistent parent directories. If a parent directory already exists or is
1092     * null, nothing happens.
1093     *
1094     * @param file the File that may need parents, may be null.
1095     * @return The parent directory, or {@code null} if the given File does have a parent.
1096     * @throws IOException       if the directory was not created along with all its parent directories.
1097     * @throws SecurityException See {@link File#mkdirs()}.
1098     * @since 2.9.0
1099     */
1100    public static File createParentDirectories(final File file) throws IOException {
1101        return mkdirs(getParentFile(file));
1102    }
1103
1104    /**
1105     * Gets the current directory.
1106     *
1107     * @return the current directory.
1108     * @since 2.12.0
1109     */
1110    public static File current() {
1111        return PathUtils.current().toFile();
1112    }
1113
1114    /**
1115     * Decodes the specified URL as per RFC 3986, i.e. transforms
1116     * percent-encoded octets to characters by decoding with the UTF-8 character
1117     * set. This function is primarily intended for usage with
1118     * {@link java.net.URL} which unfortunately does not enforce proper URLs. As
1119     * such, this method will leniently accept invalid characters or malformed
1120     * percent-encoded octets and simply pass them literally through to the
1121     * result string. Except for rare edge cases, this will make unencoded URLs
1122     * pass through unaltered.
1123     *
1124     * @param url The URL to decode, may be {@code null}.
1125     * @return The decoded URL or {@code null} if the input was
1126     * {@code null}.
1127     */
1128    static String decodeUrl(final String url) {
1129        String decoded = url;
1130        if (url != null && url.indexOf('%') >= 0) {
1131            final int n = url.length();
1132            final StringBuilder builder = new StringBuilder();
1133            final ByteBuffer byteBuffer = ByteBuffer.allocate(n);
1134            for (int i = 0; i < n; ) {
1135                if (url.charAt(i) == '%') {
1136                    try {
1137                        do {
1138                            final byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16);
1139                            byteBuffer.put(octet);
1140                            i += 3;
1141                        } while (i < n && url.charAt(i) == '%');
1142                        continue;
1143                    } catch (final RuntimeException ignored) {
1144                        // malformed percent-encoded octet, fall through and
1145                        // append characters literally
1146                    } finally {
1147                        if (byteBuffer.position() > 0) {
1148                            byteBuffer.flip();
1149                            builder.append(StandardCharsets.UTF_8.decode(byteBuffer).toString());
1150                            byteBuffer.clear();
1151                        }
1152                    }
1153                }
1154                builder.append(url.charAt(i++));
1155            }
1156            decoded = builder.toString();
1157        }
1158        return decoded;
1159    }
1160
1161    /**
1162     * Deletes the given File but throws an IOException if it cannot, unlike {@link File#delete()} which returns a
1163     * boolean.
1164     *
1165     * @param file The file to delete.
1166     * @return the given file.
1167     * @throws NullPointerException     if the parameter is {@code null}
1168     * @throws IOException              if the file cannot be deleted.
1169     * @see File#delete()
1170     * @since 2.9.0
1171     */
1172    public static File delete(final File file) throws IOException {
1173        Objects.requireNonNull(file, "file");
1174        Files.delete(file.toPath());
1175        return file;
1176    }
1177
1178    /**
1179     * Deletes a directory recursively.
1180     *
1181     * @param directory directory to delete
1182     * @throws IOException              in case deletion is unsuccessful
1183     * @throws NullPointerException     if the parameter is {@code null}
1184     * @throws IllegalArgumentException if {@code directory} is not a directory
1185     */
1186    public static void deleteDirectory(final File directory) throws IOException {
1187        Objects.requireNonNull(directory, "directory");
1188        if (!directory.exists()) {
1189            return;
1190        }
1191        if (!isSymlink(directory)) {
1192            cleanDirectory(directory);
1193        }
1194        delete(directory);
1195    }
1196
1197    /**
1198     * Schedules a directory recursively for deletion on JVM exit.
1199     *
1200     * @param directory directory to delete, must not be {@code null}
1201     * @throws NullPointerException if the directory is {@code null}
1202     * @throws IOException          in case deletion is unsuccessful
1203     */
1204    private static void deleteDirectoryOnExit(final File directory) throws IOException {
1205        if (!directory.exists()) {
1206            return;
1207        }
1208        directory.deleteOnExit();
1209        if (!isSymlink(directory)) {
1210            cleanDirectoryOnExit(directory);
1211        }
1212    }
1213
1214    /**
1215     * Deletes a file, never throwing an exception. If file is a directory, delete it and all subdirectories.
1216     * <p>
1217     * The difference between File.delete() and this method are:
1218     * </p>
1219     * <ul>
1220     * <li>A directory to be deleted does not have to be empty.</li>
1221     * <li>No exceptions are thrown when a file or directory cannot be deleted.</li>
1222     * </ul>
1223     *
1224     * @param file file or directory to delete, can be {@code null}
1225     * @return {@code true} if the file or directory was deleted, otherwise
1226     * {@code false}
1227     * @since 1.4
1228     */
1229    public static boolean deleteQuietly(final File file) {
1230        if (file == null) {
1231            return false;
1232        }
1233        try {
1234            if (file.isDirectory()) {
1235                cleanDirectory(file);
1236            }
1237        } catch (final Exception ignored) {
1238            // ignore
1239        }
1240
1241        try {
1242            return file.delete();
1243        } catch (final Exception ignored) {
1244            return false;
1245        }
1246    }
1247
1248    /**
1249     * Determines whether the {@code parent} directory contains the {@code child} element (a file or directory).
1250     * <p>
1251     * Files are normalized before comparison.
1252     * </p>
1253     *
1254     * Edge cases:
1255     * <ul>
1256     * <li>A {@code directory} must not be null: if null, throw IllegalArgumentException</li>
1257     * <li>A {@code directory} must be a directory: if not a directory, throw IllegalArgumentException</li>
1258     * <li>A directory does not contain itself: return false</li>
1259     * <li>A null child file is not contained in any parent: return false</li>
1260     * </ul>
1261     *
1262     * @param directory the file to consider as the parent.
1263     * @param child     the file to consider as the child.
1264     * @return true is the candidate leaf is under by the specified composite. False otherwise.
1265     * @throws IOException              if an IO error occurs while checking the files.
1266     * @throws NullPointerException if the given {@link File} is {@code null}.
1267     * @throws IllegalArgumentException if the given {@link File} does not exist or is not a directory.
1268     * @see FilenameUtils#directoryContains(String, String)
1269     * @since 2.2
1270     */
1271    public static boolean directoryContains(final File directory, final File child) throws IOException {
1272        requireDirectoryExists(directory, "directory");
1273
1274        if (child == null || !directory.exists() || !child.exists()) {
1275            return false;
1276        }
1277
1278        // Canonicalize paths (normalizes relative paths)
1279        return FilenameUtils.directoryContains(directory.getCanonicalPath(), child.getCanonicalPath());
1280    }
1281
1282    /**
1283     * Internal copy directory method. Creates all destination parent directories,
1284     * including any necessary but nonexistent parent directories.
1285     *
1286     * @param srcDir the validated source directory, must not be {@code null}.
1287     * @param destDir the validated destination directory, must not be {@code null}.
1288     * @param fileFilter the filter to apply, null means copy all directories and files.
1289     * @param exclusionList List of files and directories to exclude from the copy, may be null.
1290     * @param preserveDirDate preserve the directories last modified dates.
1291     * @param copyOptions options specifying how the copy should be done, see {@link StandardCopyOption}.
1292     * @throws IOException if the directory was not created along with all its parent directories.
1293     * @throws SecurityException See {@link File#mkdirs()}.
1294     */
1295    private static void doCopyDirectory(final File srcDir, final File destDir, final FileFilter fileFilter, final List<String> exclusionList,
1296        final boolean preserveDirDate, final CopyOption... copyOptions) throws IOException {
1297        // recurse dirs, copy files.
1298        final File[] srcFiles = listFiles(srcDir, fileFilter);
1299        requireDirectoryIfExists(destDir, "destDir");
1300        mkdirs(destDir);
1301        requireCanWrite(destDir, "destDir");
1302        for (final File srcFile : srcFiles) {
1303            final File dstFile = new File(destDir, srcFile.getName());
1304            if (exclusionList == null || !exclusionList.contains(srcFile.getCanonicalPath())) {
1305                if (srcFile.isDirectory()) {
1306                    doCopyDirectory(srcFile, dstFile, fileFilter, exclusionList, preserveDirDate, copyOptions);
1307                } else {
1308                    copyFile(srcFile, dstFile, preserveDirDate, copyOptions);
1309                }
1310            }
1311        }
1312        // Do this last, as the above has probably affected directory metadata
1313        if (preserveDirDate) {
1314            setTimes(srcDir, destDir);
1315        }
1316    }
1317
1318    /**
1319     * Deletes a file or directory. For a directory, delete it and all subdirectories.
1320     * <p>
1321     * The difference between File.delete() and this method are:
1322     * </p>
1323     * <ul>
1324     * <li>The directory does not have to be empty.</li>
1325     * <li>You get an exception when a file or directory cannot be deleted.</li>
1326     * </ul>
1327     *
1328     * @param file file or directory to delete, must not be {@code null}.
1329     * @throws NullPointerException  if the file is {@code null}.
1330     * @throws FileNotFoundException if the file was not found.
1331     * @throws IOException           in case deletion is unsuccessful.
1332     */
1333    public static void forceDelete(final File file) throws IOException {
1334        Objects.requireNonNull(file, "file");
1335        final Counters.PathCounters deleteCounters;
1336        try {
1337            deleteCounters = PathUtils.delete(file.toPath(), PathUtils.EMPTY_LINK_OPTION_ARRAY,
1338                StandardDeleteOption.OVERRIDE_READ_ONLY);
1339        } catch (final IOException e) {
1340            throw new IOException("Cannot delete file: " + file, e);
1341        }
1342
1343        if (deleteCounters.getFileCounter().get() < 1 && deleteCounters.getDirectoryCounter().get() < 1) {
1344            // didn't find a file to delete.
1345            throw new FileNotFoundException("File does not exist: " + file);
1346        }
1347    }
1348
1349    /**
1350     * Schedules a file to be deleted when JVM exits.
1351     * If file is directory delete it and all subdirectories.
1352     *
1353     * @param file file or directory to delete, must not be {@code null}.
1354     * @throws NullPointerException if the file is {@code null}.
1355     * @throws IOException          in case deletion is unsuccessful.
1356     */
1357    public static void forceDeleteOnExit(final File file) throws IOException {
1358        Objects.requireNonNull(file, "file");
1359        if (file.isDirectory()) {
1360            deleteDirectoryOnExit(file);
1361        } else {
1362            file.deleteOnExit();
1363        }
1364    }
1365
1366    /**
1367     * Creates all directories for a File object, including any necessary but nonexistent parent directories. If the {@code directory} already exists or is
1368     * null, nothing happens.
1369     * <p>
1370     * Calls {@link File#mkdirs()} and throws an {@link IOException} on failure.
1371     * </p>
1372     *
1373     * @param directory the receiver for {@code mkdirs()}. If the {@code directory} already exists or is null, nothing happens.
1374     * @throws IOException       if the directory was not created along with all its parent directories.
1375     * @throws IOException       if the given file object is not a directory.
1376     * @throws SecurityException See {@link File#mkdirs()}.
1377     * @see File#mkdirs()
1378     */
1379    public static void forceMkdir(final File directory) throws IOException {
1380        mkdirs(directory);
1381    }
1382
1383    /**
1384     * Calls {@link File#mkdirs()} and throws an {@link IOException} on failure.
1385     * <p>
1386     * Creates all directories for a File object, including any necessary but nonexistent parent directories. If the {@code directory} already exists or is
1387     * null, nothing happens.
1388     * </p>
1389     *
1390     * @param file file with parents to create, must not be {@code null}.
1391     * @throws NullPointerException if the file is {@code null}.
1392     * @throws IOException          if the directory was not created along with all its parent directories.
1393     * @throws IOException          if the given file object is not a directory.
1394     * @throws SecurityException    See {@link File#mkdirs()}.
1395     * @see File#mkdirs()
1396     * @since 2.5
1397     */
1398    public static void forceMkdirParent(final File file) throws IOException {
1399        forceMkdir(getParentFile(Objects.requireNonNull(file, "file")));
1400    }
1401
1402    /**
1403     * Constructs a file from the set of name elements.
1404     *
1405     * @param directory the parent directory.
1406     * @param names the name elements.
1407     * @return the new file.
1408     * @since 2.1
1409     */
1410    public static File getFile(final File directory, final String... names) {
1411        Objects.requireNonNull(directory, "directory");
1412        Objects.requireNonNull(names, "names");
1413        File file = directory;
1414        for (final String name : names) {
1415            file = new File(file, name);
1416        }
1417        return file;
1418    }
1419
1420    /**
1421     * Constructs a file from the set of name elements.
1422     *
1423     * @param names the name elements.
1424     * @return the file.
1425     * @since 2.1
1426     */
1427    public static File getFile(final String... names) {
1428        Objects.requireNonNull(names, "names");
1429        File file = null;
1430        for (final String name : names) {
1431            if (file == null) {
1432                file = new File(name);
1433            } else {
1434                file = new File(file, name);
1435            }
1436        }
1437        return file;
1438    }
1439
1440    /**
1441     * Gets the parent of the given file. The given file may be null. Note that a file's parent may be null as well.
1442     *
1443     * @param file The file to query, may be null.
1444     * @return The parent file or {@code null}. Note that a file's parent may be null as well.
1445     */
1446    private static File getParentFile(final File file) {
1447        return file == null ? null : file.getParentFile();
1448    }
1449
1450    /**
1451     * Returns a {@link File} representing the system temporary directory.
1452     *
1453     * @return the system temporary directory.
1454     * @since 2.0
1455     */
1456    public static File getTempDirectory() {
1457        return new File(getTempDirectoryPath());
1458    }
1459
1460    /**
1461     * Returns the path to the system temporary directory.
1462     *
1463     * @return the path to the system temporary directory.
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 java.io.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, ex. {"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 java.io.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 Uncheck.apply(d -> streamFiles(d, recursive, extensions).iterator(), directory);
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 java.io.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 java.io.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 visitor.getFileList().stream().map(Path::toFile).collect(Collectors.toList());
2228    }
2229
2230    /**
2231     * Finds 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, ex. {"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 java.io.File with the matching files
2239     */
2240    public static Collection<File> listFiles(final File directory, final String[] extensions, final boolean recursive) {
2241        return Uncheck.apply(d -> toList(streamFiles(d, recursive, extensions)), directory);
2242    }
2243
2244    /**
2245     * Finds files within a given directory (and optionally its
2246     * subdirectories). All files found are filtered by an IOFileFilter.
2247     * <p>
2248     * The resulting collection includes the starting directory and
2249     * any subdirectories that match the directory filter.
2250     * </p>
2251     *
2252     * @param directory  the directory to search in
2253     * @param fileFilter filter to apply when finding files.
2254     * @param dirFilter  optional filter to apply when finding subdirectories.
2255     *                   If this parameter is {@code null}, subdirectories will not be included in the
2256     *                   search. Use TrueFileFilter.INSTANCE to match all directories.
2257     * @return a collection of java.io.File with the matching files
2258     * @see org.apache.commons.io.FileUtils#listFiles
2259     * @see org.apache.commons.io.filefilter.FileFilterUtils
2260     * @see org.apache.commons.io.filefilter.NameFileFilter
2261     * @since 2.2
2262     */
2263    public static Collection<File> listFilesAndDirs(final File directory, final IOFileFilter fileFilter, final IOFileFilter dirFilter) {
2264        final AccumulatorPathVisitor visitor = Uncheck.apply(d -> listAccumulate(d, fileFilter, dirFilter, FileVisitOption.FOLLOW_LINKS),
2265            directory);
2266        final List<Path> list = visitor.getFileList();
2267        list.addAll(visitor.getDirList());
2268        return list.stream().map(Path::toFile).collect(Collectors.toList());
2269    }
2270
2271    /**
2272     * Calls {@link File#mkdirs()} and throws an {@link IOException} on failure.
2273     * <p>
2274     * Creates all directories for a File object, including any necessary but nonexistent parent directories. If the {@code directory} already exists or is
2275     * null, nothing happens.
2276     * </p>
2277     *
2278     * @param directory the receiver for {@code mkdirs()}. If the {@code directory} already exists or is null, nothing happens.
2279     * @return the given directory.
2280     * @throws IOException       if the directory was not created along with all its parent directories.
2281     * @throws IOException       if the given file object is not a directory.
2282     * @throws SecurityException See {@link File#mkdirs()}.
2283     * @see File#mkdirs()
2284     */
2285    private static File mkdirs(final File directory) throws IOException {
2286        if (directory != null && !directory.mkdirs() && !directory.isDirectory()) {
2287            throw new IOException("Cannot create directory '" + directory + "'.");
2288        }
2289        return directory;
2290    }
2291
2292    /**
2293     * Moves a directory.
2294     * <p>
2295     * When the destination directory is on another file system, do a "copy and delete".
2296     * </p>
2297     *
2298     * @param srcDir the directory to be moved.
2299     * @param destDir the destination directory.
2300     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2301     * @throws IllegalArgumentException if the source or destination is invalid.
2302     * @throws FileNotFoundException if the source does not exist.
2303     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
2304     * @since 1.4
2305     */
2306    public static void moveDirectory(final File srcDir, final File destDir) throws IOException {
2307        validateMoveParameters(srcDir, destDir);
2308        requireDirectory(srcDir, "srcDir");
2309        requireAbsent(destDir, "destDir");
2310        if (!srcDir.renameTo(destDir)) {
2311            if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath() + File.separator)) {
2312                throw new IOException("Cannot move directory: " + srcDir + " to a subdirectory of itself: " + destDir);
2313            }
2314            copyDirectory(srcDir, destDir);
2315            deleteDirectory(srcDir);
2316            if (srcDir.exists()) {
2317                throw new IOException("Failed to delete original directory '" + srcDir +
2318                        "' after copy to '" + destDir + "'");
2319            }
2320        }
2321    }
2322
2323    /**
2324     * Moves a directory to another directory.
2325     * <p>
2326     * If {@code createDestDir} is true, creates all destination parent directories, including any necessary but nonexistent parent directories.
2327     * </p>
2328     *
2329     * @param source the file to be moved.
2330     * @param destDir the destination file.
2331     * @param createDestDir If {@code true} create the destination directory, otherwise if {@code false} throw an
2332     *        IOException.
2333     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2334     * @throws IllegalArgumentException if the source or destination is invalid.
2335     * @throws FileNotFoundException if the source does not exist.
2336     * @throws IOException if the directory was not created along with all its parent directories, if enabled.
2337     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
2338     * @throws SecurityException See {@link File#mkdirs()}.
2339     * @since 1.4
2340     */
2341    public static void moveDirectoryToDirectory(final File source, final File destDir, final boolean createDestDir) throws IOException {
2342        validateMoveParameters(source, destDir);
2343        if (!destDir.isDirectory()) {
2344            if (destDir.exists()) {
2345                throw new IOException("Destination '" + destDir + "' is not a directory");
2346            }
2347            if (!createDestDir) {
2348                throw new FileNotFoundException("Destination directory '" + destDir + "' does not exist [createDestDir=" + false + "]");
2349            }
2350            mkdirs(destDir);
2351        }
2352        moveDirectory(source, new File(destDir, source.getName()));
2353    }
2354
2355    /**
2356     * Moves a file preserving attributes.
2357     * <p>
2358     * Shorthand for {@code moveFile(srcFile, destFile, StandardCopyOption.COPY_ATTRIBUTES)}.
2359     * </p>
2360     * <p>
2361     * When the destination file is on another file system, do a "copy and delete".
2362     * </p>
2363     *
2364     * @param srcFile the file to be moved.
2365     * @param destFile the destination file.
2366     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2367     * @throws FileExistsException if the destination file exists.
2368     * @throws FileNotFoundException if the source file does not exist.
2369     * @throws IOException if source or destination is invalid.
2370     * @throws IOException if an error occurs.
2371     * @since 1.4
2372     */
2373    public static void moveFile(final File srcFile, final File destFile) throws IOException {
2374        moveFile(srcFile, destFile, StandardCopyOption.COPY_ATTRIBUTES);
2375    }
2376
2377    /**
2378     * Moves a file.
2379     * <p>
2380     * When the destination file is on another file system, do a "copy and delete".
2381     * </p>
2382     *
2383     * @param srcFile the file to be moved.
2384     * @param destFile the destination file.
2385     * @param copyOptions Copy options.
2386     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2387     * @throws FileExistsException if the destination file exists.
2388     * @throws FileNotFoundException if the source file does not exist.
2389     * @throws IOException if source or destination is invalid.
2390     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
2391     * @since 2.9.0
2392     */
2393    public static void moveFile(final File srcFile, final File destFile, final CopyOption... copyOptions) throws IOException {
2394        validateMoveParameters(srcFile, destFile);
2395        requireFile(srcFile, "srcFile");
2396        requireAbsent(destFile, "destFile");
2397        final boolean rename = srcFile.renameTo(destFile);
2398        if (!rename) {
2399            // Don't interfere with file date on move, handled by StandardCopyOption.COPY_ATTRIBUTES
2400            copyFile(srcFile, destFile, false, copyOptions);
2401            if (!srcFile.delete()) {
2402                FileUtils.deleteQuietly(destFile);
2403                throw new IOException("Failed to delete original file '" + srcFile + "' after copy to '" + destFile + "'");
2404            }
2405        }
2406    }
2407
2408    /**
2409     * Moves a file to a directory.
2410     * <p>
2411     * If {@code createDestDir} is true, creates all destination parent directories, including any necessary but nonexistent parent directories.
2412     * </p>
2413     *
2414     * @param srcFile the file to be moved.
2415     * @param destDir the destination file.
2416     * @param createDestDir If {@code true} create the destination directory, otherwise if {@code false} throw an
2417     *        IOException.
2418     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2419     * @throws FileExistsException if the destination file exists.
2420     * @throws FileNotFoundException if the source file does not exist.
2421     * @throws IOException if source or destination is invalid.
2422     * @throws IOException if the directory was not created along with all its parent directories, if enabled.
2423     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
2424     * @throws SecurityException See {@link File#mkdirs()}.
2425     * @since 1.4
2426     */
2427    public static void moveFileToDirectory(final File srcFile, final File destDir, final boolean createDestDir) throws IOException {
2428        validateMoveParameters(srcFile, destDir);
2429        if (!destDir.exists() && createDestDir) {
2430            mkdirs(destDir);
2431        }
2432        requireExistsChecked(destDir, "destDir");
2433        requireDirectory(destDir, "destDir");
2434        moveFile(srcFile, new File(destDir, srcFile.getName()));
2435    }
2436
2437    /**
2438     * Moves a file or directory to a destination directory.
2439     * <p>
2440     * If {@code createDestDir} is true, creates all destination parent directories, including any necessary but nonexistent parent directories.
2441     * </p>
2442     * <p>
2443     * When the destination is on another file system, do a "copy and delete".
2444     * </p>
2445     *
2446     * @param src           the file or directory to be moved.
2447     * @param destDir       the destination directory.
2448     * @param createDestDir If {@code true} create the destination directory, otherwise if {@code false} throw an IOException.
2449     * @throws NullPointerException  if any of the given {@link File}s are {@code null}.
2450     * @throws FileExistsException   if the directory or file exists in the destination directory.
2451     * @throws FileNotFoundException if the source file does not exist.
2452     * @throws IOException           if source or destination is invalid.
2453     * @throws IOException           if an error occurs or setting the last-modified time didn't succeed.
2454     * @since 1.4
2455     */
2456    public static void moveToDirectory(final File src, final File destDir, final boolean createDestDir) throws IOException {
2457        validateMoveParameters(src, destDir);
2458        if (src.isDirectory()) {
2459            moveDirectoryToDirectory(src, destDir, createDestDir);
2460        } else {
2461            moveFileToDirectory(src, destDir, createDestDir);
2462        }
2463    }
2464
2465    /**
2466     * Creates a new OutputStream by opening or creating a file, returning an output stream that may be used to write bytes
2467     * to the file.
2468     *
2469     * @param append Whether or not to append.
2470     * @param file the File.
2471     * @return a new OutputStream.
2472     * @throws IOException if an I/O error occurs.
2473     * @see PathUtils#newOutputStream(Path, boolean)
2474     * @since 2.12.0
2475     */
2476    public static OutputStream newOutputStream(final File file, final boolean append) throws IOException {
2477        return PathUtils.newOutputStream(Objects.requireNonNull(file, "file").toPath(), append);
2478    }
2479
2480    /**
2481     * Opens a {@link FileInputStream} for the specified file, providing better error messages than simply calling
2482     * {@code new FileInputStream(file)}.
2483     * <p>
2484     * At the end of the method either the stream will be successfully opened, or an exception will have been thrown.
2485     * </p>
2486     * <p>
2487     * An exception is thrown if the file does not exist. An exception is thrown if the file object exists but is a
2488     * directory. An exception is thrown if the file exists but cannot be read.
2489     * </p>
2490     *
2491     * @param file the file to open for input, must not be {@code null}
2492     * @return a new {@link FileInputStream} for the specified file
2493     * @throws NullPointerException if file is {@code null}.
2494     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2495     *         other reason cannot be opened for reading.
2496     * @throws IOException See FileNotFoundException above, FileNotFoundException is a subclass of IOException.
2497     * @since 1.3
2498     */
2499    public static FileInputStream openInputStream(final File file) throws IOException {
2500        Objects.requireNonNull(file, "file");
2501        return new FileInputStream(file);
2502    }
2503
2504    /**
2505     * Opens a {@link FileOutputStream} for the specified file, checking and
2506     * creating the parent directory if it does not exist.
2507     * <p>
2508     * At the end of the method either the stream will be successfully opened,
2509     * or an exception will have been thrown.
2510     * </p>
2511     * <p>
2512     * The parent directory will be created if it does not exist.
2513     * The file will be created if it does not exist.
2514     * An exception is thrown if the file object exists but is a directory.
2515     * An exception is thrown if the file exists but cannot be written to.
2516     * An exception is thrown if the parent directory cannot be created.
2517     * </p>
2518     *
2519     * @param file the file to open for output, must not be {@code null}
2520     * @return a new {@link FileOutputStream} for the specified file
2521     * @throws NullPointerException if the file object is {@code null}.
2522     * @throws IllegalArgumentException if the file object is a directory
2523     * @throws IllegalArgumentException if the file is not writable.
2524     * @throws IOException if the directories could not be created.
2525     * @since 1.3
2526     */
2527    public static FileOutputStream openOutputStream(final File file) throws IOException {
2528        return openOutputStream(file, false);
2529    }
2530
2531    /**
2532     * Opens a {@link FileOutputStream} for the specified file, checking and
2533     * creating the parent directory if it does not exist.
2534     * <p>
2535     * At the end of the method either the stream will be successfully opened,
2536     * or an exception will have been thrown.
2537     * </p>
2538     * <p>
2539     * The parent directory will be created if it does not exist.
2540     * The file will be created if it does not exist.
2541     * An exception is thrown if the file object exists but is a directory.
2542     * An exception is thrown if the file exists but cannot be written to.
2543     * An exception is thrown if the parent directory cannot be created.
2544     * </p>
2545     *
2546     * @param file   the file to open for output, must not be {@code null}
2547     * @param append if {@code true}, then bytes will be added to the
2548     *               end of the file rather than overwriting
2549     * @return a new {@link FileOutputStream} for the specified file
2550     * @throws NullPointerException if the file object is {@code null}.
2551     * @throws IllegalArgumentException if the file object is a directory
2552     * @throws IllegalArgumentException if the file is not writable.
2553     * @throws IOException if the directories could not be created.
2554     * @since 2.1
2555     */
2556    public static FileOutputStream openOutputStream(final File file, final boolean append) throws IOException {
2557        Objects.requireNonNull(file, "file");
2558        if (file.exists()) {
2559            requireFile(file, "file");
2560            requireCanWrite(file, "file");
2561        } else {
2562            createParentDirectories(file);
2563        }
2564        return new FileOutputStream(file, append);
2565    }
2566
2567    /**
2568     * Reads the contents of a file into a byte array.
2569     * The file is always closed.
2570     *
2571     * @param file the file to read, must not be {@code null}
2572     * @return the file contents, never {@code null}
2573     * @throws NullPointerException if file is {@code null}.
2574     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2575     *         other reason cannot be opened for reading.
2576     * @throws IOException if an I/O error occurs.
2577     * @since 1.1
2578     */
2579    public static byte[] readFileToByteArray(final File file) throws IOException {
2580        Objects.requireNonNull(file, "file");
2581        return Files.readAllBytes(file.toPath());
2582    }
2583
2584    /**
2585     * Reads the contents of a file into a String using the default encoding for the VM.
2586     * The file is always closed.
2587     *
2588     * @param file the file to read, must not be {@code null}
2589     * @return the file contents, never {@code null}
2590     * @throws NullPointerException if file is {@code null}.
2591     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2592     *         other reason cannot be opened for reading.
2593     * @throws IOException if an I/O error occurs.
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 FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2611     *         other reason cannot be opened for reading.
2612     * @throws IOException if an I/O error occurs.
2613     * @since 2.3
2614     */
2615    public static String readFileToString(final File file, final Charset charsetName) throws IOException {
2616        return IOUtils.toString(() -> Files.newInputStream(file.toPath()), Charsets.toCharset(charsetName));
2617    }
2618
2619    /**
2620     * Reads the contents of a file into a String. The file is always closed.
2621     *
2622     * @param file     the file to read, must not be {@code null}
2623     * @param charsetName the name of the requested charset, {@code null} means platform default
2624     * @return the file contents, never {@code null}
2625     * @throws NullPointerException if file is {@code null}.
2626     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2627     *         other reason cannot be opened for reading.
2628     * @throws IOException if an I/O error occurs.
2629     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io
2630     * .UnsupportedEncodingException} in version 2.2 if the named charset is unavailable.
2631     * @since 2.3
2632     */
2633    public static String readFileToString(final File file, final String charsetName) throws IOException {
2634        return readFileToString(file, Charsets.toCharset(charsetName));
2635    }
2636
2637    /**
2638     * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM.
2639     * The file is always closed.
2640     *
2641     * @param file the file to read, must not be {@code null}
2642     * @return the list of Strings representing each line in the file, never {@code null}
2643     * @throws NullPointerException if file is {@code null}.
2644     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2645     *         other reason cannot be opened for reading.
2646     * @throws IOException if an I/O error occurs.
2647     * @since 1.3
2648     * @deprecated 2.5 use {@link #readLines(File, Charset)} instead (and specify the appropriate encoding)
2649     */
2650    @Deprecated
2651    public static List<String> readLines(final File file) throws IOException {
2652        return readLines(file, Charset.defaultCharset());
2653    }
2654
2655    /**
2656     * Reads the contents of a file line by line to a List of Strings.
2657     * The file is always closed.
2658     *
2659     * @param file     the file to read, must not be {@code null}
2660     * @param charset the charset to use, {@code null} means platform default
2661     * @return the list of Strings representing each line in the file, never {@code null}
2662     * @throws NullPointerException if file is {@code null}.
2663     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2664     *         other reason cannot be opened for reading.
2665     * @throws IOException if an I/O error occurs.
2666     * @since 2.3
2667     */
2668    public static List<String> readLines(final File file, final Charset charset) throws IOException {
2669        return Files.readAllLines(file.toPath(), charset);
2670    }
2671
2672    /**
2673     * Reads the contents of a file line by line to a List of Strings. The file is always closed.
2674     *
2675     * @param file     the file to read, must not be {@code null}
2676     * @param charsetName the name of the requested charset, {@code null} means platform default
2677     * @return the list of Strings representing each line in the file, never {@code null}
2678     * @throws NullPointerException if file is {@code null}.
2679     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2680     *         other reason cannot be opened for reading.
2681     * @throws IOException if an I/O error occurs.
2682     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io
2683     * .UnsupportedEncodingException} in version 2.2 if the named charset is unavailable.
2684     * @since 1.1
2685     */
2686    public static List<String> readLines(final File file, final String charsetName) throws IOException {
2687        return readLines(file, Charsets.toCharset(charsetName));
2688    }
2689
2690
2691    private static void requireAbsent(final File file, final String name) throws FileExistsException {
2692        if (file.exists()) {
2693            throw new FileExistsException(String.format("File element in parameter '%s' already exists: '%s'", name, file));
2694        }
2695    }
2696
2697    /**
2698     * Throws IllegalArgumentException if the given files' canonical representations are equal.
2699     *
2700     * @param file1 The first file to compare.
2701     * @param file2 The second file to compare.
2702     * @throws IOException if an I/O error occurs.
2703     * @throws IllegalArgumentException if the given files' canonical representations are equal.
2704     */
2705    private static void requireCanonicalPathsNotEquals(final File file1, final File file2) throws IOException {
2706        final String canonicalPath = file1.getCanonicalPath();
2707        if (canonicalPath.equals(file2.getCanonicalPath())) {
2708            throw new IllegalArgumentException(String
2709                .format("File canonical paths are equal: '%s' (file1='%s', file2='%s')", canonicalPath, file1, file2));
2710        }
2711    }
2712
2713    /**
2714     * Throws an {@link IllegalArgumentException} if the file is not writable. This provides a more precise exception
2715     * message than a plain access denied.
2716     *
2717     * @param file The file to test.
2718     * @param name The parameter name to use in the exception message.
2719     * @throws NullPointerException if the given {@link File} is {@code null}.
2720     * @throws IllegalArgumentException if the file is not writable.
2721     */
2722    private static void requireCanWrite(final File file, final String name) {
2723        Objects.requireNonNull(file, "file");
2724        if (!file.canWrite()) {
2725            throw new IllegalArgumentException("File parameter '" + name + " is not writable: '" + file + "'");
2726        }
2727    }
2728
2729    /**
2730     * Requires that the given {@link File} is a directory.
2731     *
2732     * @param directory The {@link File} to check.
2733     * @param name The parameter name to use in the exception message in case of null input or if the file is not a directory.
2734     * @return the given directory.
2735     * @throws NullPointerException if the given {@link File} is {@code null}.
2736     * @throws IllegalArgumentException if the given {@link File} does not exist or is not a directory.
2737     */
2738    private static File requireDirectory(final File directory, final String name) {
2739        Objects.requireNonNull(directory, name);
2740        if (!directory.isDirectory()) {
2741            throw new IllegalArgumentException("Parameter '" + name + "' is not a directory: '" + directory + "'");
2742        }
2743        return directory;
2744    }
2745
2746    /**
2747     * Requires that the given {@link File} exists and is a directory.
2748     *
2749     * @param directory The {@link File} to check.
2750     * @param name The parameter name to use in the exception message in case of null input.
2751     * @return the given directory.
2752     * @throws NullPointerException if the given {@link File} is {@code null}.
2753     * @throws IllegalArgumentException if the given {@link File} does not exist or is not a directory.
2754     */
2755    private static File requireDirectoryExists(final File directory, final String name) {
2756        requireExists(directory, name);
2757        requireDirectory(directory, name);
2758        return directory;
2759    }
2760
2761    /**
2762     * Requires that the given {@link File} is a directory if it exists.
2763     *
2764     * @param directory The {@link File} to check.
2765     * @param name The parameter name to use in the exception message in case of null input.
2766     * @return the given directory.
2767     * @throws NullPointerException if the given {@link File} is {@code null}.
2768     * @throws IllegalArgumentException if the given {@link File} exists but is not a directory.
2769     */
2770    private static File requireDirectoryIfExists(final File directory, final String name) {
2771        Objects.requireNonNull(directory, name);
2772        if (directory.exists()) {
2773            requireDirectory(directory, name);
2774        }
2775        return directory;
2776    }
2777
2778    /**
2779     * Requires that the given {@link File} exists and throws an {@link IllegalArgumentException} if it doesn't.
2780     *
2781     * @param file The {@link File} to check.
2782     * @param fileParamName The parameter name to use in the exception message in case of {@code null} input.
2783     * @return the given file.
2784     * @throws NullPointerException if the given {@link File} is {@code null}.
2785     * @throws IllegalArgumentException if the given {@link File} does not exist.
2786     */
2787    private static File requireExists(final File file, final String fileParamName) {
2788        Objects.requireNonNull(file, fileParamName);
2789        if (!file.exists()) {
2790            throw new IllegalArgumentException("File system element for parameter '" + fileParamName + "' does not exist: '" + file + "'");
2791        }
2792        return file;
2793    }
2794
2795    /**
2796     * Requires that the given {@link File} exists and throws an {@link FileNotFoundException} if it doesn't.
2797     *
2798     * @param file The {@link File} to check.
2799     * @param fileParamName The parameter name to use in the exception message in case of {@code null} input.
2800     * @return the given file.
2801     * @throws NullPointerException if the given {@link File} is {@code null}.
2802     * @throws FileNotFoundException if the given {@link File} does not exist.
2803     */
2804    private static File requireExistsChecked(final File file, final String fileParamName) throws FileNotFoundException {
2805        Objects.requireNonNull(file, fileParamName);
2806        if (!file.exists()) {
2807            throw new FileNotFoundException("File system element for parameter '" + fileParamName + "' does not exist: '" + file + "'");
2808        }
2809        return file;
2810    }
2811
2812    /**
2813     * Requires that the given {@link File} is a file.
2814     *
2815     * @param file The {@link File} to check.
2816     * @param name The parameter name to use in the exception message.
2817     * @return the given file.
2818     * @throws NullPointerException if the given {@link File} is {@code null}.
2819     * @throws IllegalArgumentException if the given {@link File} does not exist or is not a file.
2820     */
2821    private static File requireFile(final File file, final String name) {
2822        Objects.requireNonNull(file, name);
2823        if (!file.isFile()) {
2824            throw new IllegalArgumentException("Parameter '" + name + "' is not a file: " + file);
2825        }
2826        return file;
2827    }
2828
2829    /**
2830     * Requires parameter attributes for a file copy operation.
2831     *
2832     * @param source the source file
2833     * @param destination the destination
2834     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2835     * @throws FileNotFoundException if the source does not exist.
2836     */
2837    private static void requireFileCopy(final File source, final File destination) throws FileNotFoundException {
2838        requireExistsChecked(source, "source");
2839        Objects.requireNonNull(destination, "destination");
2840    }
2841
2842    /**
2843     * Requires that the given {@link File} is a file if it exists.
2844     *
2845     * @param file The {@link File} to check.
2846     * @param name The parameter name to use in the exception message in case of null input.
2847     * @return the given directory.
2848     * @throws NullPointerException if the given {@link File} is {@code null}.
2849     * @throws IllegalArgumentException if the given {@link File} exists but is not a directory.
2850     */
2851    private static File requireFileIfExists(final File file, final String name) {
2852        Objects.requireNonNull(file, name);
2853        return file.exists() ? requireFile(file, name) : file;
2854    }
2855
2856    /**
2857     * Set file lastModifiedTime, lastAccessTime and creationTime to match source file
2858     *
2859     * @param sourceFile The source file to query.
2860     * @param targetFile The target file or directory to set.
2861     * @return {@code true} if and only if the operation succeeded;
2862     *          {@code false} otherwise
2863     * @throws NullPointerException if sourceFile is {@code null}.
2864     * @throws NullPointerException if targetFile is {@code null}.
2865     * @throws IOException if setting the last-modified time failed.
2866     */
2867    private static boolean setTimes(final File sourceFile, final File targetFile) throws IOException {
2868        Objects.requireNonNull(sourceFile, "sourceFile");
2869        Objects.requireNonNull(targetFile, "targetFile");
2870        try {
2871            // Set creation, modified, last accessed to match source file
2872            final BasicFileAttributes srcAttr = Files.readAttributes(sourceFile.toPath(), BasicFileAttributes.class);
2873            final BasicFileAttributeView destAttrView = Files.getFileAttributeView(targetFile.toPath(), BasicFileAttributeView.class);
2874            // null guards are not needed; BasicFileAttributes.setTimes(...) is null safe
2875            destAttrView.setTimes(srcAttr.lastModifiedTime(), srcAttr.lastAccessTime(), srcAttr.creationTime());
2876            return true;
2877        } catch (IOException ignored) {
2878            // Fallback: Only set modified time to match source file
2879            return targetFile.setLastModified(sourceFile.lastModified());
2880        }
2881
2882        // TODO: (Help!) Determine historically why setLastModified(File, File) needed PathUtils.setLastModifiedTime() if
2883        //  sourceFile.isFile() was true, but needed setLastModifiedTime(File, long) if sourceFile.isFile() was false
2884    }
2885
2886    /**
2887     * Returns the size of the specified file or directory. If the provided
2888     * {@link File} is a regular file, then the file's length is returned.
2889     * If the argument is a directory, then the size of the directory is
2890     * calculated recursively. If a directory or subdirectory is security
2891     * restricted, its size will not be included.
2892     * <p>
2893     * Note that overflow is not detected, and the return value may be negative if
2894     * overflow occurs. See {@link #sizeOfAsBigInteger(File)} for an alternative
2895     * method that does not overflow.
2896     * </p>
2897     *
2898     * @param file the regular file or directory to return the size
2899     *             of (must not be {@code null}).
2900     *
2901     * @return the length of the file, or recursive size of the directory,
2902     * provided (in bytes).
2903     *
2904     * @throws NullPointerException     if the file is {@code null}.
2905     * @throws IllegalArgumentException if the file does not exist.
2906     * @throws UncheckedIOException if an IO error occurs.
2907     * @since 2.0
2908     */
2909    public static long sizeOf(final File file) {
2910        requireExists(file, "file");
2911        return Uncheck.get(() -> PathUtils.sizeOf(file.toPath()));
2912    }
2913
2914    /**
2915     * Returns the size of the specified file or directory. If the provided
2916     * {@link File} is a regular file, then the file's length is returned.
2917     * If the argument is a directory, then the size of the directory is
2918     * calculated recursively. If a directory or subdirectory is security
2919     * restricted, its size will not be included.
2920     *
2921     * @param file the regular file or directory to return the size
2922     *             of (must not be {@code null}).
2923     *
2924     * @return the length of the file, or recursive size of the directory,
2925     * provided (in bytes).
2926     *
2927     * @throws NullPointerException     if the file is {@code null}.
2928     * @throws IllegalArgumentException if the file does not exist.
2929     * @throws UncheckedIOException if an IO error occurs.
2930     * @since 2.4
2931     */
2932    public static BigInteger sizeOfAsBigInteger(final File file) {
2933        requireExists(file, "file");
2934        return Uncheck.get(() -> PathUtils.sizeOfAsBigInteger(file.toPath()));
2935    }
2936
2937    /**
2938     * Counts the size of a directory recursively (sum of the length of all files).
2939     * <p>
2940     * Note that overflow is not detected, and the return value may be negative if
2941     * overflow occurs. See {@link #sizeOfDirectoryAsBigInteger(File)} for an alternative
2942     * method that does not overflow.
2943     * </p>
2944     *
2945     * @param directory directory to inspect, must not be {@code null}.
2946     * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total
2947     * is greater than {@link Long#MAX_VALUE}.
2948     * @throws NullPointerException if the directory is {@code null}.
2949     * @throws UncheckedIOException if an IO error occurs.
2950     */
2951    public static long sizeOfDirectory(final File directory) {
2952        requireDirectoryExists(directory, "directory");
2953        return Uncheck.get(() -> PathUtils.sizeOfDirectory(directory.toPath()));
2954    }
2955
2956    /**
2957     * Counts the size of a directory recursively (sum of the length of all files).
2958     *
2959     * @param directory directory to inspect, must not be {@code null}.
2960     * @return size of directory in bytes, 0 if directory is security restricted.
2961     * @throws NullPointerException if the directory is {@code null}.
2962     * @throws UncheckedIOException if an IO error occurs.
2963     * @since 2.4
2964     */
2965    public static BigInteger sizeOfDirectoryAsBigInteger(final File directory) {
2966        requireDirectoryExists(directory, "directory");
2967        return Uncheck.get(() -> PathUtils.sizeOfDirectoryAsBigInteger(directory.toPath()));
2968    }
2969
2970    /**
2971     * Streams over the files in a given directory (and optionally
2972     * its subdirectories) which match an array of extensions.
2973     *
2974     * @param directory  the directory to search in
2975     * @param recursive  if true all subdirectories are searched as well
2976     * @param extensions an array of extensions, ex. {"java","xml"}. If this
2977     *                   parameter is {@code null}, all files are returned.
2978     * @return an iterator of java.io.File with the matching files
2979     * @throws IOException if an I/O error is thrown when accessing the starting file.
2980     * @since 2.9.0
2981     */
2982    public static Stream<File> streamFiles(final File directory, final boolean recursive, final String... extensions) throws IOException {
2983        // @formatter:off
2984        final IOFileFilter filter = extensions == null
2985            ? FileFileFilter.INSTANCE
2986            : FileFileFilter.INSTANCE.and(new SuffixFileFilter(toSuffixes(extensions)));
2987        // @formatter:on
2988        return PathUtils.walk(directory.toPath(), filter, toMaxDepth(recursive), false, FileVisitOption.FOLLOW_LINKS).map(Path::toFile);
2989    }
2990
2991    /**
2992     * Converts from a {@link URL} to a {@link File}.
2993     * <p>
2994     * From version 1.1 this method will decode the URL.
2995     * Syntax such as {@code file:///my%20docs/file.txt} will be
2996     * correctly decoded to {@code /my docs/file.txt}. Starting with version
2997     * 1.5, this method uses UTF-8 to decode percent-encoded octets to characters.
2998     * Additionally, malformed percent-encoded octets are handled leniently by
2999     * passing them through literally.
3000     * </p>
3001     *
3002     * @param url the file URL to convert, {@code null} returns {@code null}
3003     * @return the equivalent {@link File} object, or {@code null}
3004     * if the URL's protocol is not {@code file}
3005     */
3006    public static File toFile(final URL url) {
3007        if (url == null || !"file".equalsIgnoreCase(url.getProtocol())) {
3008            return null;
3009        }
3010        final String filename = url.getFile().replace('/', File.separatorChar);
3011        return new File(decodeUrl(filename));
3012    }
3013
3014    /**
3015     * Converts each of an array of {@link URL} to a {@link File}.
3016     * <p>
3017     * Returns an array of the same size as the input.
3018     * If the input is {@code null}, an empty array is returned.
3019     * If the input contains {@code null}, the output array contains {@code null} at the same
3020     * index.
3021     * </p>
3022     * <p>
3023     * This method will decode the URL.
3024     * Syntax such as {@code file:///my%20docs/file.txt} will be
3025     * correctly decoded to {@code /my docs/file.txt}.
3026     * </p>
3027     *
3028     * @param urls the file URLs to convert, {@code null} returns empty array
3029     * @return a non-{@code null} array of Files matching the input, with a {@code null} item
3030     * if there was a {@code null} at that index in the input array
3031     * @throws IllegalArgumentException if any file is not a URL file
3032     * @throws IllegalArgumentException if any file is incorrectly encoded
3033     * @since 1.1
3034     */
3035    public static File[] toFiles(final URL... urls) {
3036        if (IOUtils.length(urls) == 0) {
3037            return EMPTY_FILE_ARRAY;
3038        }
3039        final File[] files = new File[urls.length];
3040        for (int i = 0; i < urls.length; i++) {
3041            final URL url = urls[i];
3042            if (url != null) {
3043                if (!"file".equalsIgnoreCase(url.getProtocol())) {
3044                    throw new IllegalArgumentException("Can only convert file URL to a File: " + url);
3045                }
3046                files[i] = toFile(url);
3047            }
3048        }
3049        return files;
3050    }
3051
3052    private static List<File> toList(final Stream<File> stream) {
3053        return stream.collect(Collectors.toList());
3054    }
3055
3056    /**
3057     * Converts whether or not to recurse into a recursion max depth.
3058     *
3059     * @param recursive whether or not to recurse
3060     * @return the recursion depth
3061     */
3062    private static int toMaxDepth(final boolean recursive) {
3063        return recursive ? Integer.MAX_VALUE : 1;
3064    }
3065
3066    /**
3067     * Converts an array of file extensions to suffixes.
3068     *
3069     * @param extensions an array of extensions. Format: {"java", "xml"}
3070     * @return an array of suffixes. Format: {".java", ".xml"}
3071     * @throws NullPointerException if the parameter is null
3072     */
3073    private static String[] toSuffixes(final String... extensions) {
3074        return Stream.of(Objects.requireNonNull(extensions, "extensions")).map(e -> "." + e).toArray(String[]::new);
3075    }
3076
3077    /**
3078     * Implements behavior similar to the Unix "touch" utility. Creates a new file with size 0, or, if the file exists, just
3079     * updates the file's modified time.
3080     * <p>
3081     * NOTE: As from v1.3, this method throws an IOException if the last modified date of the file cannot be set. Also, as
3082     * from v1.3 this method creates parent directories if they do not exist.
3083     * </p>
3084     *
3085     * @param file the File to touch.
3086     * @throws NullPointerException if the parameter is {@code null}.
3087     * @throws IOException if setting the last-modified time failed or an I/O problem occurs.
3088     */
3089    public static void touch(final File file) throws IOException {
3090        PathUtils.touch(Objects.requireNonNull(file, "file").toPath());
3091    }
3092
3093    /**
3094     * Converts each of an array of {@link File} to a {@link URL}.
3095     * <p>
3096     * Returns an array of the same size as the input.
3097     * </p>
3098     *
3099     * @param files the files to convert, must not be {@code null}
3100     * @return an array of URLs matching the input
3101     * @throws IOException          if a file cannot be converted
3102     * @throws NullPointerException if the parameter is null
3103     */
3104    public static URL[] toURLs(final File... files) throws IOException {
3105        Objects.requireNonNull(files, "files");
3106        final URL[] urls = new URL[files.length];
3107        for (int i = 0; i < urls.length; i++) {
3108            urls[i] = files[i].toURI().toURL();
3109        }
3110        return urls;
3111    }
3112
3113    /**
3114     * Validates the given arguments.
3115     * <ul>
3116     * <li>Throws {@link NullPointerException} if {@code source} is null</li>
3117     * <li>Throws {@link NullPointerException} if {@code destination} is null</li>
3118     * <li>Throws {@link FileNotFoundException} if {@code source} does not exist</li>
3119     * </ul>
3120     *
3121     * @param source      the file or directory to be moved.
3122     * @param destination the destination file or directory.
3123     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
3124     * @throws FileNotFoundException if the source file does not exist.
3125     */
3126    private static void validateMoveParameters(final File source, final File destination) throws FileNotFoundException {
3127        Objects.requireNonNull(source, "source");
3128        Objects.requireNonNull(destination, "destination");
3129        if (!source.exists()) {
3130            throw new FileNotFoundException("Source '" + source + "' does not exist");
3131        }
3132    }
3133
3134    /**
3135     * Waits for the file system to propagate a file creation, with a timeout.
3136     * <p>
3137     * This method repeatedly tests {@link Files#exists(Path, LinkOption...)} until it returns
3138     * true up to the maximum time specified in seconds.
3139     * </p>
3140     *
3141     * @param file    the file to check, must not be {@code null}
3142     * @param seconds the maximum time in seconds to wait
3143     * @return true if file exists
3144     * @throws NullPointerException if the file is {@code null}
3145     */
3146    public static boolean waitFor(final File file, final int seconds) {
3147        Objects.requireNonNull(file, "file");
3148        return PathUtils.waitFor(file.toPath(), Duration.ofSeconds(seconds), PathUtils.EMPTY_LINK_OPTION_ARRAY);
3149    }
3150
3151    /**
3152     * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM.
3153     *
3154     * @param file the file to write
3155     * @param data the content to write to the file
3156     * @throws IOException in case of an I/O error
3157     * @since 2.0
3158     * @deprecated 2.5 use {@link #write(File, CharSequence, Charset)} instead (and specify the appropriate encoding)
3159     */
3160    @Deprecated
3161    public static void write(final File file, final CharSequence data) throws IOException {
3162        write(file, data, Charset.defaultCharset(), false);
3163    }
3164
3165    /**
3166     * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM.
3167     *
3168     * @param file   the file to write
3169     * @param data   the content to write to the file
3170     * @param append if {@code true}, then the data will be added to the
3171     *               end of the file rather than overwriting
3172     * @throws IOException in case of an I/O error
3173     * @since 2.1
3174     * @deprecated 2.5 use {@link #write(File, CharSequence, Charset, boolean)} instead (and specify the appropriate encoding)
3175     */
3176    @Deprecated
3177    public static void write(final File file, final CharSequence data, final boolean append) throws IOException {
3178        write(file, data, Charset.defaultCharset(), append);
3179    }
3180
3181    /**
3182     * Writes a CharSequence to a file creating the file if it does not exist.
3183     *
3184     * @param file     the file to write
3185     * @param data     the content to write to the file
3186     * @param charset the name of the requested charset, {@code null} means platform default
3187     * @throws IOException in case of an I/O error
3188     * @since 2.3
3189     */
3190    public static void write(final File file, final CharSequence data, final Charset charset) throws IOException {
3191        write(file, data, charset, false);
3192    }
3193
3194    /**
3195     * Writes a CharSequence to a file creating the file if it does not exist.
3196     *
3197     * @param file     the file to write
3198     * @param data     the content to write to the file
3199     * @param charset the charset to use, {@code null} means platform default
3200     * @param append   if {@code true}, then the data will be added to the
3201     *                 end of the file rather than overwriting
3202     * @throws IOException in case of an I/O error
3203     * @since 2.3
3204     */
3205    public static void write(final File file, final CharSequence data, final Charset charset, final boolean append) throws IOException {
3206        writeStringToFile(file, Objects.toString(data, null), charset, append);
3207    }
3208
3209    /**
3210     * Writes a CharSequence to a file creating the file if it does not exist.
3211     *
3212     * @param file     the file to write
3213     * @param data     the content to write to the file
3214     * @param charsetName the name of the requested charset, {@code null} means platform default
3215     * @throws IOException                          in case of an I/O error
3216     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3217     * @since 2.0
3218     */
3219    public static void write(final File file, final CharSequence data, final String charsetName) throws IOException {
3220        write(file, data, charsetName, false);
3221    }
3222
3223    /**
3224     * Writes a CharSequence to a file creating the file if it does not exist.
3225     *
3226     * @param file     the file to write
3227     * @param data     the content to write to the file
3228     * @param charsetName the name of the requested charset, {@code null} means platform default
3229     * @param append   if {@code true}, then the data will be added to the
3230     *                 end of the file rather than overwriting
3231     * @throws IOException                 in case of an I/O error
3232     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io
3233     * .UnsupportedEncodingException} in version 2.2 if the encoding is not supported by the VM
3234     * @since 2.1
3235     */
3236    public static void write(final File file, final CharSequence data, final String charsetName, final boolean append) throws IOException {
3237        write(file, data, Charsets.toCharset(charsetName), append);
3238    }
3239
3240    // Must be called with a directory
3241
3242    /**
3243     * Writes a byte array to a file creating the file if it does not exist.
3244     * <p>
3245     * NOTE: As from v1.3, the parent directories of the file will be created
3246     * if they do not exist.
3247     * </p>
3248     *
3249     * @param file the file to write to
3250     * @param data the content to write to the file
3251     * @throws IOException in case of an I/O error
3252     * @since 1.1
3253     */
3254    public static void writeByteArrayToFile(final File file, final byte[] data) throws IOException {
3255        writeByteArrayToFile(file, data, false);
3256    }
3257
3258    /**
3259     * Writes a byte array to a file creating the file if it does not exist.
3260     *
3261     * @param file   the file to write to
3262     * @param data   the content to write to the file
3263     * @param append if {@code true}, then bytes will be added to the
3264     *               end of the file rather than overwriting
3265     * @throws IOException in case of an I/O error
3266     * @since 2.1
3267     */
3268    public static void writeByteArrayToFile(final File file, final byte[] data, final boolean append) throws IOException {
3269        writeByteArrayToFile(file, data, 0, data.length, append);
3270    }
3271
3272    /**
3273     * Writes {@code len} bytes from the specified byte array starting
3274     * at offset {@code off} to a file, creating the file if it does
3275     * not exist.
3276     *
3277     * @param file the file to write to
3278     * @param data the content to write to the file
3279     * @param off  the start offset in the data
3280     * @param len  the number of bytes to write
3281     * @throws IOException in case of an I/O error
3282     * @since 2.5
3283     */
3284    public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len) throws IOException {
3285        writeByteArrayToFile(file, data, off, len, false);
3286    }
3287
3288    /**
3289     * Writes {@code len} bytes from the specified byte array starting
3290     * at offset {@code off} to a file, creating the file if it does
3291     * not exist.
3292     *
3293     * @param file   the file to write to
3294     * @param data   the content to write to the file
3295     * @param off    the start offset in the data
3296     * @param len    the number of bytes to write
3297     * @param append if {@code true}, then bytes will be added to the
3298     *               end of the file rather than overwriting
3299     * @throws IOException in case of an I/O error
3300     * @since 2.5
3301     */
3302    public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len, final boolean append) throws IOException {
3303        try (OutputStream out = newOutputStream(file, append)) {
3304            out.write(data, off, len);
3305        }
3306    }
3307
3308    /**
3309     * Writes the {@code toString()} value of each item in a collection to
3310     * the specified {@link File} line by line.
3311     * The default VM encoding and the default line ending will be used.
3312     *
3313     * @param file  the file to write to
3314     * @param lines the lines to write, {@code null} entries produce blank lines
3315     * @throws IOException in case of an I/O error
3316     * @since 1.3
3317     */
3318    public static void writeLines(final File file, final Collection<?> lines) throws IOException {
3319        writeLines(file, null, lines, null, false);
3320    }
3321
3322    /**
3323     * Writes the {@code toString()} value of each item in a collection to
3324     * the specified {@link File} line by line.
3325     * The default VM encoding and the default line ending will be used.
3326     *
3327     * @param file   the file to write to
3328     * @param lines  the lines to write, {@code null} entries produce blank lines
3329     * @param append if {@code true}, then the lines will be added to the
3330     *               end of the file rather than overwriting
3331     * @throws IOException in case of an I/O error
3332     * @since 2.1
3333     */
3334    public static void writeLines(final File file, final Collection<?> lines, final boolean append) throws IOException {
3335        writeLines(file, null, lines, null, append);
3336    }
3337
3338    /**
3339     * Writes the {@code toString()} value of each item in a collection to
3340     * the specified {@link File} line by line.
3341     * The default VM encoding and the specified line ending will be used.
3342     *
3343     * @param file       the file to write to
3344     * @param lines      the lines to write, {@code null} entries produce blank lines
3345     * @param lineEnding the line separator to use, {@code null} is system default
3346     * @throws IOException in case of an I/O error
3347     * @since 1.3
3348     */
3349    public static void writeLines(final File file, final Collection<?> lines, final String lineEnding) throws IOException {
3350        writeLines(file, null, lines, lineEnding, false);
3351    }
3352
3353    /**
3354     * Writes the {@code toString()} value of each item in a collection to
3355     * the specified {@link File} line by line.
3356     * The default VM encoding and the specified line ending will be used.
3357     *
3358     * @param file       the file to write to
3359     * @param lines      the lines to write, {@code null} entries produce blank lines
3360     * @param lineEnding the line separator to use, {@code null} is system default
3361     * @param append     if {@code true}, then the lines will be added to the
3362     *                   end of the file rather than overwriting
3363     * @throws IOException in case of an I/O error
3364     * @since 2.1
3365     */
3366    public static void writeLines(final File file, final Collection<?> lines, final String lineEnding, final boolean append) throws IOException {
3367        writeLines(file, null, lines, lineEnding, append);
3368    }
3369
3370    /**
3371     * Writes the {@code toString()} value of each item in a collection to
3372     * the specified {@link File} line by line.
3373     * The specified character encoding and the default line ending will be used.
3374     * <p>
3375     * NOTE: As from v1.3, the parent directories of the file will be created
3376     * if they do not exist.
3377     * </p>
3378     *
3379     * @param file     the file to write to
3380     * @param charsetName the name of the requested charset, {@code null} means platform default
3381     * @param lines    the lines to write, {@code null} entries produce blank lines
3382     * @throws IOException                          in case of an I/O error
3383     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3384     * @since 1.1
3385     */
3386    public static void writeLines(final File file, final String charsetName, final Collection<?> lines) throws IOException {
3387        writeLines(file, charsetName, lines, null, false);
3388    }
3389
3390    /**
3391     * Writes the {@code toString()} value of each item in a collection to
3392     * the specified {@link File} line by line, optionally appending.
3393     * The specified character encoding and the default line ending will be used.
3394     *
3395     * @param file     the file to write to
3396     * @param charsetName the name of the requested charset, {@code null} means platform default
3397     * @param lines    the lines to write, {@code null} entries produce blank lines
3398     * @param append   if {@code true}, then the lines will be added to the
3399     *                 end of the file rather than overwriting
3400     * @throws IOException                          in case of an I/O error
3401     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3402     * @since 2.1
3403     */
3404    public static void writeLines(final File file, final String charsetName, final Collection<?> lines, final boolean append) throws IOException {
3405        writeLines(file, charsetName, lines, null, append);
3406    }
3407
3408    /**
3409     * Writes the {@code toString()} value of each item in a collection to
3410     * the specified {@link File} line by line.
3411     * The specified character encoding and the line ending will be used.
3412     * <p>
3413     * NOTE: As from v1.3, the parent directories of the file will be created
3414     * if they do not exist.
3415     * </p>
3416     *
3417     * @param file       the file to write to
3418     * @param charsetName   the name of the requested charset, {@code null} means platform default
3419     * @param lines      the lines to write, {@code null} entries produce blank lines
3420     * @param lineEnding the line separator to use, {@code null} is system default
3421     * @throws IOException                          in case of an I/O error
3422     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3423     * @since 1.1
3424     */
3425    public static void writeLines(final File file, final String charsetName, final Collection<?> lines, final String lineEnding) throws IOException {
3426        writeLines(file, charsetName, lines, lineEnding, false);
3427    }
3428
3429    /**
3430     * Writes the {@code toString()} value of each item in a collection to
3431     * the specified {@link File} line by line.
3432     * The specified character encoding and the line ending will be used.
3433     *
3434     * @param file       the file to write to
3435     * @param charsetName   the name of the requested charset, {@code null} means platform default
3436     * @param lines      the lines to write, {@code null} entries produce blank lines
3437     * @param lineEnding the line separator to use, {@code null} is system default
3438     * @param append     if {@code true}, then the lines will be added to the
3439     *                   end of the file rather than overwriting
3440     * @throws IOException                          in case of an I/O error
3441     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3442     * @since 2.1
3443     */
3444    public static void writeLines(final File file, final String charsetName, final Collection<?> lines, final String lineEnding, final boolean append)
3445        throws IOException {
3446        try (OutputStream out = new BufferedOutputStream(newOutputStream(file, append))) {
3447            IOUtils.writeLines(lines, lineEnding, out, charsetName);
3448        }
3449    }
3450
3451    /**
3452     * Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
3453     *
3454     * @param file the file to write
3455     * @param data the content to write to the file
3456     * @throws IOException in case of an I/O error
3457     * @deprecated 2.5 use {@link #writeStringToFile(File, String, Charset)} instead (and specify the appropriate encoding)
3458     */
3459    @Deprecated
3460    public static void writeStringToFile(final File file, final String data) throws IOException {
3461        writeStringToFile(file, data, Charset.defaultCharset(), false);
3462    }
3463
3464    /**
3465     * Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
3466     *
3467     * @param file   the file to write
3468     * @param data   the content to write to the file
3469     * @param append if {@code true}, then the String will be added to the
3470     *               end of the file rather than overwriting
3471     * @throws IOException in case of an I/O error
3472     * @since 2.1
3473     * @deprecated 2.5 use {@link #writeStringToFile(File, String, Charset, boolean)} instead (and specify the appropriate encoding)
3474     */
3475    @Deprecated
3476    public static void writeStringToFile(final File file, final String data, final boolean append) throws IOException {
3477        writeStringToFile(file, data, Charset.defaultCharset(), append);
3478    }
3479
3480    /**
3481     * Writes a String to a file creating the file if it does not exist.
3482     * <p>
3483     * NOTE: As from v1.3, the parent directories of the file will be created
3484     * if they do not exist.
3485     * </p>
3486     *
3487     * @param file     the file to write
3488     * @param data     the content to write to the file
3489     * @param charset the charset to use, {@code null} means platform default
3490     * @throws IOException                          in case of an I/O error
3491     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3492     * @since 2.4
3493     */
3494    public static void writeStringToFile(final File file, final String data, final Charset charset) throws IOException {
3495        writeStringToFile(file, data, charset, false);
3496    }
3497
3498    /**
3499     * Writes a String to a file creating the file if it does not exist.
3500     *
3501     * @param file     the file to write
3502     * @param data     the content to write to the file
3503     * @param charset the charset to use, {@code null} means platform default
3504     * @param append   if {@code true}, then the String will be added to the
3505     *                 end of the file rather than overwriting
3506     * @throws IOException in case of an I/O error
3507     * @since 2.3
3508     */
3509    public static void writeStringToFile(final File file, final String data, final Charset charset, final boolean append) throws IOException {
3510        try (OutputStream out = newOutputStream(file, append)) {
3511            IOUtils.write(data, out, charset);
3512        }
3513    }
3514
3515    /**
3516     * Writes a String to a file creating the file if it does not exist.
3517     * <p>
3518     * NOTE: As from v1.3, the parent directories of the file will be created
3519     * if they do not exist.
3520     * </p>
3521     *
3522     * @param file     the file to write
3523     * @param data     the content to write to the file
3524     * @param charsetName the name of the requested charset, {@code null} means platform default
3525     * @throws IOException                          in case of an I/O error
3526     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3527     */
3528    public static void writeStringToFile(final File file, final String data, final String charsetName) throws IOException {
3529        writeStringToFile(file, data, charsetName, false);
3530    }
3531
3532    /**
3533     * Writes a String to a file creating the file if it does not exist.
3534     *
3535     * @param file     the file to write
3536     * @param data     the content to write to the file
3537     * @param charsetName the name of the requested charset, {@code null} means platform default
3538     * @param append   if {@code true}, then the String will be added to the
3539     *                 end of the file rather than overwriting
3540     * @throws IOException                 in case of an I/O error
3541     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io
3542     * .UnsupportedEncodingException} in version 2.2 if the encoding is not supported by the VM
3543     * @since 2.1
3544     */
3545    public static void writeStringToFile(final File file, final String data, final String charsetName, final boolean append) throws IOException {
3546        writeStringToFile(file, data, Charsets.toCharset(charsetName), append);
3547    }
3548
3549    /**
3550     * Instances should NOT be constructed in standard programming.
3551     * @deprecated Will be private in 3.0.
3552     */
3553    @Deprecated
3554    public FileUtils() { //NOSONAR
3555
3556    }
3557
3558}