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 non-existent parent directories. If a parent directory already exists or
1092     * is 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 non-existent 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 non-existent 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     * Creates all directories for a File object, including any necessary but non-existent parent directories. If the parent directory already exists or is
1385     * null, nothing happens.
1386     * <p>
1387     * Calls {@link File#mkdirs()} for the parent of @{code file}.
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 SecurityException    See {@link File#mkdirs()}.
1394     * @see File#mkdirs()
1395     * @since 2.5
1396     */
1397    public static void forceMkdirParent(final File file) throws IOException {
1398        forceMkdir(getParentFile(Objects.requireNonNull(file, "file")));
1399    }
1400
1401    /**
1402     * Constructs a file from the set of name elements.
1403     *
1404     * @param directory the parent directory.
1405     * @param names the name elements.
1406     * @return the new file.
1407     * @since 2.1
1408     */
1409    public static File getFile(final File directory, final String... names) {
1410        Objects.requireNonNull(directory, "directory");
1411        Objects.requireNonNull(names, "names");
1412        File file = directory;
1413        for (final String name : names) {
1414            file = new File(file, name);
1415        }
1416        return file;
1417    }
1418
1419    /**
1420     * Constructs a file from the set of name elements.
1421     *
1422     * @param names the name elements.
1423     * @return the file.
1424     * @since 2.1
1425     */
1426    public static File getFile(final String... names) {
1427        Objects.requireNonNull(names, "names");
1428        File file = null;
1429        for (final String name : names) {
1430            if (file == null) {
1431                file = new File(name);
1432            } else {
1433                file = new File(file, name);
1434            }
1435        }
1436        return file;
1437    }
1438
1439    /**
1440     * Gets the parent of the given file. The given file may be null. Note that a file's parent may be null as well.
1441     *
1442     * @param file The file to query, may be null.
1443     * @return The parent file or {@code null}. Note that a file's parent may be null as well.
1444     */
1445    private static File getParentFile(final File file) {
1446        return file == null ? null : file.getParentFile();
1447    }
1448
1449    /**
1450     * Returns a {@link File} representing the system temporary directory.
1451     *
1452     * @return the system temporary directory.
1453     * @since 2.0
1454     */
1455    public static File getTempDirectory() {
1456        return new File(getTempDirectoryPath());
1457    }
1458
1459    /**
1460     * Returns the path to the system temporary directory.
1461     *
1462     * @return the path to the system temporary directory.
1463     * @since 2.0
1464     */
1465    public static String getTempDirectoryPath() {
1466        return System.getProperty("java.io.tmpdir");
1467    }
1468
1469    /**
1470     * Returns a {@link File} representing the user's home directory.
1471     *
1472     * @return the user's home directory.
1473     * @since 2.0
1474     */
1475    public static File getUserDirectory() {
1476        return new File(getUserDirectoryPath());
1477    }
1478
1479    /**
1480     * Returns the path to the user's home directory.
1481     *
1482     * @return the path to the user's home directory.
1483     * @since 2.0
1484     */
1485    public static String getUserDirectoryPath() {
1486        return System.getProperty("user.home");
1487    }
1488
1489    /**
1490     * Tests whether the specified {@link File} is a directory or not. Implemented as a
1491     * null-safe delegate to {@link Files#isDirectory(Path path, LinkOption... options)}.
1492     *
1493     * @param   file the path to the file.
1494     * @param   options options indicating how symbolic links are handled
1495     * @return  {@code true} if the file is a directory; {@code false} if
1496     *          the path is null, the file does not exist, is not a directory, or it cannot
1497     *          be determined if the file is a directory or not.
1498     * @throws SecurityException     In the case of the default provider, and a security manager is installed, the
1499     *                               {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read
1500     *                               access to the directory.
1501     * @since 2.9.0
1502     */
1503    public static boolean isDirectory(final File file, final LinkOption... options) {
1504        return file != null && Files.isDirectory(file.toPath(), options);
1505    }
1506
1507    /**
1508     * Tests whether the directory is empty.
1509     *
1510     * @param directory the directory to query.
1511     * @return whether the directory is empty.
1512     * @throws IOException if an I/O error occurs.
1513     * @throws NotDirectoryException if the file could not otherwise be opened because it is not a directory
1514     *                               <i>(optional specific exception)</i>.
1515     * @since 2.9.0
1516     */
1517    public static boolean isEmptyDirectory(final File directory) throws IOException {
1518        return PathUtils.isEmptyDirectory(directory.toPath());
1519    }
1520
1521    /**
1522     * Tests if the specified {@link File} is newer than the specified {@link ChronoLocalDate}
1523     * at the end of day.
1524     *
1525     * <p>Note: The input date is assumed to be in the system default time-zone with the time
1526     * part set to the current time. To use a non-default time-zone use the method
1527     * {@link #isFileNewer(File, ChronoLocalDateTime, ZoneId)
1528     * isFileNewer(file, chronoLocalDate.atTime(LocalTime.now(zoneId)), zoneId)} where
1529     * {@code zoneId} is a valid {@link ZoneId}.
1530     *
1531     * @param file            the {@link File} of which the modification date must be compared.
1532     * @param chronoLocalDate the date reference.
1533     * @return true if the {@link File} exists and has been modified after the given
1534     * {@link ChronoLocalDate} at the current time.
1535     * @throws NullPointerException if the file or local date is {@code null}.
1536     * @since 2.8.0
1537     */
1538    public static boolean isFileNewer(final File file, final ChronoLocalDate chronoLocalDate) {
1539        return isFileNewer(file, chronoLocalDate, LocalTime.MAX);
1540    }
1541
1542    /**
1543     * Tests if the specified {@link File} is newer than the specified {@link ChronoLocalDate}
1544     * at the specified time.
1545     *
1546     * <p>Note: The input date and time are assumed to be in the system default time-zone. To use a
1547     * non-default time-zone use the method {@link #isFileNewer(File, ChronoLocalDateTime, ZoneId)
1548     * isFileNewer(file, chronoLocalDate.atTime(localTime), zoneId)} where {@code zoneId} is a valid
1549     * {@link ZoneId}.
1550     *
1551     * @param file            the {@link File} of which the modification date must be compared.
1552     * @param chronoLocalDate the date reference.
1553     * @param localTime       the time reference.
1554     * @return true if the {@link File} exists and has been modified after the given
1555     * {@link ChronoLocalDate} at the given time.
1556     * @throws NullPointerException if the file, local date or zone ID is {@code null}.
1557     * @since 2.8.0
1558     */
1559    public static boolean isFileNewer(final File file, final ChronoLocalDate chronoLocalDate, final LocalTime localTime) {
1560        Objects.requireNonNull(chronoLocalDate, "chronoLocalDate");
1561        Objects.requireNonNull(localTime, "localTime");
1562        return isFileNewer(file, chronoLocalDate.atTime(localTime));
1563    }
1564
1565    /**
1566     * Tests if the specified {@link File} is newer than the specified {@link ChronoLocalDate} at the specified
1567     * {@link OffsetTime}.
1568     *
1569     * @param file the {@link File} of which the modification date must be compared
1570     * @param chronoLocalDate the date reference
1571     * @param offsetTime the time reference
1572     * @return true if the {@link File} exists and has been modified after the given {@link ChronoLocalDate} at the given
1573     *         {@link OffsetTime}.
1574     * @throws NullPointerException if the file, local date or zone ID is {@code null}
1575     * @since 2.12.0
1576     */
1577    public static boolean isFileNewer(final File file, final ChronoLocalDate chronoLocalDate, final OffsetTime offsetTime) {
1578        Objects.requireNonNull(chronoLocalDate, "chronoLocalDate");
1579        Objects.requireNonNull(offsetTime, "offsetTime");
1580        return isFileNewer(file, chronoLocalDate.atTime(offsetTime.toLocalTime()));
1581    }
1582
1583    /**
1584     * Tests if the specified {@link File} is newer than the specified {@link ChronoLocalDateTime}
1585     * at the system-default time zone.
1586     *
1587     * <p>Note: The input date and time is assumed to be in the system default time-zone. To use a
1588     * non-default time-zone use the method {@link #isFileNewer(File, ChronoLocalDateTime, ZoneId)
1589     * isFileNewer(file, chronoLocalDateTime, zoneId)} where {@code zoneId} is a valid
1590     * {@link ZoneId}.
1591     *
1592     * @param file                the {@link File} of which the modification date must be compared.
1593     * @param chronoLocalDateTime the date reference.
1594     * @return true if the {@link File} exists and has been modified after the given
1595     * {@link ChronoLocalDateTime} at the system-default time zone.
1596     * @throws NullPointerException if the file or local date time is {@code null}.
1597     * @since 2.8.0
1598     */
1599    public static boolean isFileNewer(final File file, final ChronoLocalDateTime<?> chronoLocalDateTime) {
1600        return isFileNewer(file, chronoLocalDateTime, ZoneId.systemDefault());
1601    }
1602
1603    /**
1604     * Tests if the specified {@link File} is newer than the specified {@link ChronoLocalDateTime}
1605     * at the specified {@link ZoneId}.
1606     *
1607     * @param file                the {@link File} of which the modification date must be compared.
1608     * @param chronoLocalDateTime the date reference.
1609     * @param zoneId              the time zone.
1610     * @return true if the {@link File} exists and has been modified after the given
1611     * {@link ChronoLocalDateTime} at the given {@link ZoneId}.
1612     * @throws NullPointerException if the file, local date time or zone ID is {@code null}.
1613     * @since 2.8.0
1614     */
1615    public static boolean isFileNewer(final File file, final ChronoLocalDateTime<?> chronoLocalDateTime, final ZoneId zoneId) {
1616        Objects.requireNonNull(chronoLocalDateTime, "chronoLocalDateTime");
1617        Objects.requireNonNull(zoneId, "zoneId");
1618        return isFileNewer(file, chronoLocalDateTime.atZone(zoneId));
1619    }
1620
1621    /**
1622     * Tests if the specified {@link File} is newer than the specified {@link ChronoZonedDateTime}.
1623     *
1624     * @param file                the {@link File} of which the modification date must be compared.
1625     * @param chronoZonedDateTime the date reference.
1626     * @return true if the {@link File} exists and has been modified after the given
1627     * {@link ChronoZonedDateTime}.
1628     * @throws NullPointerException if the file or zoned date time is {@code null}.
1629     * @since 2.8.0
1630     */
1631    public static boolean isFileNewer(final File file, final ChronoZonedDateTime<?> chronoZonedDateTime) {
1632        Objects.requireNonNull(file, "file");
1633        Objects.requireNonNull(chronoZonedDateTime, "chronoZonedDateTime");
1634        return Uncheck.get(() -> PathUtils.isNewer(file.toPath(), chronoZonedDateTime));
1635    }
1636
1637    /**
1638     * Tests if the specified {@link File} is newer than the specified {@link Date}.
1639     *
1640     * @param file the {@link File} of which the modification date must be compared.
1641     * @param date the date reference.
1642     * @return true if the {@link File} exists and has been modified
1643     * after the given {@link Date}.
1644     * @throws NullPointerException if the file or date is {@code null}.
1645     */
1646    public static boolean isFileNewer(final File file, final Date date) {
1647        Objects.requireNonNull(date, "date");
1648        return isFileNewer(file, date.getTime());
1649    }
1650
1651    /**
1652     * Tests if the specified {@link File} is newer than the reference {@link File}.
1653     *
1654     * @param file      the {@link File} of which the modification date must be compared.
1655     * @param reference the {@link File} of which the modification date is used.
1656     * @return true if the {@link File} exists and has been modified more
1657     * recently than the reference {@link File}.
1658     * @throws NullPointerException if the file or reference file is {@code null}.
1659     * @throws IllegalArgumentException if the reference file doesn't exist.
1660     */
1661    public static boolean isFileNewer(final File file, final File reference) {
1662        requireExists(reference, "reference");
1663        return Uncheck.get(() -> PathUtils.isNewer(file.toPath(), reference.toPath()));
1664    }
1665
1666    /**
1667     * Tests if the specified {@link File} is newer than the specified {@link FileTime}.
1668     *
1669     * @param file the {@link File} of which the modification date must be compared.
1670     * @param fileTime the file time reference.
1671     * @return true if the {@link File} exists and has been modified after the given {@link FileTime}.
1672     * @throws IOException if an I/O error occurs.
1673     * @throws NullPointerException if the file or local date is {@code null}.
1674     * @since 2.12.0
1675     */
1676    public static boolean isFileNewer(final File file, final FileTime fileTime) throws IOException {
1677        Objects.requireNonNull(file, "file");
1678        return PathUtils.isNewer(file.toPath(), fileTime);
1679    }
1680
1681    /**
1682     * Tests if the specified {@link File} is newer than the specified {@link Instant}.
1683     *
1684     * @param file the {@link File} of which the modification date must be compared.
1685     * @param instant the date reference.
1686     * @return true if the {@link File} exists and has been modified after the given {@link Instant}.
1687     * @throws NullPointerException if the file or instant is {@code null}.
1688     * @since 2.8.0
1689     */
1690    public static boolean isFileNewer(final File file, final Instant instant) {
1691        Objects.requireNonNull(instant, "instant");
1692        return Uncheck.get(() -> PathUtils.isNewer(file.toPath(), instant));
1693    }
1694
1695    /**
1696     * Tests if the specified {@link File} is newer than the specified time reference.
1697     *
1698     * @param file       the {@link File} of which the modification date must be compared.
1699     * @param timeMillis the time reference measured in milliseconds since the
1700     *                   epoch (00:00:00 GMT, January 1, 1970).
1701     * @return true if the {@link File} exists and has been modified after the given time reference.
1702     * @throws NullPointerException if the file is {@code null}.
1703     */
1704    public static boolean isFileNewer(final File file, final long timeMillis) {
1705        Objects.requireNonNull(file, "file");
1706        return Uncheck.get(() -> PathUtils.isNewer(file.toPath(), timeMillis));
1707    }
1708
1709    /**
1710     * Tests if the specified {@link File} is newer than the specified {@link OffsetDateTime}.
1711     *
1712     * @param file the {@link File} of which the modification date must be compared
1713     * @param offsetDateTime the date reference
1714     * @return true if the {@link File} exists and has been modified before the given {@link OffsetDateTime}.
1715     * @throws NullPointerException if the file or zoned date time is {@code null}
1716     * @since 2.12.0
1717     */
1718    public static boolean isFileNewer(final File file, final OffsetDateTime offsetDateTime) {
1719        Objects.requireNonNull(offsetDateTime, "offsetDateTime");
1720        return isFileNewer(file, offsetDateTime.toInstant());
1721    }
1722
1723    /**
1724     * Tests if the specified {@link File} is older than the specified {@link ChronoLocalDate}
1725     * at the end of day.
1726     *
1727     * <p>Note: The input date is assumed to be in the system default time-zone with the time
1728     * part set to the current time. To use a non-default time-zone use the method
1729     * {@link #isFileOlder(File, ChronoLocalDateTime, ZoneId)
1730     * isFileOlder(file, chronoLocalDate.atTime(LocalTime.now(zoneId)), zoneId)} where
1731     * {@code zoneId} is a valid {@link ZoneId}.
1732     *
1733     * @param file            the {@link File} of which the modification date must be compared.
1734     * @param chronoLocalDate the date reference.
1735     * @return true if the {@link File} exists and has been modified before the given
1736     * {@link ChronoLocalDate} at the current time.
1737     * @throws NullPointerException if the file or local date is {@code null}.
1738     * @see ZoneId#systemDefault()
1739     * @see LocalTime#now()
1740     * @since 2.8.0
1741     */
1742    public static boolean isFileOlder(final File file, final ChronoLocalDate chronoLocalDate) {
1743        return isFileOlder(file, chronoLocalDate, LocalTime.MAX);
1744    }
1745
1746    /**
1747     * Tests if the specified {@link File} is older than the specified {@link ChronoLocalDate}
1748     * at the specified {@link LocalTime}.
1749     *
1750     * <p>Note: The input date and time are assumed to be in the system default time-zone. To use a
1751     * non-default time-zone use the method {@link #isFileOlder(File, ChronoLocalDateTime, ZoneId)
1752     * isFileOlder(file, chronoLocalDate.atTime(localTime), zoneId)} where {@code zoneId} is a valid
1753     * {@link ZoneId}.
1754     *
1755     * @param file            the {@link File} of which the modification date must be compared.
1756     * @param chronoLocalDate the date reference.
1757     * @param localTime       the time reference.
1758     * @return true if the {@link File} exists and has been modified before the
1759     * given {@link ChronoLocalDate} at the specified time.
1760     * @throws NullPointerException if the file, local date or local time is {@code null}.
1761     * @see ZoneId#systemDefault()
1762     * @since 2.8.0
1763     */
1764    public static boolean isFileOlder(final File file, final ChronoLocalDate chronoLocalDate, final LocalTime localTime) {
1765        Objects.requireNonNull(chronoLocalDate, "chronoLocalDate");
1766        Objects.requireNonNull(localTime, "localTime");
1767        return isFileOlder(file, chronoLocalDate.atTime(localTime));
1768    }
1769
1770    /**
1771     * Tests if the specified {@link File} is older than the specified {@link ChronoLocalDate} at the specified
1772     * {@link OffsetTime}.
1773     *
1774     * @param file the {@link File} of which the modification date must be compared
1775     * @param chronoLocalDate the date reference
1776     * @param offsetTime the time reference
1777     * @return true if the {@link File} exists and has been modified after the given {@link ChronoLocalDate} at the given
1778     *         {@link OffsetTime}.
1779     * @throws NullPointerException if the file, local date or zone ID is {@code null}
1780     * @since 2.12.0
1781     */
1782    public static boolean isFileOlder(final File file, final ChronoLocalDate chronoLocalDate, final OffsetTime offsetTime) {
1783        Objects.requireNonNull(chronoLocalDate, "chronoLocalDate");
1784        Objects.requireNonNull(offsetTime, "offsetTime");
1785        return isFileOlder(file, chronoLocalDate.atTime(offsetTime.toLocalTime()));
1786    }
1787
1788    /**
1789     * Tests if the specified {@link File} is older than the specified {@link ChronoLocalDateTime}
1790     * at the system-default time zone.
1791     *
1792     * <p>Note: The input date and time is assumed to be in the system default time-zone. To use a
1793     * non-default time-zone use the method {@link #isFileOlder(File, ChronoLocalDateTime, ZoneId)
1794     * isFileOlder(file, chronoLocalDateTime, zoneId)} where {@code zoneId} is a valid
1795     * {@link ZoneId}.
1796     *
1797     * @param file                the {@link File} of which the modification date must be compared.
1798     * @param chronoLocalDateTime the date reference.
1799     * @return true if the {@link File} exists and has been modified before the given
1800     * {@link ChronoLocalDateTime} at the system-default time zone.
1801     * @throws NullPointerException if the file or local date time is {@code null}.
1802     * @see ZoneId#systemDefault()
1803     * @since 2.8.0
1804     */
1805    public static boolean isFileOlder(final File file, final ChronoLocalDateTime<?> chronoLocalDateTime) {
1806        return isFileOlder(file, chronoLocalDateTime, ZoneId.systemDefault());
1807    }
1808
1809    /**
1810     * Tests if the specified {@link File} is older than the specified {@link ChronoLocalDateTime}
1811     * at the specified {@link ZoneId}.
1812     *
1813     * @param file          the {@link File} of which the modification date must be compared.
1814     * @param chronoLocalDateTime the date reference.
1815     * @param zoneId        the time zone.
1816     * @return true if the {@link File} exists and has been modified before the given
1817     * {@link ChronoLocalDateTime} at the given {@link ZoneId}.
1818     * @throws NullPointerException if the file, local date time or zone ID is {@code null}.
1819     * @since 2.8.0
1820     */
1821    public static boolean isFileOlder(final File file, final ChronoLocalDateTime<?> chronoLocalDateTime, final ZoneId zoneId) {
1822        Objects.requireNonNull(chronoLocalDateTime, "chronoLocalDateTime");
1823        Objects.requireNonNull(zoneId, "zoneId");
1824        return isFileOlder(file, chronoLocalDateTime.atZone(zoneId));
1825    }
1826
1827    /**
1828     * Tests if the specified {@link File} is older than the specified {@link ChronoZonedDateTime}.
1829     *
1830     * @param file                the {@link File} of which the modification date must be compared.
1831     * @param chronoZonedDateTime the date reference.
1832     * @return true if the {@link File} exists and has been modified before the given
1833     * {@link ChronoZonedDateTime}.
1834     * @throws NullPointerException if the file or zoned date time is {@code null}.
1835     * @since 2.8.0
1836     */
1837    public static boolean isFileOlder(final File file, final ChronoZonedDateTime<?> chronoZonedDateTime) {
1838        Objects.requireNonNull(chronoZonedDateTime, "chronoZonedDateTime");
1839        return isFileOlder(file, chronoZonedDateTime.toInstant());
1840    }
1841
1842    /**
1843     * Tests if the specified {@link File} is older than the specified {@link Date}.
1844     *
1845     * @param file the {@link File} of which the modification date must be compared.
1846     * @param date the date reference.
1847     * @return true if the {@link File} exists and has been modified before the given {@link Date}.
1848     * @throws NullPointerException if the file or date is {@code null}.
1849     */
1850    public static boolean isFileOlder(final File file, final Date date) {
1851        Objects.requireNonNull(date, "date");
1852        return isFileOlder(file, date.getTime());
1853    }
1854
1855    /**
1856     * Tests if the specified {@link File} is older than the reference {@link File}.
1857     *
1858     * @param file      the {@link File} of which the modification date must be compared.
1859     * @param reference the {@link File} of which the modification date is used.
1860     * @return true if the {@link File} exists and has been modified before the reference {@link File}.
1861     * @throws NullPointerException if the file or reference file is {@code null}.
1862     * @throws IllegalArgumentException if the reference file doesn't exist.
1863     */
1864    public static boolean isFileOlder(final File file, final File reference) {
1865        requireExists(reference, "reference");
1866        return Uncheck.get(() -> PathUtils.isOlder(file.toPath(), reference.toPath()));
1867    }
1868
1869    /**
1870     * Tests if the specified {@link File} is older than the specified {@link FileTime}.
1871     *
1872     * @param file the {@link File} of which the modification date must be compared.
1873     * @param fileTime the file time reference.
1874     * @return true if the {@link File} exists and has been modified before the given {@link FileTime}.
1875     * @throws IOException if an I/O error occurs.
1876     * @throws NullPointerException if the file or local date is {@code null}.
1877     * @since 2.12.0
1878     */
1879    public static boolean isFileOlder(final File file, final FileTime fileTime) throws IOException {
1880        Objects.requireNonNull(file, "file");
1881        return PathUtils.isOlder(file.toPath(), fileTime);
1882    }
1883
1884    /**
1885     * Tests if the specified {@link File} is older than the specified {@link Instant}.
1886     *
1887     * @param file    the {@link File} of which the modification date must be compared.
1888     * @param instant the date reference.
1889     * @return true if the {@link File} exists and has been modified before the given {@link Instant}.
1890     * @throws NullPointerException if the file or instant is {@code null}.
1891     * @since 2.8.0
1892     */
1893    public static boolean isFileOlder(final File file, final Instant instant) {
1894        Objects.requireNonNull(instant, "instant");
1895        return Uncheck.get(() -> PathUtils.isOlder(file.toPath(), instant));
1896    }
1897
1898    /**
1899     * Tests if the specified {@link File} is older than the specified time reference.
1900     *
1901     * @param file       the {@link File} of which the modification date must be compared.
1902     * @param timeMillis the time reference measured in milliseconds since the
1903     *                   epoch (00:00:00 GMT, January 1, 1970).
1904     * @return true if the {@link File} exists and has been modified before the given time reference.
1905     * @throws NullPointerException if the file is {@code null}.
1906     */
1907    public static boolean isFileOlder(final File file, final long timeMillis) {
1908        Objects.requireNonNull(file, "file");
1909        return Uncheck.get(() -> PathUtils.isOlder(file.toPath(), timeMillis));
1910    }
1911
1912    /**
1913     * Tests if the specified {@link File} is older than the specified {@link OffsetDateTime}.
1914     *
1915     * @param file the {@link File} of which the modification date must be compared
1916     * @param offsetDateTime the date reference
1917     * @return true if the {@link File} exists and has been modified before the given {@link OffsetDateTime}.
1918     * @throws NullPointerException if the file or zoned date time is {@code null}
1919     * @since 2.12.0
1920     */
1921    public static boolean isFileOlder(final File file, final OffsetDateTime offsetDateTime) {
1922        Objects.requireNonNull(offsetDateTime, "offsetDateTime");
1923        return isFileOlder(file, offsetDateTime.toInstant());
1924    }
1925
1926    /**
1927     * Tests whether the specified {@link File} is a regular file or not. Implemented as a
1928     * null-safe delegate to {@link Files#isRegularFile(Path path, LinkOption... options)}.
1929     *
1930     * @param   file the path to the file.
1931     * @param   options options indicating how symbolic links are handled
1932     * @return  {@code true} if the file is a regular file; {@code false} if
1933     *          the path is null, the file does not exist, is not a regular file, or it cannot
1934     *          be determined if the file is a regular file or not.
1935     * @throws SecurityException     In the case of the default provider, and a security manager is installed, the
1936     *                               {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read
1937     *                               access to the directory.
1938     * @since 2.9.0
1939     */
1940    public static boolean isRegularFile(final File file, final LinkOption... options) {
1941        return file != null && Files.isRegularFile(file.toPath(), options);
1942    }
1943
1944    /**
1945     * Tests whether the specified file is a symbolic link rather than an actual file.
1946     * <p>
1947     * This method delegates to {@link Files#isSymbolicLink(Path path)}
1948     * </p>
1949     *
1950     * @param file the file to test.
1951     * @return true if the file is a symbolic link, see {@link Files#isSymbolicLink(Path path)}.
1952     * @since 2.0
1953     * @see Files#isSymbolicLink(Path)
1954     */
1955    public static boolean isSymlink(final File file) {
1956        return file != null && Files.isSymbolicLink(file.toPath());
1957    }
1958
1959    /**
1960     * Iterates over the files in given directory (and optionally
1961     * its subdirectories).
1962     * <p>
1963     * The resulting iterator MUST be consumed in its entirety in order to close its underlying stream.
1964     * </p>
1965     * <p>
1966     * All files found are filtered by an IOFileFilter.
1967     * </p>
1968     *
1969     * @param directory  the directory to search in
1970     * @param fileFilter filter to apply when finding files.
1971     * @param dirFilter  optional filter to apply when finding subdirectories.
1972     *                   If this parameter is {@code null}, subdirectories will not be included in the
1973     *                   search. Use TrueFileFilter.INSTANCE to match all directories.
1974     * @return an iterator of java.io.File for the matching files
1975     * @see org.apache.commons.io.filefilter.FileFilterUtils
1976     * @see org.apache.commons.io.filefilter.NameFileFilter
1977     * @since 1.2
1978     */
1979    public static Iterator<File> iterateFiles(final File directory, final IOFileFilter fileFilter, final IOFileFilter dirFilter) {
1980        return listFiles(directory, fileFilter, dirFilter).iterator();
1981    }
1982
1983    /**
1984     * Iterates over the files in a given directory (and optionally
1985     * its subdirectories) which match an array of extensions.
1986     * <p>
1987     * The resulting iterator MUST be consumed in its entirety in order to close its underlying stream.
1988     * </p>
1989     *
1990     * @param directory  the directory to search in
1991     * @param extensions an array of extensions, ex. {"java","xml"}. If this
1992     *                   parameter is {@code null}, all files are returned.
1993     * @param recursive  if true all subdirectories are searched as well
1994     * @return an iterator of java.io.File with the matching files
1995     * @since 1.2
1996     */
1997    public static Iterator<File> iterateFiles(final File directory, final String[] extensions, final boolean recursive) {
1998        return Uncheck.apply(d -> streamFiles(d, recursive, extensions).iterator(), directory);
1999    }
2000
2001    /**
2002     * Iterates over the files in given directory (and optionally
2003     * its subdirectories).
2004     * <p>
2005     * The resulting iterator MUST be consumed in its entirety in order to close its underlying stream.
2006     * </p>
2007     * <p>
2008     * All files found are filtered by an IOFileFilter.
2009     * </p>
2010     * <p>
2011     * The resulting iterator includes the subdirectories themselves.
2012     * </p>
2013     *
2014     * @param directory  the directory to search in
2015     * @param fileFilter filter to apply when finding files.
2016     * @param dirFilter  optional filter to apply when finding subdirectories.
2017     *                   If this parameter is {@code null}, subdirectories will not be included in the
2018     *                   search. Use TrueFileFilter.INSTANCE to match all directories.
2019     * @return an iterator of java.io.File for the matching files
2020     * @see org.apache.commons.io.filefilter.FileFilterUtils
2021     * @see org.apache.commons.io.filefilter.NameFileFilter
2022     * @since 2.2
2023     */
2024    public static Iterator<File> iterateFilesAndDirs(final File directory, final IOFileFilter fileFilter, final IOFileFilter dirFilter) {
2025        return listFilesAndDirs(directory, fileFilter, dirFilter).iterator();
2026    }
2027
2028    /**
2029     * Returns the last modification time in milliseconds via
2030     * {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}.
2031     * <p>
2032     * For the best precision, use {@link #lastModifiedFileTime(File)}.
2033     * </p>
2034     * <p>
2035     * Use this method to avoid issues with {@link File#lastModified()} like
2036     * <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a> where {@link File#lastModified()} is
2037     * losing milliseconds (always ends in 000). This bug exists in OpenJDK 8 and 9, and is fixed in 10.
2038     * </p>
2039     *
2040     * @param file The File to query.
2041     * @return See {@link java.nio.file.attribute.FileTime#toMillis()}.
2042     * @throws IOException if an I/O error occurs.
2043     * @since 2.9.0
2044     */
2045    public static long lastModified(final File file) throws IOException {
2046        // https://bugs.openjdk.java.net/browse/JDK-8177809
2047        // File.lastModified() is losing milliseconds (always ends in 000)
2048        // This bug is in OpenJDK 8 and 9, and fixed in 10.
2049        return lastModifiedFileTime(file).toMillis();
2050    }
2051
2052    /**
2053     * Returns the last modification {@link FileTime} via
2054     * {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}.
2055     * <p>
2056     * Use this method to avoid issues with {@link File#lastModified()} like
2057     * <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a> where {@link File#lastModified()} is
2058     * losing milliseconds (always ends in 000). This bug exists in OpenJDK 8 and 9, and is fixed in 10.
2059     * </p>
2060     *
2061     * @param file The File to query.
2062     * @return See {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}.
2063     * @throws IOException if an I/O error occurs.
2064     * @since 2.12.0
2065     */
2066    public static FileTime lastModifiedFileTime(final File file) throws IOException {
2067        // https://bugs.openjdk.java.net/browse/JDK-8177809
2068        // File.lastModified() is losing milliseconds (always ends in 000)
2069        // This bug is in OpenJDK 8 and 9, and fixed in 10.
2070        return Files.getLastModifiedTime(Objects.requireNonNull(file.toPath(), "file"));
2071    }
2072
2073    /**
2074     * Returns the last modification time in milliseconds via
2075     * {@link java.nio.file.Files#getLastModifiedTime(Path, LinkOption...)}.
2076     * <p>
2077     * For the best precision, use {@link #lastModifiedFileTime(File)}.
2078     * </p>
2079     * <p>
2080     * Use this method to avoid issues with {@link File#lastModified()} like
2081     * <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a> where {@link File#lastModified()} is
2082     * losing milliseconds (always ends in 000). This bug exists in OpenJDK 8 and 9, and is fixed in 10.
2083     * </p>
2084     *
2085     * @param file The File to query.
2086     * @return See {@link java.nio.file.attribute.FileTime#toMillis()}.
2087     * @throws UncheckedIOException if an I/O error occurs.
2088     * @since 2.9.0
2089     */
2090    public static long lastModifiedUnchecked(final File file) {
2091        // https://bugs.openjdk.java.net/browse/JDK-8177809
2092        // File.lastModified() is losing milliseconds (always ends in 000)
2093        // This bug is in OpenJDK 8 and 9, and fixed in 10.
2094        return Uncheck.apply(FileUtils::lastModified, file);
2095    }
2096
2097    /**
2098     * Returns an Iterator for the lines in a {@link File} using the default encoding for the VM.
2099     *
2100     * @param file the file to open for input, must not be {@code null}
2101     * @return an Iterator of the lines in the file, never {@code null}
2102     * @throws NullPointerException if file is {@code null}.
2103     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2104     *         other reason cannot be opened for reading.
2105     * @throws IOException if an I/O error occurs.
2106     * @see #lineIterator(File, String)
2107     * @since 1.3
2108     */
2109    public static LineIterator lineIterator(final File file) throws IOException {
2110        return lineIterator(file, null);
2111    }
2112
2113    /**
2114     * Returns an Iterator for the lines in a {@link File}.
2115     * <p>
2116     * This method opens an {@link InputStream} for the file.
2117     * When you have finished with the iterator you should close the stream
2118     * to free internal resources. This can be done by using a try-with-resources block or calling the
2119     * {@link LineIterator#close()} method.
2120     * </p>
2121     * <p>
2122     * The recommended usage pattern is:
2123     * </p>
2124     * <pre>
2125     * LineIterator it = FileUtils.lineIterator(file, StandardCharsets.UTF_8.name());
2126     * try {
2127     *   while (it.hasNext()) {
2128     *     String line = it.nextLine();
2129     *     /// do something with line
2130     *   }
2131     * } finally {
2132     *   LineIterator.closeQuietly(iterator);
2133     * }
2134     * </pre>
2135     * <p>
2136     * If an exception occurs during the creation of the iterator, the
2137     * underlying stream is closed.
2138     * </p>
2139     *
2140     * @param file     the file to open for input, must not be {@code null}
2141     * @param charsetName the name of the requested charset, {@code null} means platform default
2142     * @return a LineIterator for lines in the file, never {@code null}; MUST be closed by the caller.
2143     * @throws NullPointerException if file is {@code null}.
2144     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2145     *         other reason cannot be opened for reading.
2146     * @throws IOException if an I/O error occurs.
2147     * @since 1.2
2148     */
2149    @SuppressWarnings("resource") // Caller closes the result LineIterator.
2150    public static LineIterator lineIterator(final File file, final String charsetName) throws IOException {
2151        InputStream inputStream = null;
2152        try {
2153            inputStream = Files.newInputStream(file.toPath());
2154            return IOUtils.lineIterator(inputStream, charsetName);
2155        } catch (final IOException | RuntimeException ex) {
2156            IOUtils.closeQuietly(inputStream, ex::addSuppressed);
2157            throw ex;
2158        }
2159    }
2160
2161    private static AccumulatorPathVisitor listAccumulate(final File directory, final IOFileFilter fileFilter, final IOFileFilter dirFilter,
2162        final FileVisitOption... options) throws IOException {
2163        final boolean isDirFilterSet = dirFilter != null;
2164        final FileEqualsFileFilter rootDirFilter = new FileEqualsFileFilter(directory);
2165        final PathFilter dirPathFilter = isDirFilterSet ? rootDirFilter.or(dirFilter) : rootDirFilter;
2166        final AccumulatorPathVisitor visitor = new AccumulatorPathVisitor(Counters.noopPathCounters(), fileFilter, dirPathFilter,
2167            (p, e) -> FileVisitResult.CONTINUE);
2168        final Set<FileVisitOption> optionSet = new HashSet<>();
2169        Collections.addAll(optionSet, options);
2170        Files.walkFileTree(directory.toPath(), optionSet, toMaxDepth(isDirFilterSet), visitor);
2171        return visitor;
2172    }
2173
2174    /**
2175     * Lists files in a directory, asserting that the supplied directory exists and is a directory.
2176     *
2177     * @param directory The directory to list
2178     * @param fileFilter Optional file filter, may be null.
2179     * @return The files in the directory, never {@code null}.
2180     * @throws NullPointerException if directory is {@code null}.
2181     * @throws IllegalArgumentException if directory does not exist or is not a directory.
2182     * @throws IOException if an I/O error occurs.
2183     */
2184    private static File[] listFiles(final File directory, final FileFilter fileFilter) throws IOException {
2185        requireDirectoryExists(directory, "directory");
2186        final File[] files = fileFilter == null ? directory.listFiles() : directory.listFiles(fileFilter);
2187        if (files == null) {
2188            // null if the directory does not denote a directory, or if an I/O error occurs.
2189            throw new IOException("Unknown I/O error listing contents of directory: " + directory);
2190        }
2191        return files;
2192    }
2193
2194    /**
2195     * Finds files within a given directory (and optionally its
2196     * subdirectories). All files found are filtered by an IOFileFilter.
2197     * <p>
2198     * If your search should recurse into subdirectories you can pass in
2199     * an IOFileFilter for directories. You don't need to bind a
2200     * DirectoryFileFilter (via logical AND) to this filter. This method does
2201     * that for you.
2202     * </p>
2203     * <p>
2204     * An example: If you want to search through all directories called
2205     * "temp" you pass in {@code FileFilterUtils.NameFileFilter("temp")}
2206     * </p>
2207     * <p>
2208     * Another common usage of this method is find files in a directory
2209     * tree but ignoring the directories generated CVS. You can simply pass
2210     * in {@code FileFilterUtils.makeCVSAware(null)}.
2211     * </p>
2212     *
2213     * @param directory  the directory to search in
2214     * @param fileFilter filter to apply when finding files. Must not be {@code null},
2215     *                   use {@link TrueFileFilter#INSTANCE} to match all files in selected directories.
2216     * @param dirFilter  optional filter to apply when finding subdirectories.
2217     *                   If this parameter is {@code null}, subdirectories will not be included in the
2218     *                   search. Use {@link TrueFileFilter#INSTANCE} to match all directories.
2219     * @return a collection of java.io.File with the matching files
2220     * @see org.apache.commons.io.filefilter.FileFilterUtils
2221     * @see org.apache.commons.io.filefilter.NameFileFilter
2222     */
2223    public static Collection<File> listFiles(final File directory, final IOFileFilter fileFilter, final IOFileFilter dirFilter) {
2224        final AccumulatorPathVisitor visitor = Uncheck
2225            .apply(d -> listAccumulate(d, FileFileFilter.INSTANCE.and(fileFilter), dirFilter, FileVisitOption.FOLLOW_LINKS), directory);
2226        return visitor.getFileList().stream().map(Path::toFile).collect(Collectors.toList());
2227    }
2228
2229    /**
2230     * Finds files within a given directory (and optionally its subdirectories)
2231     * which match an array of extensions.
2232     *
2233     * @param directory  the directory to search in
2234     * @param extensions an array of extensions, ex. {"java","xml"}. If this
2235     *                   parameter is {@code null}, all files are returned.
2236     * @param recursive  if true all subdirectories are searched as well
2237     * @return a collection of java.io.File with the matching files
2238     */
2239    public static Collection<File> listFiles(final File directory, final String[] extensions, final boolean recursive) {
2240        return Uncheck.apply(d -> toList(streamFiles(d, recursive, extensions)), directory);
2241    }
2242
2243    /**
2244     * Finds files within a given directory (and optionally its
2245     * subdirectories). All files found are filtered by an IOFileFilter.
2246     * <p>
2247     * The resulting collection includes the starting directory and
2248     * any subdirectories that match the directory filter.
2249     * </p>
2250     *
2251     * @param directory  the directory to search in
2252     * @param fileFilter filter to apply when finding files.
2253     * @param dirFilter  optional filter to apply when finding subdirectories.
2254     *                   If this parameter is {@code null}, subdirectories will not be included in the
2255     *                   search. Use TrueFileFilter.INSTANCE to match all directories.
2256     * @return a collection of java.io.File with the matching files
2257     * @see org.apache.commons.io.FileUtils#listFiles
2258     * @see org.apache.commons.io.filefilter.FileFilterUtils
2259     * @see org.apache.commons.io.filefilter.NameFileFilter
2260     * @since 2.2
2261     */
2262    public static Collection<File> listFilesAndDirs(final File directory, final IOFileFilter fileFilter, final IOFileFilter dirFilter) {
2263        final AccumulatorPathVisitor visitor = Uncheck.apply(d -> listAccumulate(d, fileFilter, dirFilter, FileVisitOption.FOLLOW_LINKS),
2264            directory);
2265        final List<Path> list = visitor.getFileList();
2266        list.addAll(visitor.getDirList());
2267        return list.stream().map(Path::toFile).collect(Collectors.toList());
2268    }
2269
2270    /**
2271     * Calls {@link File#mkdirs()} and throws an {@link IOException} on failure.
2272     * <p>
2273     * Creates all directories for a File object, including any necessary but non-existent parent directories. If the {@code directory} already exists or is
2274     * null, nothing happens.
2275     * </p>
2276     *
2277     * @param directory the receiver for {@code mkdirs()}. If the {@code directory} already exists or is null, nothing happens.
2278     * @return the given directory.
2279     * @throws IOException       if the directory was not created along with all its parent directories.
2280     * @throws IOException       if the given file object is not a directory.
2281     * @throws SecurityException See {@link File#mkdirs()}.
2282     * @see File#mkdirs()
2283     */
2284    private static File mkdirs(final File directory) throws IOException {
2285        if (directory != null && !directory.mkdirs() && !directory.isDirectory()) {
2286            throw new IOException("Cannot create directory '" + directory + "'.");
2287        }
2288        return directory;
2289    }
2290
2291    /**
2292     * Moves a directory.
2293     * <p>
2294     * When the destination directory is on another file system, do a "copy and delete".
2295     * </p>
2296     *
2297     * @param srcDir the directory to be moved.
2298     * @param destDir the destination directory.
2299     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2300     * @throws IllegalArgumentException if the source or destination is invalid.
2301     * @throws FileNotFoundException if the source does not exist.
2302     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
2303     * @since 1.4
2304     */
2305    public static void moveDirectory(final File srcDir, final File destDir) throws IOException {
2306        validateMoveParameters(srcDir, destDir);
2307        requireDirectory(srcDir, "srcDir");
2308        requireAbsent(destDir, "destDir");
2309        if (!srcDir.renameTo(destDir)) {
2310            if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath() + File.separator)) {
2311                throw new IOException("Cannot move directory: " + srcDir + " to a subdirectory of itself: " + destDir);
2312            }
2313            copyDirectory(srcDir, destDir);
2314            deleteDirectory(srcDir);
2315            if (srcDir.exists()) {
2316                throw new IOException("Failed to delete original directory '" + srcDir +
2317                        "' after copy to '" + destDir + "'");
2318            }
2319        }
2320    }
2321
2322    /**
2323     * Moves a directory to another directory.
2324     * <p>
2325     * If {@code createDestDir} is true, creates all destination parent directories, including any necessary but non-existent parent directories.
2326     * </p>
2327     *
2328     * @param source the file to be moved.
2329     * @param destDir the destination file.
2330     * @param createDestDir If {@code true} create the destination directory, otherwise if {@code false} throw an
2331     *        IOException.
2332     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2333     * @throws IllegalArgumentException if the source or destination is invalid.
2334     * @throws FileNotFoundException if the source does not exist.
2335     * @throws IOException if the directory was not created along with all its parent directories, if enabled.
2336     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
2337     * @throws SecurityException See {@link File#mkdirs()}.
2338     * @since 1.4
2339     */
2340    public static void moveDirectoryToDirectory(final File source, final File destDir, final boolean createDestDir) throws IOException {
2341        validateMoveParameters(source, destDir);
2342        if (!destDir.isDirectory()) {
2343            if (destDir.exists()) {
2344                throw new IOException("Destination '" + destDir + "' is not a directory");
2345            }
2346            if (!createDestDir) {
2347                throw new FileNotFoundException("Destination directory '" + destDir + "' does not exist [createDestDir=" + false + "]");
2348            }
2349            mkdirs(destDir);
2350        }
2351        moveDirectory(source, new File(destDir, source.getName()));
2352    }
2353
2354    /**
2355     * Moves a file preserving attributes.
2356     * <p>
2357     * Shorthand for {@code moveFile(srcFile, destFile, StandardCopyOption.COPY_ATTRIBUTES)}.
2358     * </p>
2359     * <p>
2360     * When the destination file is on another file system, do a "copy and delete".
2361     * </p>
2362     *
2363     * @param srcFile the file to be moved.
2364     * @param destFile the destination file.
2365     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2366     * @throws FileExistsException if the destination file exists.
2367     * @throws FileNotFoundException if the source file does not exist.
2368     * @throws IOException if source or destination is invalid.
2369     * @throws IOException if an error occurs.
2370     * @since 1.4
2371     */
2372    public static void moveFile(final File srcFile, final File destFile) throws IOException {
2373        moveFile(srcFile, destFile, StandardCopyOption.COPY_ATTRIBUTES);
2374    }
2375
2376    /**
2377     * Moves a file.
2378     * <p>
2379     * When the destination file is on another file system, do a "copy and delete".
2380     * </p>
2381     *
2382     * @param srcFile the file to be moved.
2383     * @param destFile the destination file.
2384     * @param copyOptions Copy options.
2385     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2386     * @throws FileExistsException if the destination file exists.
2387     * @throws FileNotFoundException if the source file does not exist.
2388     * @throws IOException if source or destination is invalid.
2389     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
2390     * @since 2.9.0
2391     */
2392    public static void moveFile(final File srcFile, final File destFile, final CopyOption... copyOptions) throws IOException {
2393        validateMoveParameters(srcFile, destFile);
2394        requireFile(srcFile, "srcFile");
2395        requireAbsent(destFile, "destFile");
2396        final boolean rename = srcFile.renameTo(destFile);
2397        if (!rename) {
2398            // Don't interfere with file date on move, handled by StandardCopyOption.COPY_ATTRIBUTES
2399            copyFile(srcFile, destFile, false, copyOptions);
2400            if (!srcFile.delete()) {
2401                FileUtils.deleteQuietly(destFile);
2402                throw new IOException("Failed to delete original file '" + srcFile + "' after copy to '" + destFile + "'");
2403            }
2404        }
2405    }
2406
2407    /**
2408     * Moves a file to a directory.
2409     * <p>
2410     * If {@code createDestDir} is true, creates all destination parent directories, including any necessary but non-existent parent directories.
2411     * </p>
2412     *
2413     * @param srcFile the file to be moved.
2414     * @param destDir the destination file.
2415     * @param createDestDir If {@code true} create the destination directory, otherwise if {@code false} throw an
2416     *        IOException.
2417     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2418     * @throws FileExistsException if the destination file exists.
2419     * @throws FileNotFoundException if the source file does not exist.
2420     * @throws IOException if source or destination is invalid.
2421     * @throws IOException if the directory was not created along with all its parent directories, if enabled.
2422     * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
2423     * @throws SecurityException See {@link File#mkdirs()}.
2424     * @since 1.4
2425     */
2426    public static void moveFileToDirectory(final File srcFile, final File destDir, final boolean createDestDir) throws IOException {
2427        validateMoveParameters(srcFile, destDir);
2428        if (!destDir.exists() && createDestDir) {
2429            mkdirs(destDir);
2430        }
2431        requireExistsChecked(destDir, "destDir");
2432        requireDirectory(destDir, "destDir");
2433        moveFile(srcFile, new File(destDir, srcFile.getName()));
2434    }
2435
2436    /**
2437     * Moves a file or directory to a destination directory.
2438     * <p>
2439     * If {@code createDestDir} is true, creates all destination parent directories, including any necessary but non-existent parent directories.
2440     * </p>
2441     * <p>
2442     * When the destination is on another file system, do a "copy and delete".
2443     * </p>
2444     *
2445     * @param src           the file or directory to be moved.
2446     * @param destDir       the destination directory.
2447     * @param createDestDir If {@code true} create the destination directory, otherwise if {@code false} throw an IOException.
2448     * @throws NullPointerException  if any of the given {@link File}s are {@code null}.
2449     * @throws FileExistsException   if the directory or file exists in the destination directory.
2450     * @throws FileNotFoundException if the source file does not exist.
2451     * @throws IOException           if source or destination is invalid.
2452     * @throws IOException           if an error occurs or setting the last-modified time didn't succeed.
2453     * @since 1.4
2454     */
2455    public static void moveToDirectory(final File src, final File destDir, final boolean createDestDir) throws IOException {
2456        validateMoveParameters(src, destDir);
2457        if (src.isDirectory()) {
2458            moveDirectoryToDirectory(src, destDir, createDestDir);
2459        } else {
2460            moveFileToDirectory(src, destDir, createDestDir);
2461        }
2462    }
2463
2464    /**
2465     * Creates a new OutputStream by opening or creating a file, returning an output stream that may be used to write bytes
2466     * to the file.
2467     *
2468     * @param append Whether or not to append.
2469     * @param file the File.
2470     * @return a new OutputStream.
2471     * @throws IOException if an I/O error occurs.
2472     * @see PathUtils#newOutputStream(Path, boolean)
2473     * @since 2.12.0
2474     */
2475    public static OutputStream newOutputStream(final File file, final boolean append) throws IOException {
2476        return PathUtils.newOutputStream(Objects.requireNonNull(file, "file").toPath(), append);
2477    }
2478
2479    /**
2480     * Opens a {@link FileInputStream} for the specified file, providing better error messages than simply calling
2481     * {@code new FileInputStream(file)}.
2482     * <p>
2483     * At the end of the method either the stream will be successfully opened, or an exception will have been thrown.
2484     * </p>
2485     * <p>
2486     * An exception is thrown if the file does not exist. An exception is thrown if the file object exists but is a
2487     * directory. An exception is thrown if the file exists but cannot be read.
2488     * </p>
2489     *
2490     * @param file the file to open for input, must not be {@code null}
2491     * @return a new {@link FileInputStream} for the specified file
2492     * @throws NullPointerException if file is {@code null}.
2493     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2494     *         other reason cannot be opened for reading.
2495     * @throws IOException See FileNotFoundException above, FileNotFoundException is a subclass of IOException.
2496     * @since 1.3
2497     */
2498    public static FileInputStream openInputStream(final File file) throws IOException {
2499        Objects.requireNonNull(file, "file");
2500        return new FileInputStream(file);
2501    }
2502
2503    /**
2504     * Opens a {@link FileOutputStream} for the specified file, checking and
2505     * creating the parent directory if it does not exist.
2506     * <p>
2507     * At the end of the method either the stream will be successfully opened,
2508     * or an exception will have been thrown.
2509     * </p>
2510     * <p>
2511     * The parent directory will be created if it does not exist.
2512     * The file will be created if it does not exist.
2513     * An exception is thrown if the file object exists but is a directory.
2514     * An exception is thrown if the file exists but cannot be written to.
2515     * An exception is thrown if the parent directory cannot be created.
2516     * </p>
2517     *
2518     * @param file the file to open for output, must not be {@code null}
2519     * @return a new {@link FileOutputStream} for the specified file
2520     * @throws NullPointerException if the file object is {@code null}.
2521     * @throws IllegalArgumentException if the file object is a directory
2522     * @throws IllegalArgumentException if the file is not writable.
2523     * @throws IOException if the directories could not be created.
2524     * @since 1.3
2525     */
2526    public static FileOutputStream openOutputStream(final File file) throws IOException {
2527        return openOutputStream(file, false);
2528    }
2529
2530    /**
2531     * Opens a {@link FileOutputStream} for the specified file, checking and
2532     * creating the parent directory if it does not exist.
2533     * <p>
2534     * At the end of the method either the stream will be successfully opened,
2535     * or an exception will have been thrown.
2536     * </p>
2537     * <p>
2538     * The parent directory will be created if it does not exist.
2539     * The file will be created if it does not exist.
2540     * An exception is thrown if the file object exists but is a directory.
2541     * An exception is thrown if the file exists but cannot be written to.
2542     * An exception is thrown if the parent directory cannot be created.
2543     * </p>
2544     *
2545     * @param file   the file to open for output, must not be {@code null}
2546     * @param append if {@code true}, then bytes will be added to the
2547     *               end of the file rather than overwriting
2548     * @return a new {@link FileOutputStream} for the specified file
2549     * @throws NullPointerException if the file object is {@code null}.
2550     * @throws IllegalArgumentException if the file object is a directory
2551     * @throws IllegalArgumentException if the file is not writable.
2552     * @throws IOException if the directories could not be created.
2553     * @since 2.1
2554     */
2555    public static FileOutputStream openOutputStream(final File file, final boolean append) throws IOException {
2556        Objects.requireNonNull(file, "file");
2557        if (file.exists()) {
2558            requireFile(file, "file");
2559            requireCanWrite(file, "file");
2560        } else {
2561            createParentDirectories(file);
2562        }
2563        return new FileOutputStream(file, append);
2564    }
2565
2566    /**
2567     * Reads the contents of a file into a byte array.
2568     * The file is always closed.
2569     *
2570     * @param file the file to read, must not be {@code null}
2571     * @return the file contents, never {@code null}
2572     * @throws NullPointerException if file is {@code null}.
2573     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2574     *         other reason cannot be opened for reading.
2575     * @throws IOException if an I/O error occurs.
2576     * @since 1.1
2577     */
2578    public static byte[] readFileToByteArray(final File file) throws IOException {
2579        Objects.requireNonNull(file, "file");
2580        return Files.readAllBytes(file.toPath());
2581    }
2582
2583    /**
2584     * Reads the contents of a file into a String using the default encoding for the VM.
2585     * The file is always closed.
2586     *
2587     * @param file the file to read, must not be {@code null}
2588     * @return the file contents, never {@code null}
2589     * @throws NullPointerException if file is {@code null}.
2590     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2591     *         other reason cannot be opened for reading.
2592     * @throws IOException if an I/O error occurs.
2593     * @since 1.3.1
2594     * @deprecated 2.5 use {@link #readFileToString(File, Charset)} instead (and specify the appropriate encoding)
2595     */
2596    @Deprecated
2597    public static String readFileToString(final File file) throws IOException {
2598        return readFileToString(file, Charset.defaultCharset());
2599    }
2600
2601    /**
2602     * Reads the contents of a file into a String.
2603     * The file is always closed.
2604     *
2605     * @param file     the file to read, must not be {@code null}
2606     * @param charsetName the name of the requested charset, {@code null} means platform default
2607     * @return the file contents, never {@code null}
2608     * @throws NullPointerException if file is {@code null}.
2609     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2610     *         other reason cannot be opened for reading.
2611     * @throws IOException if an I/O error occurs.
2612     * @since 2.3
2613     */
2614    public static String readFileToString(final File file, final Charset charsetName) throws IOException {
2615        return IOUtils.toString(() -> Files.newInputStream(file.toPath()), Charsets.toCharset(charsetName));
2616    }
2617
2618    /**
2619     * Reads the contents of a file into a String. The file is always closed.
2620     *
2621     * @param file     the file to read, must not be {@code null}
2622     * @param charsetName the name of the requested charset, {@code null} means platform default
2623     * @return the file contents, never {@code null}
2624     * @throws NullPointerException if file is {@code null}.
2625     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2626     *         other reason cannot be opened for reading.
2627     * @throws IOException if an I/O error occurs.
2628     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io
2629     * .UnsupportedEncodingException} in version 2.2 if the named charset is unavailable.
2630     * @since 2.3
2631     */
2632    public static String readFileToString(final File file, final String charsetName) throws IOException {
2633        return readFileToString(file, Charsets.toCharset(charsetName));
2634    }
2635
2636    /**
2637     * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM.
2638     * The file is always closed.
2639     *
2640     * @param file the file to read, must not be {@code null}
2641     * @return the list of Strings representing each line in the file, never {@code null}
2642     * @throws NullPointerException if file is {@code null}.
2643     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2644     *         other reason cannot be opened for reading.
2645     * @throws IOException if an I/O error occurs.
2646     * @since 1.3
2647     * @deprecated 2.5 use {@link #readLines(File, Charset)} instead (and specify the appropriate encoding)
2648     */
2649    @Deprecated
2650    public static List<String> readLines(final File file) throws IOException {
2651        return readLines(file, Charset.defaultCharset());
2652    }
2653
2654    /**
2655     * Reads the contents of a file line by line to a List of Strings.
2656     * The file is always closed.
2657     *
2658     * @param file     the file to read, must not be {@code null}
2659     * @param charset the charset to use, {@code null} means platform default
2660     * @return the list of Strings representing each line in the file, never {@code null}
2661     * @throws NullPointerException if file is {@code null}.
2662     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2663     *         other reason cannot be opened for reading.
2664     * @throws IOException if an I/O error occurs.
2665     * @since 2.3
2666     */
2667    public static List<String> readLines(final File file, final Charset charset) throws IOException {
2668        return Files.readAllLines(file.toPath(), charset);
2669    }
2670
2671    /**
2672     * Reads the contents of a file line by line to a List of Strings. The file is always closed.
2673     *
2674     * @param file     the file to read, must not be {@code null}
2675     * @param charsetName the name of the requested charset, {@code null} means platform default
2676     * @return the list of Strings representing each line in the file, never {@code null}
2677     * @throws NullPointerException if file is {@code null}.
2678     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
2679     *         other reason cannot be opened for reading.
2680     * @throws IOException if an I/O error occurs.
2681     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io
2682     * .UnsupportedEncodingException} in version 2.2 if the named charset is unavailable.
2683     * @since 1.1
2684     */
2685    public static List<String> readLines(final File file, final String charsetName) throws IOException {
2686        return readLines(file, Charsets.toCharset(charsetName));
2687    }
2688
2689
2690    private static void requireAbsent(final File file, final String name) throws FileExistsException {
2691        if (file.exists()) {
2692            throw new FileExistsException(String.format("File element in parameter '%s' already exists: '%s'", name, file));
2693        }
2694    }
2695
2696    /**
2697     * Throws IllegalArgumentException if the given files' canonical representations are equal.
2698     *
2699     * @param file1 The first file to compare.
2700     * @param file2 The second file to compare.
2701     * @throws IOException if an I/O error occurs.
2702     * @throws IllegalArgumentException if the given files' canonical representations are equal.
2703     */
2704    private static void requireCanonicalPathsNotEquals(final File file1, final File file2) throws IOException {
2705        final String canonicalPath = file1.getCanonicalPath();
2706        if (canonicalPath.equals(file2.getCanonicalPath())) {
2707            throw new IllegalArgumentException(String
2708                .format("File canonical paths are equal: '%s' (file1='%s', file2='%s')", canonicalPath, file1, file2));
2709        }
2710    }
2711
2712    /**
2713     * Throws an {@link IllegalArgumentException} if the file is not writable. This provides a more precise exception
2714     * message than a plain access denied.
2715     *
2716     * @param file The file to test.
2717     * @param name The parameter name to use in the exception message.
2718     * @throws NullPointerException if the given {@link File} is {@code null}.
2719     * @throws IllegalArgumentException if the file is not writable.
2720     */
2721    private static void requireCanWrite(final File file, final String name) {
2722        Objects.requireNonNull(file, "file");
2723        if (!file.canWrite()) {
2724            throw new IllegalArgumentException("File parameter '" + name + " is not writable: '" + file + "'");
2725        }
2726    }
2727
2728    /**
2729     * Requires that the given {@link File} is a directory.
2730     *
2731     * @param directory The {@link File} to check.
2732     * @param name The parameter name to use in the exception message in case of null input or if the file is not a directory.
2733     * @return the given directory.
2734     * @throws NullPointerException if the given {@link File} is {@code null}.
2735     * @throws IllegalArgumentException if the given {@link File} does not exist or is not a directory.
2736     */
2737    private static File requireDirectory(final File directory, final String name) {
2738        Objects.requireNonNull(directory, name);
2739        if (!directory.isDirectory()) {
2740            throw new IllegalArgumentException("Parameter '" + name + "' is not a directory: '" + directory + "'");
2741        }
2742        return directory;
2743    }
2744
2745    /**
2746     * Requires that the given {@link File} exists and is a directory.
2747     *
2748     * @param directory The {@link File} to check.
2749     * @param name The parameter name to use in the exception message in case of null input.
2750     * @return the given directory.
2751     * @throws NullPointerException if the given {@link File} is {@code null}.
2752     * @throws IllegalArgumentException if the given {@link File} does not exist or is not a directory.
2753     */
2754    private static File requireDirectoryExists(final File directory, final String name) {
2755        requireExists(directory, name);
2756        requireDirectory(directory, name);
2757        return directory;
2758    }
2759
2760    /**
2761     * Requires that the given {@link File} is a directory if it exists.
2762     *
2763     * @param directory The {@link File} to check.
2764     * @param name The parameter name to use in the exception message in case of null input.
2765     * @return the given directory.
2766     * @throws NullPointerException if the given {@link File} is {@code null}.
2767     * @throws IllegalArgumentException if the given {@link File} exists but is not a directory.
2768     */
2769    private static File requireDirectoryIfExists(final File directory, final String name) {
2770        Objects.requireNonNull(directory, name);
2771        if (directory.exists()) {
2772            requireDirectory(directory, name);
2773        }
2774        return directory;
2775    }
2776
2777    /**
2778     * Requires that the given {@link File} exists and throws an {@link IllegalArgumentException} if it doesn't.
2779     *
2780     * @param file The {@link File} to check.
2781     * @param fileParamName The parameter name to use in the exception message in case of {@code null} input.
2782     * @return the given file.
2783     * @throws NullPointerException if the given {@link File} is {@code null}.
2784     * @throws IllegalArgumentException if the given {@link File} does not exist.
2785     */
2786    private static File requireExists(final File file, final String fileParamName) {
2787        Objects.requireNonNull(file, fileParamName);
2788        if (!file.exists()) {
2789            throw new IllegalArgumentException("File system element for parameter '" + fileParamName + "' does not exist: '" + file + "'");
2790        }
2791        return file;
2792    }
2793
2794    /**
2795     * Requires that the given {@link File} exists and throws an {@link FileNotFoundException} if it doesn't.
2796     *
2797     * @param file The {@link File} to check.
2798     * @param fileParamName The parameter name to use in the exception message in case of {@code null} input.
2799     * @return the given file.
2800     * @throws NullPointerException if the given {@link File} is {@code null}.
2801     * @throws FileNotFoundException if the given {@link File} does not exist.
2802     */
2803    private static File requireExistsChecked(final File file, final String fileParamName) throws FileNotFoundException {
2804        Objects.requireNonNull(file, fileParamName);
2805        if (!file.exists()) {
2806            throw new FileNotFoundException("File system element for parameter '" + fileParamName + "' does not exist: '" + file + "'");
2807        }
2808        return file;
2809    }
2810
2811    /**
2812     * Requires that the given {@link File} is a file.
2813     *
2814     * @param file The {@link File} to check.
2815     * @param name The parameter name to use in the exception message.
2816     * @return the given file.
2817     * @throws NullPointerException if the given {@link File} is {@code null}.
2818     * @throws IllegalArgumentException if the given {@link File} does not exist or is not a file.
2819     */
2820    private static File requireFile(final File file, final String name) {
2821        Objects.requireNonNull(file, name);
2822        if (!file.isFile()) {
2823            throw new IllegalArgumentException("Parameter '" + name + "' is not a file: " + file);
2824        }
2825        return file;
2826    }
2827
2828    /**
2829     * Requires parameter attributes for a file copy operation.
2830     *
2831     * @param source the source file
2832     * @param destination the destination
2833     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
2834     * @throws FileNotFoundException if the source does not exist.
2835     */
2836    private static void requireFileCopy(final File source, final File destination) throws FileNotFoundException {
2837        requireExistsChecked(source, "source");
2838        Objects.requireNonNull(destination, "destination");
2839    }
2840
2841    /**
2842     * Requires that the given {@link File} is a file if it exists.
2843     *
2844     * @param file The {@link File} to check.
2845     * @param name The parameter name to use in the exception message in case of null input.
2846     * @return the given directory.
2847     * @throws NullPointerException if the given {@link File} is {@code null}.
2848     * @throws IllegalArgumentException if the given {@link File} exists but is not a directory.
2849     */
2850    private static File requireFileIfExists(final File file, final String name) {
2851        Objects.requireNonNull(file, name);
2852        return file.exists() ? requireFile(file, name) : file;
2853    }
2854
2855    /**
2856     * Set file lastModifiedTime, lastAccessTime and creationTime to match source file
2857     *
2858     * @param sourceFile The source file to query.
2859     * @param targetFile The target file or directory to set.
2860     * @return {@code true} if and only if the operation succeeded;
2861     *          {@code false} otherwise
2862     * @throws NullPointerException if sourceFile is {@code null}.
2863     * @throws NullPointerException if targetFile is {@code null}.
2864     * @throws IOException if setting the last-modified time failed.
2865     */
2866    private static boolean setTimes(final File sourceFile, final File targetFile) throws IOException {
2867        Objects.requireNonNull(sourceFile, "sourceFile");
2868        Objects.requireNonNull(targetFile, "targetFile");
2869        try {
2870            // Set creation, modified, last accessed to match source file
2871            final BasicFileAttributes srcAttr = Files.readAttributes(sourceFile.toPath(), BasicFileAttributes.class);
2872            final BasicFileAttributeView destAttrView = Files.getFileAttributeView(targetFile.toPath(), BasicFileAttributeView.class);
2873            // null guards are not needed; BasicFileAttributes.setTimes(...) is null safe
2874            destAttrView.setTimes(srcAttr.lastModifiedTime(), srcAttr.lastAccessTime(), srcAttr.creationTime());
2875            return true;
2876        } catch (IOException ignored) {
2877            // Fallback: Only set modified time to match source file
2878            return targetFile.setLastModified(sourceFile.lastModified());
2879        }
2880
2881        // TODO: (Help!) Determine historically why setLastModified(File, File) needed PathUtils.setLastModifiedTime() if
2882        //  sourceFile.isFile() was true, but needed setLastModifiedTime(File, long) if sourceFile.isFile() was false
2883    }
2884
2885    /**
2886     * Returns the size of the specified file or directory. If the provided
2887     * {@link File} is a regular file, then the file's length is returned.
2888     * If the argument is a directory, then the size of the directory is
2889     * calculated recursively. If a directory or subdirectory is security
2890     * restricted, its size will not be included.
2891     * <p>
2892     * Note that overflow is not detected, and the return value may be negative if
2893     * overflow occurs. See {@link #sizeOfAsBigInteger(File)} for an alternative
2894     * method that does not overflow.
2895     * </p>
2896     *
2897     * @param file the regular file or directory to return the size
2898     *             of (must not be {@code null}).
2899     *
2900     * @return the length of the file, or recursive size of the directory,
2901     * provided (in bytes).
2902     *
2903     * @throws NullPointerException     if the file is {@code null}.
2904     * @throws IllegalArgumentException if the file does not exist.
2905     * @throws UncheckedIOException if an IO error occurs.
2906     * @since 2.0
2907     */
2908    public static long sizeOf(final File file) {
2909        requireExists(file, "file");
2910        return Uncheck.get(() -> PathUtils.sizeOf(file.toPath()));
2911    }
2912
2913    /**
2914     * Returns the size of the specified file or directory. If the provided
2915     * {@link File} is a regular file, then the file's length is returned.
2916     * If the argument is a directory, then the size of the directory is
2917     * calculated recursively. If a directory or subdirectory is security
2918     * restricted, its size will not be included.
2919     *
2920     * @param file the regular file or directory to return the size
2921     *             of (must not be {@code null}).
2922     *
2923     * @return the length of the file, or recursive size of the directory,
2924     * provided (in bytes).
2925     *
2926     * @throws NullPointerException     if the file is {@code null}.
2927     * @throws IllegalArgumentException if the file does not exist.
2928     * @throws UncheckedIOException if an IO error occurs.
2929     * @since 2.4
2930     */
2931    public static BigInteger sizeOfAsBigInteger(final File file) {
2932        requireExists(file, "file");
2933        return Uncheck.get(() -> PathUtils.sizeOfAsBigInteger(file.toPath()));
2934    }
2935
2936    /**
2937     * Counts the size of a directory recursively (sum of the length of all files).
2938     * <p>
2939     * Note that overflow is not detected, and the return value may be negative if
2940     * overflow occurs. See {@link #sizeOfDirectoryAsBigInteger(File)} for an alternative
2941     * method that does not overflow.
2942     * </p>
2943     *
2944     * @param directory directory to inspect, must not be {@code null}.
2945     * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total
2946     * is greater than {@link Long#MAX_VALUE}.
2947     * @throws NullPointerException if the directory is {@code null}.
2948     * @throws UncheckedIOException if an IO error occurs.
2949     */
2950    public static long sizeOfDirectory(final File directory) {
2951        requireDirectoryExists(directory, "directory");
2952        return Uncheck.get(() -> PathUtils.sizeOfDirectory(directory.toPath()));
2953    }
2954
2955    /**
2956     * Counts the size of a directory recursively (sum of the length of all files).
2957     *
2958     * @param directory directory to inspect, must not be {@code null}.
2959     * @return size of directory in bytes, 0 if directory is security restricted.
2960     * @throws NullPointerException if the directory is {@code null}.
2961     * @throws UncheckedIOException if an IO error occurs.
2962     * @since 2.4
2963     */
2964    public static BigInteger sizeOfDirectoryAsBigInteger(final File directory) {
2965        requireDirectoryExists(directory, "directory");
2966        return Uncheck.get(() -> PathUtils.sizeOfDirectoryAsBigInteger(directory.toPath()));
2967    }
2968
2969    /**
2970     * Streams over the files in a given directory (and optionally
2971     * its subdirectories) which match an array of extensions.
2972     *
2973     * @param directory  the directory to search in
2974     * @param recursive  if true all subdirectories are searched as well
2975     * @param extensions an array of extensions, ex. {"java","xml"}. If this
2976     *                   parameter is {@code null}, all files are returned.
2977     * @return an iterator of java.io.File with the matching files
2978     * @throws IOException if an I/O error is thrown when accessing the starting file.
2979     * @since 2.9.0
2980     */
2981    public static Stream<File> streamFiles(final File directory, final boolean recursive, final String... extensions) throws IOException {
2982        // @formatter:off
2983        final IOFileFilter filter = extensions == null
2984            ? FileFileFilter.INSTANCE
2985            : FileFileFilter.INSTANCE.and(new SuffixFileFilter(toSuffixes(extensions)));
2986        // @formatter:on
2987        return PathUtils.walk(directory.toPath(), filter, toMaxDepth(recursive), false, FileVisitOption.FOLLOW_LINKS).map(Path::toFile);
2988    }
2989
2990    /**
2991     * Converts from a {@link URL} to a {@link File}.
2992     * <p>
2993     * From version 1.1 this method will decode the URL.
2994     * Syntax such as {@code file:///my%20docs/file.txt} will be
2995     * correctly decoded to {@code /my docs/file.txt}. Starting with version
2996     * 1.5, this method uses UTF-8 to decode percent-encoded octets to characters.
2997     * Additionally, malformed percent-encoded octets are handled leniently by
2998     * passing them through literally.
2999     * </p>
3000     *
3001     * @param url the file URL to convert, {@code null} returns {@code null}
3002     * @return the equivalent {@link File} object, or {@code null}
3003     * if the URL's protocol is not {@code file}
3004     */
3005    public static File toFile(final URL url) {
3006        if (url == null || !"file".equalsIgnoreCase(url.getProtocol())) {
3007            return null;
3008        }
3009        final String filename = url.getFile().replace('/', File.separatorChar);
3010        return new File(decodeUrl(filename));
3011    }
3012
3013    /**
3014     * Converts each of an array of {@link URL} to a {@link File}.
3015     * <p>
3016     * Returns an array of the same size as the input.
3017     * If the input is {@code null}, an empty array is returned.
3018     * If the input contains {@code null}, the output array contains {@code null} at the same
3019     * index.
3020     * </p>
3021     * <p>
3022     * This method will decode the URL.
3023     * Syntax such as {@code file:///my%20docs/file.txt} will be
3024     * correctly decoded to {@code /my docs/file.txt}.
3025     * </p>
3026     *
3027     * @param urls the file URLs to convert, {@code null} returns empty array
3028     * @return a non-{@code null} array of Files matching the input, with a {@code null} item
3029     * if there was a {@code null} at that index in the input array
3030     * @throws IllegalArgumentException if any file is not a URL file
3031     * @throws IllegalArgumentException if any file is incorrectly encoded
3032     * @since 1.1
3033     */
3034    public static File[] toFiles(final URL... urls) {
3035        if (IOUtils.length(urls) == 0) {
3036            return EMPTY_FILE_ARRAY;
3037        }
3038        final File[] files = new File[urls.length];
3039        for (int i = 0; i < urls.length; i++) {
3040            final URL url = urls[i];
3041            if (url != null) {
3042                if (!"file".equalsIgnoreCase(url.getProtocol())) {
3043                    throw new IllegalArgumentException("Can only convert file URL to a File: " + url);
3044                }
3045                files[i] = toFile(url);
3046            }
3047        }
3048        return files;
3049    }
3050
3051    private static List<File> toList(final Stream<File> stream) {
3052        return stream.collect(Collectors.toList());
3053    }
3054
3055    /**
3056     * Converts whether or not to recurse into a recursion max depth.
3057     *
3058     * @param recursive whether or not to recurse
3059     * @return the recursion depth
3060     */
3061    private static int toMaxDepth(final boolean recursive) {
3062        return recursive ? Integer.MAX_VALUE : 1;
3063    }
3064
3065    /**
3066     * Converts an array of file extensions to suffixes.
3067     *
3068     * @param extensions an array of extensions. Format: {"java", "xml"}
3069     * @return an array of suffixes. Format: {".java", ".xml"}
3070     * @throws NullPointerException if the parameter is null
3071     */
3072    private static String[] toSuffixes(final String... extensions) {
3073        return Stream.of(Objects.requireNonNull(extensions, "extensions")).map(e -> "." + e).toArray(String[]::new);
3074    }
3075
3076    /**
3077     * Implements behavior similar to the Unix "touch" utility. Creates a new file with size 0, or, if the file exists, just
3078     * updates the file's modified time.
3079     * <p>
3080     * NOTE: As from v1.3, this method throws an IOException if the last modified date of the file cannot be set. Also, as
3081     * from v1.3 this method creates parent directories if they do not exist.
3082     * </p>
3083     *
3084     * @param file the File to touch.
3085     * @throws NullPointerException if the parameter is {@code null}.
3086     * @throws IOException if setting the last-modified time failed or an I/O problem occurs.
3087     */
3088    public static void touch(final File file) throws IOException {
3089        PathUtils.touch(Objects.requireNonNull(file, "file").toPath());
3090    }
3091
3092    /**
3093     * Converts each of an array of {@link File} to a {@link URL}.
3094     * <p>
3095     * Returns an array of the same size as the input.
3096     * </p>
3097     *
3098     * @param files the files to convert, must not be {@code null}
3099     * @return an array of URLs matching the input
3100     * @throws IOException          if a file cannot be converted
3101     * @throws NullPointerException if the parameter is null
3102     */
3103    public static URL[] toURLs(final File... files) throws IOException {
3104        Objects.requireNonNull(files, "files");
3105        final URL[] urls = new URL[files.length];
3106        for (int i = 0; i < urls.length; i++) {
3107            urls[i] = files[i].toURI().toURL();
3108        }
3109        return urls;
3110    }
3111
3112    /**
3113     * Validates the given arguments.
3114     * <ul>
3115     * <li>Throws {@link NullPointerException} if {@code source} is null</li>
3116     * <li>Throws {@link NullPointerException} if {@code destination} is null</li>
3117     * <li>Throws {@link FileNotFoundException} if {@code source} does not exist</li>
3118     * </ul>
3119     *
3120     * @param source      the file or directory to be moved.
3121     * @param destination the destination file or directory.
3122     * @throws NullPointerException if any of the given {@link File}s are {@code null}.
3123     * @throws FileNotFoundException if the source file does not exist.
3124     */
3125    private static void validateMoveParameters(final File source, final File destination) throws FileNotFoundException {
3126        Objects.requireNonNull(source, "source");
3127        Objects.requireNonNull(destination, "destination");
3128        if (!source.exists()) {
3129            throw new FileNotFoundException("Source '" + source + "' does not exist");
3130        }
3131    }
3132
3133    /**
3134     * Waits for the file system to propagate a file creation, with a timeout.
3135     * <p>
3136     * This method repeatedly tests {@link Files#exists(Path, LinkOption...)} until it returns
3137     * true up to the maximum time specified in seconds.
3138     * </p>
3139     *
3140     * @param file    the file to check, must not be {@code null}
3141     * @param seconds the maximum time in seconds to wait
3142     * @return true if file exists
3143     * @throws NullPointerException if the file is {@code null}
3144     */
3145    public static boolean waitFor(final File file, final int seconds) {
3146        Objects.requireNonNull(file, "file");
3147        return PathUtils.waitFor(file.toPath(), Duration.ofSeconds(seconds), PathUtils.EMPTY_LINK_OPTION_ARRAY);
3148    }
3149
3150    /**
3151     * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM.
3152     *
3153     * @param file the file to write
3154     * @param data the content to write to the file
3155     * @throws IOException in case of an I/O error
3156     * @since 2.0
3157     * @deprecated 2.5 use {@link #write(File, CharSequence, Charset)} instead (and specify the appropriate encoding)
3158     */
3159    @Deprecated
3160    public static void write(final File file, final CharSequence data) throws IOException {
3161        write(file, data, Charset.defaultCharset(), false);
3162    }
3163
3164    /**
3165     * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM.
3166     *
3167     * @param file   the file to write
3168     * @param data   the content to write to the file
3169     * @param append if {@code true}, then the data will be added to the
3170     *               end of the file rather than overwriting
3171     * @throws IOException in case of an I/O error
3172     * @since 2.1
3173     * @deprecated 2.5 use {@link #write(File, CharSequence, Charset, boolean)} instead (and specify the appropriate encoding)
3174     */
3175    @Deprecated
3176    public static void write(final File file, final CharSequence data, final boolean append) throws IOException {
3177        write(file, data, Charset.defaultCharset(), append);
3178    }
3179
3180    /**
3181     * Writes a CharSequence to a file creating the file if it does not exist.
3182     *
3183     * @param file     the file to write
3184     * @param data     the content to write to the file
3185     * @param charset the name of the requested charset, {@code null} means platform default
3186     * @throws IOException in case of an I/O error
3187     * @since 2.3
3188     */
3189    public static void write(final File file, final CharSequence data, final Charset charset) throws IOException {
3190        write(file, data, charset, false);
3191    }
3192
3193    /**
3194     * Writes a CharSequence to a file creating the file if it does not exist.
3195     *
3196     * @param file     the file to write
3197     * @param data     the content to write to the file
3198     * @param charset the charset to use, {@code null} means platform default
3199     * @param append   if {@code true}, then the data will be added to the
3200     *                 end of the file rather than overwriting
3201     * @throws IOException in case of an I/O error
3202     * @since 2.3
3203     */
3204    public static void write(final File file, final CharSequence data, final Charset charset, final boolean append) throws IOException {
3205        writeStringToFile(file, Objects.toString(data, null), charset, append);
3206    }
3207
3208    /**
3209     * Writes a CharSequence to a file creating the file if it does not exist.
3210     *
3211     * @param file     the file to write
3212     * @param data     the content to write to the file
3213     * @param charsetName the name of the requested charset, {@code null} means platform default
3214     * @throws IOException                          in case of an I/O error
3215     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3216     * @since 2.0
3217     */
3218    public static void write(final File file, final CharSequence data, final String charsetName) throws IOException {
3219        write(file, data, charsetName, false);
3220    }
3221
3222    /**
3223     * Writes a CharSequence to a file creating the file if it does not exist.
3224     *
3225     * @param file     the file to write
3226     * @param data     the content to write to the file
3227     * @param charsetName the name of the requested charset, {@code null} means platform default
3228     * @param append   if {@code true}, then the data will be added to the
3229     *                 end of the file rather than overwriting
3230     * @throws IOException                 in case of an I/O error
3231     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io
3232     * .UnsupportedEncodingException} in version 2.2 if the encoding is not supported by the VM
3233     * @since 2.1
3234     */
3235    public static void write(final File file, final CharSequence data, final String charsetName, final boolean append) throws IOException {
3236        write(file, data, Charsets.toCharset(charsetName), append);
3237    }
3238
3239    // Must be called with a directory
3240
3241    /**
3242     * Writes a byte array to a file creating the file if it does not exist.
3243     * <p>
3244     * NOTE: As from v1.3, the parent directories of the file will be created
3245     * if they do not exist.
3246     * </p>
3247     *
3248     * @param file the file to write to
3249     * @param data the content to write to the file
3250     * @throws IOException in case of an I/O error
3251     * @since 1.1
3252     */
3253    public static void writeByteArrayToFile(final File file, final byte[] data) throws IOException {
3254        writeByteArrayToFile(file, data, false);
3255    }
3256
3257    /**
3258     * Writes a byte array to a file creating the file if it does not exist.
3259     *
3260     * @param file   the file to write to
3261     * @param data   the content to write to the file
3262     * @param append if {@code true}, then bytes will be added to the
3263     *               end of the file rather than overwriting
3264     * @throws IOException in case of an I/O error
3265     * @since 2.1
3266     */
3267    public static void writeByteArrayToFile(final File file, final byte[] data, final boolean append) throws IOException {
3268        writeByteArrayToFile(file, data, 0, data.length, append);
3269    }
3270
3271    /**
3272     * Writes {@code len} bytes from the specified byte array starting
3273     * at offset {@code off} to a file, creating the file if it does
3274     * not exist.
3275     *
3276     * @param file the file to write to
3277     * @param data the content to write to the file
3278     * @param off  the start offset in the data
3279     * @param len  the number of bytes to write
3280     * @throws IOException in case of an I/O error
3281     * @since 2.5
3282     */
3283    public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len) throws IOException {
3284        writeByteArrayToFile(file, data, off, len, false);
3285    }
3286
3287    /**
3288     * Writes {@code len} bytes from the specified byte array starting
3289     * at offset {@code off} to a file, creating the file if it does
3290     * not exist.
3291     *
3292     * @param file   the file to write to
3293     * @param data   the content to write to the file
3294     * @param off    the start offset in the data
3295     * @param len    the number of bytes to write
3296     * @param append if {@code true}, then bytes will be added to the
3297     *               end of the file rather than overwriting
3298     * @throws IOException in case of an I/O error
3299     * @since 2.5
3300     */
3301    public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len, final boolean append) throws IOException {
3302        try (OutputStream out = newOutputStream(file, append)) {
3303            out.write(data, off, len);
3304        }
3305    }
3306
3307    /**
3308     * Writes the {@code toString()} value of each item in a collection to
3309     * the specified {@link File} line by line.
3310     * The default VM encoding and the default line ending will be used.
3311     *
3312     * @param file  the file to write to
3313     * @param lines the lines to write, {@code null} entries produce blank lines
3314     * @throws IOException in case of an I/O error
3315     * @since 1.3
3316     */
3317    public static void writeLines(final File file, final Collection<?> lines) throws IOException {
3318        writeLines(file, null, lines, null, false);
3319    }
3320
3321    /**
3322     * Writes the {@code toString()} value of each item in a collection to
3323     * the specified {@link File} line by line.
3324     * The default VM encoding and the default line ending will be used.
3325     *
3326     * @param file   the file to write to
3327     * @param lines  the lines to write, {@code null} entries produce blank lines
3328     * @param append if {@code true}, then the lines will be added to the
3329     *               end of the file rather than overwriting
3330     * @throws IOException in case of an I/O error
3331     * @since 2.1
3332     */
3333    public static void writeLines(final File file, final Collection<?> lines, final boolean append) throws IOException {
3334        writeLines(file, null, lines, null, append);
3335    }
3336
3337    /**
3338     * Writes the {@code toString()} value of each item in a collection to
3339     * the specified {@link File} line by line.
3340     * The default VM encoding and the specified line ending will be used.
3341     *
3342     * @param file       the file to write to
3343     * @param lines      the lines to write, {@code null} entries produce blank lines
3344     * @param lineEnding the line separator to use, {@code null} is system default
3345     * @throws IOException in case of an I/O error
3346     * @since 1.3
3347     */
3348    public static void writeLines(final File file, final Collection<?> lines, final String lineEnding) throws IOException {
3349        writeLines(file, null, lines, lineEnding, false);
3350    }
3351
3352    /**
3353     * Writes the {@code toString()} value of each item in a collection to
3354     * the specified {@link File} line by line.
3355     * The default VM encoding and the specified line ending will be used.
3356     *
3357     * @param file       the file to write to
3358     * @param lines      the lines to write, {@code null} entries produce blank lines
3359     * @param lineEnding the line separator to use, {@code null} is system default
3360     * @param append     if {@code true}, then the lines will be added to the
3361     *                   end of the file rather than overwriting
3362     * @throws IOException in case of an I/O error
3363     * @since 2.1
3364     */
3365    public static void writeLines(final File file, final Collection<?> lines, final String lineEnding, final boolean append) throws IOException {
3366        writeLines(file, null, lines, lineEnding, append);
3367    }
3368
3369    /**
3370     * Writes the {@code toString()} value of each item in a collection to
3371     * the specified {@link File} line by line.
3372     * The specified character encoding and the default line ending will be used.
3373     * <p>
3374     * NOTE: As from v1.3, the parent directories of the file will be created
3375     * if they do not exist.
3376     * </p>
3377     *
3378     * @param file     the file to write to
3379     * @param charsetName the name of the requested charset, {@code null} means platform default
3380     * @param lines    the lines to write, {@code null} entries produce blank lines
3381     * @throws IOException                          in case of an I/O error
3382     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3383     * @since 1.1
3384     */
3385    public static void writeLines(final File file, final String charsetName, final Collection<?> lines) throws IOException {
3386        writeLines(file, charsetName, lines, null, false);
3387    }
3388
3389    /**
3390     * Writes the {@code toString()} value of each item in a collection to
3391     * the specified {@link File} line by line, optionally appending.
3392     * The specified character encoding and the default line ending will be used.
3393     *
3394     * @param file     the file to write to
3395     * @param charsetName the name of the requested charset, {@code null} means platform default
3396     * @param lines    the lines to write, {@code null} entries produce blank lines
3397     * @param append   if {@code true}, then the lines will be added to the
3398     *                 end of the file rather than overwriting
3399     * @throws IOException                          in case of an I/O error
3400     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3401     * @since 2.1
3402     */
3403    public static void writeLines(final File file, final String charsetName, final Collection<?> lines, final boolean append) throws IOException {
3404        writeLines(file, charsetName, lines, null, append);
3405    }
3406
3407    /**
3408     * Writes the {@code toString()} value of each item in a collection to
3409     * the specified {@link File} line by line.
3410     * The specified character encoding and the line ending will be used.
3411     * <p>
3412     * NOTE: As from v1.3, the parent directories of the file will be created
3413     * if they do not exist.
3414     * </p>
3415     *
3416     * @param file       the file to write to
3417     * @param charsetName   the name of the requested charset, {@code null} means platform default
3418     * @param lines      the lines to write, {@code null} entries produce blank lines
3419     * @param lineEnding the line separator to use, {@code null} is system default
3420     * @throws IOException                          in case of an I/O error
3421     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3422     * @since 1.1
3423     */
3424    public static void writeLines(final File file, final String charsetName, final Collection<?> lines, final String lineEnding) throws IOException {
3425        writeLines(file, charsetName, lines, lineEnding, false);
3426    }
3427
3428    /**
3429     * Writes the {@code toString()} value of each item in a collection to
3430     * the specified {@link File} line by line.
3431     * The specified character encoding and the line ending will be used.
3432     *
3433     * @param file       the file to write to
3434     * @param charsetName   the name of the requested charset, {@code null} means platform default
3435     * @param lines      the lines to write, {@code null} entries produce blank lines
3436     * @param lineEnding the line separator to use, {@code null} is system default
3437     * @param append     if {@code true}, then the lines will be added to the
3438     *                   end of the file rather than overwriting
3439     * @throws IOException                          in case of an I/O error
3440     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3441     * @since 2.1
3442     */
3443    public static void writeLines(final File file, final String charsetName, final Collection<?> lines, final String lineEnding, final boolean append)
3444        throws IOException {
3445        try (OutputStream out = new BufferedOutputStream(newOutputStream(file, append))) {
3446            IOUtils.writeLines(lines, lineEnding, out, charsetName);
3447        }
3448    }
3449
3450    /**
3451     * Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
3452     *
3453     * @param file the file to write
3454     * @param data the content to write to the file
3455     * @throws IOException in case of an I/O error
3456     * @deprecated 2.5 use {@link #writeStringToFile(File, String, Charset)} instead (and specify the appropriate encoding)
3457     */
3458    @Deprecated
3459    public static void writeStringToFile(final File file, final String data) throws IOException {
3460        writeStringToFile(file, data, Charset.defaultCharset(), false);
3461    }
3462
3463    /**
3464     * Writes a String to a file creating the file if it does not exist using the default encoding for the VM.
3465     *
3466     * @param file   the file to write
3467     * @param data   the content to write to the file
3468     * @param append if {@code true}, then the String will be added to the
3469     *               end of the file rather than overwriting
3470     * @throws IOException in case of an I/O error
3471     * @since 2.1
3472     * @deprecated 2.5 use {@link #writeStringToFile(File, String, Charset, boolean)} instead (and specify the appropriate encoding)
3473     */
3474    @Deprecated
3475    public static void writeStringToFile(final File file, final String data, final boolean append) throws IOException {
3476        writeStringToFile(file, data, Charset.defaultCharset(), append);
3477    }
3478
3479    /**
3480     * Writes a String to a file creating the file if it does not exist.
3481     * <p>
3482     * NOTE: As from v1.3, the parent directories of the file will be created
3483     * if they do not exist.
3484     * </p>
3485     *
3486     * @param file     the file to write
3487     * @param data     the content to write to the file
3488     * @param charset the charset to use, {@code null} means platform default
3489     * @throws IOException                          in case of an I/O error
3490     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3491     * @since 2.4
3492     */
3493    public static void writeStringToFile(final File file, final String data, final Charset charset) throws IOException {
3494        writeStringToFile(file, data, charset, false);
3495    }
3496
3497    /**
3498     * Writes a String to a file creating the file if it does not exist.
3499     *
3500     * @param file     the file to write
3501     * @param data     the content to write to the file
3502     * @param charset the charset to use, {@code null} means platform default
3503     * @param append   if {@code true}, then the String will be added to the
3504     *                 end of the file rather than overwriting
3505     * @throws IOException in case of an I/O error
3506     * @since 2.3
3507     */
3508    public static void writeStringToFile(final File file, final String data, final Charset charset, final boolean append) throws IOException {
3509        try (OutputStream out = newOutputStream(file, append)) {
3510            IOUtils.write(data, out, charset);
3511        }
3512    }
3513
3514    /**
3515     * Writes a String to a file creating the file if it does not exist.
3516     * <p>
3517     * NOTE: As from v1.3, the parent directories of the file will be created
3518     * if they do not exist.
3519     * </p>
3520     *
3521     * @param file     the file to write
3522     * @param data     the content to write to the file
3523     * @param charsetName the name of the requested charset, {@code null} means platform default
3524     * @throws IOException                          in case of an I/O error
3525     * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
3526     */
3527    public static void writeStringToFile(final File file, final String data, final String charsetName) throws IOException {
3528        writeStringToFile(file, data, charsetName, false);
3529    }
3530
3531    /**
3532     * Writes a String to a file creating the file if it does not exist.
3533     *
3534     * @param file     the file to write
3535     * @param data     the content to write to the file
3536     * @param charsetName the name of the requested charset, {@code null} means platform default
3537     * @param append   if {@code true}, then the String will be added to the
3538     *                 end of the file rather than overwriting
3539     * @throws IOException                 in case of an I/O error
3540     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io
3541     * .UnsupportedEncodingException} in version 2.2 if the encoding is not supported by the VM
3542     * @since 2.1
3543     */
3544    public static void writeStringToFile(final File file, final String data, final String charsetName, final boolean append) throws IOException {
3545        writeStringToFile(file, data, Charsets.toCharset(charsetName), append);
3546    }
3547
3548    /**
3549     * Instances should NOT be constructed in standard programming.
3550     * @deprecated Will be private in 3.0.
3551     */
3552    @Deprecated
3553    public FileUtils() { //NOSONAR
3554
3555    }
3556
3557}