    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 */
    017
    018package org.apache.commons.io.file;
    019
    020import java.io.File;
    021import java.io.IOException;
    022import java.io.InputStream;
    023import java.io.OutputStream;
    024import java.io.RandomAccessFile;
    025import java.math.BigInteger;
    026import java.net.URI;
    027import java.net.URISyntaxException;
    028import java.net.URL;
    029import java.nio.charset.Charset;
    030import java.nio.file.AccessDeniedException;
    031import java.nio.file.CopyOption;
    032import java.nio.file.DirectoryStream;
    033import java.nio.file.FileVisitOption;
    034import java.nio.file.FileVisitResult;
    035import java.nio.file.FileVisitor;
    036import java.nio.file.Files;
    037import java.nio.file.LinkOption;
    038import java.nio.file.NoSuchFileException;
    039import java.nio.file.NotDirectoryException;
    040import java.nio.file.OpenOption;
    041import java.nio.file.Path;
    042import java.nio.file.Paths;
    043import java.nio.file.StandardOpenOption;
    044import java.nio.file.attribute.AclEntry;
    045import java.nio.file.attribute.AclFileAttributeView;
    046import java.nio.file.attribute.BasicFileAttributes;
    047import java.nio.file.attribute.DosFileAttributeView;
    048import java.nio.file.attribute.DosFileAttributes;
    049import java.nio.file.attribute.FileAttribute;
    050import java.nio.file.attribute.FileTime;
    051import java.nio.file.attribute.PosixFileAttributeView;
    052import java.nio.file.attribute.PosixFileAttributes;
    053import java.nio.file.attribute.PosixFilePermission;
    054import java.time.Duration;
    055import java.time.Instant;
    056import java.time.chrono.ChronoZonedDateTime;
    057import java.util.ArrayList;
    058import java.util.Arrays;
    059import java.util.Collection;
    060import java.util.Collections;
    061import java.util.Comparator;
    062import java.util.EnumSet;
    063import java.util.List;
    064import java.util.Objects;
    065import java.util.Set;
    066import java.util.stream.Collector;
    067import java.util.stream.Collectors;
    068import java.util.stream.Stream;
    069
    070import org.apache.commons.io.Charsets;
    071import org.apache.commons.io.FileUtils;
    072import org.apache.commons.io.FilenameUtils;
    073import org.apache.commons.io.IOUtils;
    074import org.apache.commons.io.RandomAccessFileMode;
    075import org.apache.commons.io.RandomAccessFiles;
    076import org.apache.commons.io.ThreadUtils;
    077import org.apache.commons.io.file.Counters.PathCounters;
    078import org.apache.commons.io.file.attribute.FileTimes;
    079import org.apache.commons.io.filefilter.IOFileFilter;
    080import org.apache.commons.io.function.IOFunction;
    081import org.apache.commons.io.function.IOSupplier;
    082
    083/**
    084 * NIO Path utilities.
    085 *
    086 * @since 2.7
    087 */
    088public final class PathUtils {
    089
    090    /**
    091     * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted relative lists when comparing directories.
    092     */
    093    private static final class RelativeSortedPaths {
    094
    095        final boolean equals;
    096        // final List<Path> relativeDirList1; // might need later?
    097        // final List<Path> relativeDirList2; // might need later?
    098        final List<Path> relativeFileList1;
    099        final List<Path> relativeFileList2;
    100
    101        /**
    102         * Constructs and initializes a new instance by accumulating directory and file info.
    103         *
    104         * @param dir1             First directory to compare.
    105         * @param dir2             Seconds directory to compare.
    106         * @param maxDepth         See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    107         * @param linkOptions      Options indicating how symbolic links are handled.
    108         * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    109         * @throws IOException if an I/O error is thrown by a visitor method.
    110         */
    111        private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth, final LinkOption[] linkOptions,
    112                final FileVisitOption[] fileVisitOptions) throws IOException {
    113            final List<Path> tmpRelativeDirList1;
    114            final List<Path> tmpRelativeDirList2;
    115            List<Path> tmpRelativeFileList1 = null;
    116            List<Path> tmpRelativeFileList2 = null;
    117            if (dir1 == null && dir2 == null) {
    118                equals = true;
    119            } else if (dir1 == null ^ dir2 == null) {
    120                equals = false;
    121            } else {
    122                final boolean parentDirNotExists1 = Files.notExists(dir1, linkOptions);
    123                final boolean parentDirNotExists2 = Files.notExists(dir2, linkOptions);
    124                if (parentDirNotExists1 || parentDirNotExists2) {
    125                    equals = parentDirNotExists1 && parentDirNotExists2;
    126                } else {
    127                    final AccumulatorPathVisitor visitor1 = accumulate(dir1, maxDepth, fileVisitOptions);
    128                    final AccumulatorPathVisitor visitor2 = accumulate(dir2, maxDepth, fileVisitOptions);
    129                    if (visitor1.getDirList().size() != visitor2.getDirList().size() || visitor1.getFileList().size() != visitor2.getFileList().size()) {
    130                        equals = false;
    131                    } else {
    132                        tmpRelativeDirList1 = visitor1.relativizeDirectories(dir1, true, null);
    133                        tmpRelativeDirList2 = visitor2.relativizeDirectories(dir2, true, null);
    134                        if (!tmpRelativeDirList1.equals(tmpRelativeDirList2)) {
    135                            equals = false;
    136                        } else {
    137                            tmpRelativeFileList1 = visitor1.relativizeFiles(dir1, true, null);
    138                            tmpRelativeFileList2 = visitor2.relativizeFiles(dir2, true, null);
    139                            equals = tmpRelativeFileList1.equals(tmpRelativeFileList2);
    140                        }
    141                    }
    142                }
    143            }
    144            // relativeDirList1 = tmpRelativeDirList1;
    145            // relativeDirList2 = tmpRelativeDirList2;
    146            relativeFileList1 = tmpRelativeFileList1;
    147            relativeFileList2 = tmpRelativeFileList2;
    148        }
    149    }
    150
    151    private static final OpenOption[] OPEN_OPTIONS_TRUNCATE = { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING };
    152
    153    private static final OpenOption[] OPEN_OPTIONS_APPEND = { StandardOpenOption.CREATE, StandardOpenOption.APPEND };
    154
    155    /**
    156     * Empty {@link CopyOption} array.
    157     *
    158     * @since 2.8.0
    159     */
    160    public static final CopyOption[] EMPTY_COPY_OPTIONS = {};
    161
    162    /**
    163     * Empty {@link DeleteOption} array.
    164     *
    165     * @since 2.8.0
    166     */
    167    public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = {};
    168
    169    /**
    170     * Empty {@link FileAttribute} array.
    171     *
    172     * @since 2.13.0
    173     */
    174    public static final FileAttribute<?>[] EMPTY_FILE_ATTRIBUTE_ARRAY = {};
    175
    176    /**
    177     * Empty {@link FileVisitOption} array.
    178     */
    179    public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = {};
    180
    181    /**
    182     * Empty {@link LinkOption} array.
    183     */
    184    public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = {};
    185
    186    /**
    187     * {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
    188     *
    189     * @since 2.9.0
    190     * @deprecated Use {@link #noFollowLinkOptionArray()}.
    191     */
    192    @Deprecated
    193    public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = { LinkOption.NOFOLLOW_LINKS };
    194
    195    /**
    196     * A LinkOption used to follow link in this class, the inverse of {@link LinkOption#NOFOLLOW_LINKS}.
    197     *
    198     * @since 2.12.0
    199     */
    200    static final LinkOption NULL_LINK_OPTION = null;
    201
    202    /**
    203     * Empty {@link OpenOption} array.
    204     */
    205    public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = {};
    206
    207    /**
    208     * Empty {@link Path} array.
    209     *
    210     * @since 2.9.0
    211     */
    212    public static final Path[] EMPTY_PATH_ARRAY = {};
    213
    214    /**
    215     * Accumulates file tree information in a {@link AccumulatorPathVisitor}.
    216     *
    217     * @param directory        The directory to accumulate information.
    218     * @param maxDepth         See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    219     * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    220     * @throws IOException if an I/O error is thrown by a visitor method.
    221     * @return file tree information.
    222     */
    223    private static AccumulatorPathVisitor accumulate(final Path directory, final int maxDepth, final FileVisitOption[] fileVisitOptions) throws IOException {
    224        return visitFileTree(AccumulatorPathVisitor.withLongCounters(), directory, toFileVisitOptionSet(fileVisitOptions), maxDepth);
    225    }
    226
    227    /**
    228     * Cleans a directory including subdirectories without deleting directories.
    229     *
    230     * @param directory directory to clean.
    231     * @return The visitation path counters.
    232     * @throws IOException if an I/O error is thrown by a visitor method.
    233     */
    234    public static PathCounters cleanDirectory(final Path directory) throws IOException {
    235        return cleanDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
    236    }
    237
    238    /**
    239     * Cleans a directory including subdirectories without deleting directories.
    240     *
    241     * @param directory     directory to clean.
    242     * @param deleteOptions How to handle deletion.
    243     * @return The visitation path counters.
    244     * @throws IOException if an I/O error is thrown by a visitor method.
    245     * @since 2.8.0
    246     */
    247    public static PathCounters cleanDirectory(final Path directory, final DeleteOption... deleteOptions) throws IOException {
    248        return visitFileTree(new CleaningPathVisitor(Counters.longPathCounters(), deleteOptions), directory).getPathCounters();
    249    }
    250
    251    /**
    252     * Compares the given {@link Path}'s last modified time to the given file time.
    253     *
    254     * @param file     the {@link Path} to test.
    255     * @param fileTime the time reference.
    256     * @param options  options indicating how to handle symbolic links.
    257     * @return See {@link FileTime#compareTo(FileTime)}
    258     * @throws IOException          if an I/O error occurs.
    259     * @throws NullPointerException if the file is {@code null}.
    260     */
    261    private static int compareLastModifiedTimeTo(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
    262        return getLastModifiedTime(file, options).compareTo(fileTime);
    263    }
    264
    265    /**
    266     * Copies the InputStream from the supplier with {@link Files#copy(InputStream, Path, CopyOption...)}.
    267     *
    268     * @param in          Supplies the InputStream.
    269     * @param target      See {@link Files#copy(InputStream, Path, CopyOption...)}.
    270     * @param copyOptions See {@link Files#copy(InputStream, Path, CopyOption...)}.
    271     * @return See {@link Files#copy(InputStream, Path, CopyOption...)}
    272     * @throws IOException See {@link Files#copy(InputStream, Path, CopyOption...)}
    273     * @since 2.12.0
    274     */
    275    public static long copy(final IOSupplier<InputStream> in, final Path target, final CopyOption... copyOptions) throws IOException {
    276        try (InputStream inputStream = in.get()) {
    277            return Files.copy(inputStream, target, copyOptions);
    278        }
    279    }
    280
    281    /**
    282     * Copies a directory to another directory.
    283     *
    284     * @param sourceDirectory The source directory.
    285     * @param targetDirectory The target directory.
    286     * @param copyOptions     Specifies how the copying should be done.
    287     * @return The visitation path counters.
    288     * @throws IOException if an I/O error is thrown by a visitor method.
    289     */
    290    public static PathCounters copyDirectory(final Path sourceDirectory, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
    291        final Path absoluteSource = sourceDirectory.toAbsolutePath();
    292        return visitFileTree(new CopyDirectoryVisitor(Counters.longPathCounters(), absoluteSource, targetDirectory, copyOptions), absoluteSource)
    293                .getPathCounters();
    294    }
    295
    296    /**
    297     * Copies a URL to a directory.
    298     *
    299     * @param sourceFile  The source URL.
    300     * @param targetFile  The target file.
    301     * @param copyOptions Specifies how the copying should be done.
    302     * @return The target file
    303     * @throws IOException if an I/O error occurs.
    304     * @see Files#copy(InputStream, Path, CopyOption...)
    305     */
    306    public static Path copyFile(final URL sourceFile, final Path targetFile, final CopyOption... copyOptions) throws IOException {
    307        copy(sourceFile::openStream, targetFile, copyOptions);
    308        return targetFile;
    309    }
    310
    311    /**
    312     * Copies a file to a directory.
    313     *
    314     * @param sourceFile      The source file.
    315     * @param targetDirectory The target directory.
    316     * @param copyOptions     Specifies how the copying should be done.
    317     * @return The target file
    318     * @throws IOException if an I/O error occurs.
    319     * @see Files#copy(Path, Path, CopyOption...)
    320     */
    321    public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
    322        return Files.copy(sourceFile, targetDirectory.resolve(sourceFile.getFileName()), copyOptions);
    323    }
    324
    325    /**
    326     * Copies a URL to a directory.
    327     *
    328     * @param sourceFile      The source URL.
    329     * @param targetDirectory The target directory.
    330     * @param copyOptions     Specifies how the copying should be done.
    331     * @return The target file
    332     * @throws IOException if an I/O error occurs.
    333     * @see Files#copy(InputStream, Path, CopyOption...)
    334     */
    335    public static Path copyFileToDirectory(final URL sourceFile, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
    336        final Path resolve = targetDirectory.resolve(FilenameUtils.getName(sourceFile.getFile()));
    337        copy(sourceFile::openStream, resolve, copyOptions);
    338        return resolve;
    339    }
    340
    341    /**
    342     * Counts aspects of a directory including subdirectories.
    343     *
    344     * @param directory directory to delete.
    345     * @return The visitor used to count the given directory.
    346     * @throws IOException if an I/O error is thrown by a visitor method.
    347     */
    348    public static PathCounters countDirectory(final Path directory) throws IOException {
    349        return visitFileTree(CountingPathVisitor.withLongCounters(), directory).getPathCounters();
    350    }
    351
    352    /**
    353     * Counts aspects of a directory including subdirectories.
    354     *
    355     * @param directory directory to count.
    356     * @return The visitor used to count the given directory.
    357     * @throws IOException if an I/O error occurs.
    358     * @since 2.12.0
    359     */
    360    public static PathCounters countDirectoryAsBigInteger(final Path directory) throws IOException {
    361        return visitFileTree(CountingPathVisitor.withBigIntegerCounters(), directory).getPathCounters();
    362    }
    363
    364    /**
    365     * Creates the parent directories for the given {@code path}.
    366     * <p>
    367     * If the parent directory already exists, then return it.
    368     * </p>
    369     *
    370     * @param path  The path to a file (or directory).
    371     * @param attrs An optional list of file attributes to set atomically when creating the directories.
    372     * @return The Path for the {@code path}'s parent directory or null if the given path has no parent.
    373     * @throws IOException if an I/O error occurs.
    374     * @since 2.9.0
    375     */
    376    public static Path createParentDirectories(final Path path, final FileAttribute<?>... attrs) throws IOException {
    377        return createParentDirectories(path, LinkOption.NOFOLLOW_LINKS, attrs);
    378    }
    379
    380    /**
    381     * Creates the parent directories for the given {@code path}.
    382     * <p>
    383     * If the parent directory already exists, then return it.
    384     * </p>
    385     *
    386     * @param path       The path to a file (or directory).
    387     * @param linkOption A {@link LinkOption} or null.
    388     * @param attrs      An optional list of file attributes to set atomically when creating the directories.
    389     * @return The Path for the {@code path}'s parent directory or null if the given path has no parent.
    390     * @throws IOException if an I/O error occurs.
    391     * @since 2.12.0
    392     */
    393    public static Path createParentDirectories(final Path path, final LinkOption linkOption, final FileAttribute<?>... attrs) throws IOException {
    394        Path parent = getParent(path);
    395        parent = linkOption == LinkOption.NOFOLLOW_LINKS ? parent : readIfSymbolicLink(parent);
    396        if (parent == null) {
    397            return null;
    398        }
    399        final boolean exists = linkOption == null ? Files.exists(parent) : Files.exists(parent, linkOption);
    400        return exists ? parent : Files.createDirectories(parent, attrs);
    401    }
    402
    403    /**
    404     * Gets the current directory.
    405     *
    406     * @return the current directory.
    407     *
    408     * @since 2.9.0
    409     */
    410    public static Path current() {
    411        return Paths.get(".");
    412    }
    413
    414    /**
    415     * Deletes a file or directory. If the path is a directory, delete it and all subdirectories.
    416     * <p>
    417     * The difference between File.delete() and this method are:
    418     * </p>
    419     * <ul>
    420     * <li>A directory to delete does not have to be empty.</li>
    421     * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a boolean.
    422     * </ul>
    423     *
    424     * @param path file or directory to delete, must not be {@code null}
    425     * @return The visitor used to delete the given directory.
    426     * @throws NullPointerException if the directory is {@code null}
    427     * @throws IOException          if an I/O error is thrown by a visitor method or if an I/O error occurs.
    428     */
    429    public static PathCounters delete(final Path path) throws IOException {
    430        return delete(path, EMPTY_DELETE_OPTION_ARRAY);
    431    }
    432
    433    /**
    434     * Deletes a file or directory. If the path is a directory, delete it and all subdirectories.
    435     * <p>
    436     * The difference between File.delete() and this method are:
    437     * </p>
    438     * <ul>
    439     * <li>A directory to delete does not have to be empty.</li>
    440     * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a boolean.
    441     * </ul>
    442     *
    443     * @param path          file or directory to delete, must not be {@code null}
    444     * @param deleteOptions How to handle deletion.
    445     * @return The visitor used to delete the given directory.
    446     * @throws NullPointerException if the directory is {@code null}
    447     * @throws IOException          if an I/O error is thrown by a visitor method or if an I/O error occurs.
    448     * @since 2.8.0
    449     */
    450    public static PathCounters delete(final Path path, final DeleteOption... deleteOptions) throws IOException {
    451        // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
    452        return Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) ? deleteDirectory(path, deleteOptions) : deleteFile(path, deleteOptions);
    453    }
    454
    455    /**
    456     * Deletes a file or directory. If the path is a directory, delete it and all subdirectories.
    457     * <p>
    458     * The difference between File.delete() and this method are:
    459     * </p>
    460     * <ul>
    461     * <li>A directory to delete does not have to be empty.</li>
    462     * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a boolean.
    463     * </ul>
    464     *
    465     * @param path          file or directory to delete, must not be {@code null}
    466     * @param linkOptions   How to handle symbolic links.
    467     * @param deleteOptions How to handle deletion.
    468     * @return The visitor used to delete the given directory.
    469     * @throws NullPointerException if the directory is {@code null}
    470     * @throws IOException          if an I/O error is thrown by a visitor method or if an I/O error occurs.
    471     * @since 2.9.0
    472     */
    473    public static PathCounters delete(final Path path, final LinkOption[] linkOptions, final DeleteOption... deleteOptions) throws IOException {
    474        // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
    475        return Files.isDirectory(path, linkOptions) ? deleteDirectory(path, linkOptions, deleteOptions) : deleteFile(path, linkOptions, deleteOptions);
    476    }
    477
    478    /**
    479     * Deletes a directory including subdirectories.
    480     *
    481     * @param directory directory to delete.
    482     * @return The visitor used to delete the given directory.
    483     * @throws IOException if an I/O error is thrown by a visitor method.
    484     */
    485    public static PathCounters deleteDirectory(final Path directory) throws IOException {
    486        return deleteDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
    487    }
    488
    489    /**
    490     * Deletes a directory including subdirectories.
    491     *
    492     * @param directory     directory to delete.
    493     * @param deleteOptions How to handle deletion.
    494     * @return The visitor used to delete the given directory.
    495     * @throws IOException if an I/O error is thrown by a visitor method.
    496     * @since 2.8.0
    497     */
    498    public static PathCounters deleteDirectory(final Path directory, final DeleteOption... deleteOptions) throws IOException {
    499        final LinkOption[] linkOptions = PathUtils.noFollowLinkOptionArray();
    500        // POSIX ops will noop on non-POSIX.
    501        return withPosixFileAttributes(getParent(directory), linkOptions, overrideReadOnly(deleteOptions),
    502                pfa -> visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), directory).getPathCounters());
    503    }
    504
    505    /**
    506     * Deletes a directory including subdirectories.
    507     *
    508     * @param directory     directory to delete.
    509     * @param linkOptions   How to handle symbolic links.
    510     * @param deleteOptions How to handle deletion.
    511     * @return The visitor used to delete the given directory.
    512     * @throws IOException if an I/O error is thrown by a visitor method.
    513     * @since 2.9.0
    514     */
    515    public static PathCounters deleteDirectory(final Path directory, final LinkOption[] linkOptions, final DeleteOption... deleteOptions) throws IOException {
    516        return visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), directory).getPathCounters();
    517    }
    518
    519    /**
    520     * Deletes the given file.
    521     *
    522     * @param file The file to delete.
    523     * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
    524     * @throws IOException         if an I/O error occurs.
    525     * @throws NoSuchFileException if the file is a directory.
    526     */
    527    public static PathCounters deleteFile(final Path file) throws IOException {
    528        return deleteFile(file, EMPTY_DELETE_OPTION_ARRAY);
    529    }
    530
    531    /**
    532     * Deletes the given file.
    533     *
    534     * @param file          The file to delete.
    535     * @param deleteOptions How to handle deletion.
    536     * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
    537     * @throws IOException         if an I/O error occurs.
    538     * @throws NoSuchFileException if the file is a directory.
    539     * @since 2.8.0
    540     */
    541    public static PathCounters deleteFile(final Path file, final DeleteOption... deleteOptions) throws IOException {
    542        // Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files.
    543        return deleteFile(file, noFollowLinkOptionArray(), deleteOptions);
    544    }
    545
    546    /**
    547     * Deletes the given file.
    548     *
    549     * @param file          The file to delete.
    550     * @param linkOptions   How to handle symbolic links.
    551     * @param deleteOptions How to handle deletion.
    552     * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
    553     * @throws IOException         if an I/O error occurs.
    554     * @throws NoSuchFileException if the file is a directory.
    555     * @since 2.9.0
    556     */
    557    public static PathCounters deleteFile(final Path file, final LinkOption[] linkOptions, final DeleteOption... deleteOptions)
    558            throws NoSuchFileException, IOException {
    559        //
    560        // TODO Needs clean up
    561        //
    562        if (Files.isDirectory(file, linkOptions)) {
    563            throw new NoSuchFileException(file.toString());
    564        }
    565        final PathCounters pathCounts = Counters.longPathCounters();
    566        boolean exists = exists(file, linkOptions);
    567        long size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
    568        try {
    569            if (Files.deleteIfExists(file)) {
    570                pathCounts.getFileCounter().increment();
    571                pathCounts.getByteCounter().add(size);
    572                return pathCounts;
    573            }
    574        } catch (final AccessDeniedException ignored) {
    575            // Ignore and try again below.
    576        }
    577        final Path parent = getParent(file);
    578        PosixFileAttributes posixFileAttributes = null;
    579        try {
    580            if (overrideReadOnly(deleteOptions)) {
    581                posixFileAttributes = readPosixFileAttributes(parent, linkOptions);
    582                setReadOnly(file, false, linkOptions);
    583            }
    584            // Read size _after_ having read/execute access on POSIX.
    585            exists = exists(file, linkOptions);
    586            size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
    587            if (Files.deleteIfExists(file)) {
    588                pathCounts.getFileCounter().increment();
    589                pathCounts.getByteCounter().add(size);
    590            }
    591        } finally {
    592            if (posixFileAttributes != null) {
    593                Files.setPosixFilePermissions(parent, posixFileAttributes.permissions());
    594            }
    595        }
    596        return pathCounts;
    597    }
    598
    599    /**
    600     * Delegates to {@link File#deleteOnExit()}.
    601     *
    602     * @param path the path to delete.
    603     * @since 3.13.0
    604     */
    605    public static void deleteOnExit(final Path path) {
    606        Objects.requireNonNull(path.toFile()).deleteOnExit();
    607    }
    608
    609    /**
    610     * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The comparison includes all files in all
    611     * subdirectories.
    612     *
    613     * @param path1 The first directory.
    614     * @param path2 The second directory.
    615     * @return Whether the two directories contain the same files while considering file contents.
    616     * @throws IOException if an I/O error is thrown by a visitor method.
    617     */
    618    public static boolean directoryAndFileContentEquals(final Path path1, final Path path2) throws IOException {
    619        return directoryAndFileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY, EMPTY_FILE_VISIT_OPTION_ARRAY);
    620    }
    621
    622    /**
    623     * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The comparison includes all files in all
    624     * subdirectories.
    625     *
    626     * @param path1           The first directory.
    627     * @param path2           The second directory.
    628     * @param linkOptions     options to follow links.
    629     * @param openOptions     options to open files.
    630     * @param fileVisitOption options to configure traversal.
    631     * @return Whether the two directories contain the same files while considering file contents.
    632     * @throws IOException if an I/O error is thrown by a visitor method.
    633     */
    634    public static boolean directoryAndFileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, final OpenOption[] openOptions,
    635            final FileVisitOption[] fileVisitOption) throws IOException {
    636        // First walk both file trees and gather normalized paths.
    637        if (path1 == null && path2 == null) {
    638            return true;
    639        }
    640        if (path1 == null || path2 == null) {
    641            return false;
    642        }
    643        if (notExists(path1) && notExists(path2)) {
    644            return true;
    645        }
    646        final RelativeSortedPaths relativeSortedPaths = new RelativeSortedPaths(path1, path2, Integer.MAX_VALUE, linkOptions, fileVisitOption);
    647        // If the normalized path names and counts are not the same, no need to compare contents.
    648        if (!relativeSortedPaths.equals) {
    649            return false;
    650        }
    651        // Both visitors contain the same normalized paths, we can compare file contents.
    652        final List<Path> fileList1 = relativeSortedPaths.relativeFileList1;
    653        final List<Path> fileList2 = relativeSortedPaths.relativeFileList2;
    654        for (final Path path : fileList1) {
    655            final int binarySearch = Collections.binarySearch(fileList2, path);
    656            if (binarySearch <= -1) {
    657                throw new IllegalStateException("Unexpected mismatch.");
    658            }
    659            if (!fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) {
    660                return false;
    661            }
    662        }
    663        return true;
    664    }
    665
    666    /**
    667     * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The comparison includes all files in all
    668     * subdirectories.
    669     *
    670     * @param path1 The first directory.
    671     * @param path2 The second directory.
    672     * @return Whether the two directories contain the same files without considering file contents.
    673     * @throws IOException if an I/O error is thrown by a visitor method.
    674     */
    675    public static boolean directoryContentEquals(final Path path1, final Path path2) throws IOException {
    676        return directoryContentEquals(path1, path2, Integer.MAX_VALUE, EMPTY_LINK_OPTION_ARRAY, EMPTY_FILE_VISIT_OPTION_ARRAY);
    677    }
    678
    679    /**
    680     * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The comparison includes all files in all
    681     * subdirectories.
    682     *
    683     * @param path1            The first directory.
    684     * @param path2            The second directory.
    685     * @param maxDepth         See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    686     * @param linkOptions      options to follow links.
    687     * @param fileVisitOptions options to configure the traversal
    688     * @return Whether the two directories contain the same files without considering file contents.
    689     * @throws IOException if an I/O error is thrown by a visitor method.
    690     */
    691    public static boolean directoryContentEquals(final Path path1, final Path path2, final int maxDepth, final LinkOption[] linkOptions,
    692            final FileVisitOption[] fileVisitOptions) throws IOException {
    693        return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, fileVisitOptions).equals;
    694    }
    695
    696    private static boolean exists(final Path path, final LinkOption... options) {
    697        Objects.requireNonNull(path, "path");
    698        return options != null ? Files.exists(path, options) : Files.exists(path);
    699    }
    700
    701    /**
    702     * Compares the file contents of two Paths to determine if they are equal or not.
    703     * <p>
    704     * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
    705     * </p>
    706     *
    707     * @param path1 the first stream.
    708     * @param path2 the second stream.
    709     * @return true if the content of the streams are equal or they both don't exist, false otherwise.
    710     * @throws NullPointerException if either input is null.
    711     * @throws IOException          if an I/O error occurs.
    712     * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
    713     */
    714    public static boolean fileContentEquals(final Path path1, final Path path2) throws IOException {
    715        return fileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY);
    716    }
    717
    718    /**
    719     * Compares the file contents of two Paths to determine if they are equal or not.
    720     * <p>
    721     * File content is accessed through {@link RandomAccessFileMode#create(Path)}.
    722     * </p>
    723     *
    724     * @param path1       the first stream.
    725     * @param path2       the second stream.
    726     * @param linkOptions options specifying how files are followed.
    727     * @param openOptions ignored.
    728     * @return true if the content of the streams are equal or they both don't exist, false otherwise.
    729     * @throws NullPointerException if openOptions is null.
    730     * @throws IOException          if an I/O error occurs.
    731     * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
    732     */
    733    public static boolean fileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, final OpenOption[] openOptions)
    734            throws IOException {
    735        if (path1 == null && path2 == null) {
    736            return true;
    737        }
    738        if (path1 == null || path2 == null) {
    739            return false;
    740        }
    741        final Path nPath1 = path1.normalize();
    742        final Path nPath2 = path2.normalize();
    743        final boolean path1Exists = exists(nPath1, linkOptions);
    744        if (path1Exists != exists(nPath2, linkOptions)) {
    745            return false;
    746        }
    747        if (!path1Exists) {
    748            // Two not existing files are equal?
    749            // Same as FileUtils
    750            return true;
    751        }
    752        if (Files.isDirectory(nPath1, linkOptions)) {
    753            // don't compare directory contents.
    754            throw new IOException("Can't compare directories, only files: " + nPath1);
    755        }
    756        if (Files.isDirectory(nPath2, linkOptions)) {
    757            // don't compare directory contents.
    758            throw new IOException("Can't compare directories, only files: " + nPath2);
    759        }
    760        if (Files.size(nPath1) != Files.size(nPath2)) {
    761            // lengths differ, cannot be equal
    762            return false;
    763        }
    764        if (path1.equals(path2)) {
    765            // same file
    766            return true;
    767        }
    768        // Faster:
    769        try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(path1.toRealPath(linkOptions));
    770                RandomAccessFile raf2 = RandomAccessFileMode.READ_ONLY.create(path2.toRealPath(linkOptions))) {
    771            return RandomAccessFiles.contentEquals(raf1, raf2);
    772        } catch (UnsupportedOperationException e) {
    773            // Slower:
    774            // Handle
    775            // java.lang.UnsupportedOperationException
    776            // at com.sun.nio.zipfs.ZipPath.toFile(ZipPath.java:656)
    777            try (InputStream inputStream1 = Files.newInputStream(nPath1, openOptions);
    778                    InputStream inputStream2 = Files.newInputStream(nPath2, openOptions)) {
    779                return IOUtils.contentEquals(inputStream1, inputStream2);
    780            }
    781        }
    782    }
    783
    784    /**
    785     * <p>
    786     * Applies an {@link IOFileFilter} to the provided {@link File} objects. The resulting array is a subset of the original file list that matches the provided
    787     * filter.
    788     * </p>
    789     *
    790     * <p>
    791     * The {@link Set} returned by this method is not guaranteed to be thread safe.
    792     * </p>
    793     *
    794     * <pre>
    795     * Set&lt;File&gt; allFiles = ...
    796     * Set&lt;File&gt; javaFiles = FileFilterUtils.filterSet(allFiles,
    797     *     FileFilterUtils.suffixFileFilter(".java"));
    798     * </pre>
    799     *
    800     * @param filter the filter to apply to the set of files.
    801     * @param paths  the array of files to apply the filter to.
    802     *
    803     * @return a subset of {@code files} that is accepted by the file filter.
    804     * @throws NullPointerException     if the filter is {@code null}
    805     * @throws IllegalArgumentException if {@code files} contains a {@code null} value.
    806     *
    807     * @since 2.9.0
    808     */
    809    public static Path[] filter(final PathFilter filter, final Path... paths) {
    810        Objects.requireNonNull(filter, "filter");
    811        if (paths == null) {
    812            return EMPTY_PATH_ARRAY;
    813        }
    814        return filterPaths(filter, Stream.of(paths), Collectors.toList()).toArray(EMPTY_PATH_ARRAY);
    815    }
    816
    817    private static <R, A> R filterPaths(final PathFilter filter, final Stream<Path> stream, final Collector<? super Path, A, R> collector) {
    818        Objects.requireNonNull(filter, "filter");
    819        Objects.requireNonNull(collector, "collector");
    820        if (stream == null) {
    821            return Stream.<Path>empty().collect(collector);
    822        }
    823        return stream.filter(p -> {
    824            try {
    825                return p != null && filter.accept(p, readBasicFileAttributes(p)) == FileVisitResult.CONTINUE;
    826            } catch (final IOException e) {
    827                return false;
    828            }
    829        }).collect(collector);
    830    }
    831
    832    /**
    833     * Reads the access control list from a file attribute view.
    834     *
    835     * @param sourcePath the path to the file.
    836     * @return a file attribute view of the given type, or null if the attribute view type is not available.
    837     * @throws IOException if an I/O error occurs.
    838     * @since 2.8.0
    839     */
    840    public static List<AclEntry> getAclEntryList(final Path sourcePath) throws IOException {
    841        final AclFileAttributeView fileAttributeView = getAclFileAttributeView(sourcePath);
    842        return fileAttributeView == null ? null : fileAttributeView.getAcl();
    843    }
    844
    845    /**
    846     * Shorthand for {@code Files.getFileAttributeView(path, AclFileAttributeView.class)}.
    847     *
    848     * @param path    the path to the file.
    849     * @param options how to handle symbolic links.
    850     * @return a AclFileAttributeView, or {@code null} if the attribute view type is not available.
    851     * @since 2.12.0
    852     */
    853    public static AclFileAttributeView getAclFileAttributeView(final Path path, final LinkOption... options) {
    854        return Files.getFileAttributeView(path, AclFileAttributeView.class, options);
    855    }
    856
    857    /**
    858     * Shorthand for {@code Files.getFileAttributeView(path, DosFileAttributeView.class)}.
    859     *
    860     * @param path    the path to the file.
    861     * @param options how to handle symbolic links.
    862     * @return a DosFileAttributeView, or {@code null} if the attribute view type is not available.
    863     * @since 2.12.0
    864     */
    865    public static DosFileAttributeView getDosFileAttributeView(final Path path, final LinkOption... options) {
    866        return Files.getFileAttributeView(path, DosFileAttributeView.class, options);
    867    }
    868
    869    /**
    870     * Gets the file's last modified time or null if the file does not exist.
    871     * <p>
    872     * The method provides a workaround for bug <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a> where {@link File#lastModified()}
    873     * looses milliseconds and always ends in 000. This bug is in OpenJDK 8 and 9, and fixed in 11.
    874     * </p>
    875     *
    876     * @param file the file to query.
    877     * @return the file's last modified time.
    878     * @throws IOException Thrown if an I/O error occurs.
    879     * @since 2.12.0
    880     */
    881    public static FileTime getLastModifiedFileTime(final File file) throws IOException {
    882        return getLastModifiedFileTime(file.toPath(), null, EMPTY_LINK_OPTION_ARRAY);
    883    }
    884
    885    /**
    886     * Gets the file's last modified time or null if the file does not exist.
    887     *
    888     * @param path            the file to query.
    889     * @param defaultIfAbsent Returns this file time of the file does not exist, may be null.
    890     * @param options         options indicating how symbolic links are handled.
    891     * @return the file's last modified time.
    892     * @throws IOException Thrown if an I/O error occurs.
    893     * @since 2.12.0
    894     */
    895    public static FileTime getLastModifiedFileTime(final Path path, final FileTime defaultIfAbsent, final LinkOption... options) throws IOException {
    896        return Files.exists(path) ? getLastModifiedTime(path, options) : defaultIfAbsent;
    897    }
    898
    899    /**
    900     * Gets the file's last modified time or null if the file does not exist.
    901     *
    902     * @param path    the file to query.
    903     * @param options options indicating how symbolic links are handled.
    904     * @return the file's last modified time.
    905     * @throws IOException Thrown if an I/O error occurs.
    906     * @since 2.12.0
    907     */
    908    public static FileTime getLastModifiedFileTime(final Path path, final LinkOption... options) throws IOException {
    909        return getLastModifiedFileTime(path, null, options);
    910    }
    911
    912    /**
    913     * Gets the file's last modified time or null if the file does not exist.
    914     *
    915     * @param uri the file to query.
    916     * @return the file's last modified time.
    917     * @throws IOException Thrown if an I/O error occurs.
    918     * @since 2.12.0
    919     */
    920    public static FileTime getLastModifiedFileTime(final URI uri) throws IOException {
    921        return getLastModifiedFileTime(Paths.get(uri), null, EMPTY_LINK_OPTION_ARRAY);
    922    }
    923
    924    /**
    925     * Gets the file's last modified time or null if the file does not exist.
    926     *
    927     * @param url the file to query.
    928     * @return the file's last modified time.
    929     * @throws IOException        Thrown if an I/O error occurs.
    930     * @throws URISyntaxException if the URL is not formatted strictly according to RFC2396 and cannot be converted to a URI.
    931     * @since 2.12.0
    932     */
    933    public static FileTime getLastModifiedFileTime(final URL url) throws IOException, URISyntaxException {
    934        return getLastModifiedFileTime(url.toURI());
    935    }
    936
    937    private static FileTime getLastModifiedTime(final Path path, final LinkOption... options) throws IOException {
    938        return Files.getLastModifiedTime(Objects.requireNonNull(path, "path"), options);
    939    }
    940
    941    private static Path getParent(final Path path) {
    942        return path == null ? null : path.getParent();
    943    }
    944
    945    /**
    946     * Shorthand for {@code Files.getFileAttributeView(path, PosixFileAttributeView.class)}.
    947     *
    948     * @param path    the path to the file.
    949     * @param options how to handle symbolic links.
    950     * @return a PosixFileAttributeView, or {@code null} if the attribute view type is not available.
    951     * @since 2.12.0
    952     */
    953    public static PosixFileAttributeView getPosixFileAttributeView(final Path path, final LinkOption... options) {
    954        return Files.getFileAttributeView(path, PosixFileAttributeView.class, options);
    955    }
    956
    957    /**
    958     * Gets a {@link Path} representing the system temporary directory.
    959     *
    960     * @return the system temporary directory.
    961     * @since 2.12.0
    962     */
    963    public static Path getTempDirectory() {
    964        return Paths.get(FileUtils.getTempDirectoryPath());
    965    }
    966
    967    /**
    968     * Tests whether the given {@link Path} is a directory or not. Implemented as a null-safe delegate to
    969     * {@code Files.isDirectory(Path path, LinkOption... options)}.
    970     *
    971     * @param path    the path to the file.
    972     * @param options options indicating how to handle symbolic links
    973     * @return {@code true} if the file is a directory; {@code false} if the path is null, the file does not exist, is not a directory, or it cannot be
    974     *         determined if the file is a directory or not.
    975     * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
    976     *                           checkRead} method is invoked to check read access to the directory.
    977     * @since 2.9.0
    978     */
    979    public static boolean isDirectory(final Path path, final LinkOption... options) {
    980        return path != null && Files.isDirectory(path, options);
    981    }
    982
    983    /**
    984     * Tests whether the given file or directory is empty.
    985     *
    986     * @param path the file or directory to query.
    987     * @return whether the file or directory is empty.
    988     * @throws IOException if an I/O error occurs.
    989     */
    990    public static boolean isEmpty(final Path path) throws IOException {
    991        return Files.isDirectory(path) ? isEmptyDirectory(path) : isEmptyFile(path);
    992    }
    993
    994    /**
    995     * Tests whether the directory is empty.
    996     *
    997     * @param directory the directory to query.
    998     * @return whether the directory is empty.
    999     * @throws NotDirectoryException if the file could not otherwise be opened because it is not a directory <i>(optional specific exception)</i>.
    1000     * @throws IOException           if an I/O error occurs.
    1001     * @throws SecurityException     In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
    1002     *                               checkRead} method is invoked to check read access to the directory.
    1003     */
    1004    public static boolean isEmptyDirectory(final Path directory) throws IOException {
    1005        try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
    1006            return !directoryStream.iterator().hasNext();
    1007        }
    1008    }
    1009
    1010    /**
    1011     * Tests whether the given file is empty.
    1012     *
    1013     * @param file the file to query.
    1014     * @return whether the file is empty.
    1015     * @throws IOException       if an I/O error occurs.
    1016     * @throws SecurityException In the case of the default provider, and a security manager is installed, its {@link SecurityManager#checkRead(String)
    1017     *                           checkRead} method denies read access to the file.
    1018     */
    1019    public static boolean isEmptyFile(final Path file) throws IOException {
    1020        return Files.size(file) <= 0;
    1021    }
    1022
    1023    /**
    1024     * Tests if the given {@link Path} is newer than the given time reference.
    1025     *
    1026     * @param file    the {@link Path} to test.
    1027     * @param czdt    the time reference.
    1028     * @param options options indicating how to handle symbolic links.
    1029     * @return true if the {@link Path} exists and has been modified after the given time reference.
    1030     * @throws IOException          if an I/O error occurs.
    1031     * @throws NullPointerException if the file is {@code null}.
    1032     * @since 2.12.0
    1033     */
    1034    public static boolean isNewer(final Path file, final ChronoZonedDateTime<?> czdt, final LinkOption... options) throws IOException {
    1035        Objects.requireNonNull(czdt, "czdt");
    1036        return isNewer(file, czdt.toInstant(), options);
    1037    }
    1038
    1039    /**
    1040     * Tests if the given {@link Path} is newer than the given time reference.
    1041     *
    1042     * @param file     the {@link Path} to test.
    1043     * @param fileTime the time reference.
    1044     * @param options  options indicating how to handle symbolic links.
    1045     * @return true if the {@link Path} exists and has been modified after the given time reference.
    1046     * @throws IOException          if an I/O error occurs.
    1047     * @throws NullPointerException if the file is {@code null}.
    1048     * @since 2.12.0
    1049     */
    1050    public static boolean isNewer(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
    1051        if (notExists(file)) {
    1052            return false;
    1053        }
    1054        return compareLastModifiedTimeTo(file, fileTime, options) > 0;
    1055    }
    1056
    1057    /**
    1058     * Tests if the given {@link Path} is newer than the given time reference.
    1059     *
    1060     * @param file    the {@link Path} to test.
    1061     * @param instant the time reference.
    1062     * @param options options indicating how to handle symbolic links.
    1063     * @return true if the {@link Path} exists and has been modified after the given time reference.
    1064     * @throws IOException          if an I/O error occurs.
    1065     * @throws NullPointerException if the file is {@code null}.
    1066     * @since 2.12.0
    1067     */
    1068    public static boolean isNewer(final Path file, final Instant instant, final LinkOption... options) throws IOException {
    1069        return isNewer(file, FileTime.from(instant), options);
    1070    }
    1071
    1072    /**
    1073     * Tests if the given {@link Path} is newer than the given time reference.
    1074     *
    1075     * @param file       the {@link Path} to test.
    1076     * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
    1077     * @param options    options indicating how to handle symbolic links.
    1078     * @return true if the {@link Path} exists and has been modified after the given time reference.
    1079     * @throws IOException          if an I/O error occurs.
    1080     * @throws NullPointerException if the file is {@code null}.
    1081     * @since 2.9.0
    1082     */
    1083    public static boolean isNewer(final Path file, final long timeMillis, final LinkOption... options) throws IOException {
    1084        return isNewer(file, FileTime.fromMillis(timeMillis), options);
    1085    }
    1086
    1087    /**
    1088     * Tests if the given {@link Path} is newer than the reference {@link Path}.
    1089     *
    1090     * @param file      the {@link File} to test.
    1091     * @param reference the {@link File} of which the modification date is used.
    1092     * @return true if the {@link File} exists and has been modified more recently than the reference {@link File}.
    1093     * @throws IOException if an I/O error occurs.
    1094     * @since 2.12.0
    1095     */
    1096    public static boolean isNewer(final Path file, final Path reference) throws IOException {
    1097        return isNewer(file, getLastModifiedTime(reference));
    1098    }
    1099
    1100    /**
    1101     * Tests if the given {@link Path} is older than the given time reference.
    1102     *
    1103     * @param file     the {@link Path} to test.
    1104     * @param fileTime the time reference.
    1105     * @param options  options indicating how to handle symbolic links.
    1106     * @return true if the {@link Path} exists and has been modified before the given time reference.
    1107     * @throws IOException          if an I/O error occurs.
    1108     * @throws NullPointerException if the file is {@code null}.
    1109     * @since 2.12.0
    1110     */
    1111    public static boolean isOlder(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
    1112        if (notExists(file)) {
    1113            return false;
    1114        }
    1115        return compareLastModifiedTimeTo(file, fileTime, options) < 0;
    1116    }
    1117
    1118    /**
    1119     * Tests if the given {@link Path} is older than the given time reference.
    1120     *
    1121     * @param file    the {@link Path} to test.
    1122     * @param instant the time reference.
    1123     * @param options options indicating how to handle symbolic links.
    1124     * @return true if the {@link Path} exists and has been modified before the given time reference.
    1125     * @throws IOException          if an I/O error occurs.
    1126     * @throws NullPointerException if the file is {@code null}.
    1127     * @since 2.12.0
    1128     */
    1129    public static boolean isOlder(final Path file, final Instant instant, final LinkOption... options) throws IOException {
    1130        return isOlder(file, FileTime.from(instant), options);
    1131    }
    1132
    1133    /**
    1134     * Tests if the given {@link Path} is older than the given time reference.
    1135     *
    1136     * @param file       the {@link Path} to test.
    1137     * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
    1138     * @param options    options indicating how to handle symbolic links.
    1139     * @return true if the {@link Path} exists and has been modified before the given time reference.
    1140     * @throws IOException          if an I/O error occurs.
    1141     * @throws NullPointerException if the file is {@code null}.
    1142     * @since 2.12.0
    1143     */
    1144    public static boolean isOlder(final Path file, final long timeMillis, final LinkOption... options) throws IOException {
    1145        return isOlder(file, FileTime.fromMillis(timeMillis), options);
    1146    }
    1147
    1148    /**
    1149     * Tests if the given {@link Path} is older than the reference {@link Path}.
    1150     *
    1151     * @param file      the {@link File} to test.
    1152     * @param reference the {@link File} of which the modification date is used.
    1153     * @return true if the {@link File} exists and has been modified before than the reference {@link File}.
    1154     * @throws IOException if an I/O error occurs.
    1155     * @since 2.12.0
    1156     */
    1157    public static boolean isOlder(final Path file, final Path reference) throws IOException {
    1158        return isOlder(file, getLastModifiedTime(reference));
    1159    }
    1160
    1161    /**
    1162     * Tests whether the given path is on a POSIX file system.
    1163     *
    1164     * @param test    The Path to test.
    1165     * @param options options indicating how to handle symbolic links.
    1166     * @return true if test is on a POSIX file system.
    1167     * @since 2.12.0
    1168     */
    1169    public static boolean isPosix(final Path test, final LinkOption... options) {
    1170        return exists(test, options) && readPosixFileAttributes(test, options) != null;
    1171    }
    1172
    1173    /**
    1174     * Tests whether the given {@link Path} is a regular file or not. Implemented as a null-safe delegate to
    1175     * {@code Files.isRegularFile(Path path, LinkOption... options)}.
    1176     *
    1177     * @param path    the path to the file.
    1178     * @param options options indicating how to handle symbolic links.
    1179     * @return {@code true} if the file is a regular file; {@code false} if the path is null, the file does not exist, is not a directory, or it cannot be
    1180     *         determined if the file is a regular file or not.
    1181     * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
    1182     *                           checkRead} method is invoked to check read access to the directory.
    1183     * @since 2.9.0
    1184     */
    1185    public static boolean isRegularFile(final Path path, final LinkOption... options) {
    1186        return path != null && Files.isRegularFile(path, options);
    1187    }
    1188
    1189    /**
    1190     * Creates a new DirectoryStream for Paths rooted at the given directory.
    1191     * <p>
    1192     * If you don't use the try-with-resources construct, then you must call the stream's {@link Stream#close()} method after iteration is complete to free any
    1193     * resources held for the open directory.
    1194     * </p>
    1195     *
    1196     * @param dir        the path to the directory to stream.
    1197     * @param pathFilter the directory stream filter.
    1198     * @return a new instance.
    1199     * @throws IOException if an I/O error occurs.
    1200     */
    1201    public static DirectoryStream<Path> newDirectoryStream(final Path dir, final PathFilter pathFilter) throws IOException {
    1202        return Files.newDirectoryStream(dir, new DirectoryStreamFilter(pathFilter));
    1203    }
    1204
    1205    /**
    1206     * Creates a new OutputStream by opening or creating a file, returning an output stream that may be used to write bytes to the file.
    1207     *
    1208     * @param path   the Path.
    1209     * @param append Whether or not to append.
    1210     * @return a new OutputStream.
    1211     * @throws IOException if an I/O error occurs.
    1212     * @see Files#newOutputStream(Path, OpenOption...)
    1213     * @since 2.12.0
    1214     */
    1215    public static OutputStream newOutputStream(final Path path, final boolean append) throws IOException {
    1216        return newOutputStream(path, EMPTY_LINK_OPTION_ARRAY, append ? OPEN_OPTIONS_APPEND : OPEN_OPTIONS_TRUNCATE);
    1217    }
    1218
    1219    static OutputStream newOutputStream(final Path path, final LinkOption[] linkOptions, final OpenOption... openOptions) throws IOException {
    1220        if (!exists(path, linkOptions)) {
    1221            createParentDirectories(path, linkOptions != null && linkOptions.length > 0 ? linkOptions[0] : NULL_LINK_OPTION);
    1222        }
    1223        final List<OpenOption> list = new ArrayList<>(Arrays.asList(openOptions != null ? openOptions : EMPTY_OPEN_OPTION_ARRAY));
    1224        list.addAll(Arrays.asList(linkOptions != null ? linkOptions : EMPTY_LINK_OPTION_ARRAY));
    1225        return Files.newOutputStream(path, list.toArray(EMPTY_OPEN_OPTION_ARRAY));
    1226    }
    1227
    1228    /**
    1229     * Copy of the {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
    1230     *
    1231     * @return Copy of the {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
    1232     */
    1233    public static LinkOption[] noFollowLinkOptionArray() {
    1234        return NOFOLLOW_LINK_OPTION_ARRAY.clone();
    1235    }
    1236
    1237    private static boolean notExists(final Path path, final LinkOption... options) {
    1238        return Files.notExists(Objects.requireNonNull(path, "path"), options);
    1239    }
    1240
    1241    /**
    1242     * Returns true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
    1243     *
    1244     * @param deleteOptions the array to test
    1245     * @return true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
    1246     */
    1247    private static boolean overrideReadOnly(final DeleteOption... deleteOptions) {
    1248        if (deleteOptions == null) {
    1249            return false;
    1250        }
    1251        return Stream.of(deleteOptions).anyMatch(e -> e == StandardDeleteOption.OVERRIDE_READ_ONLY);
    1252    }
    1253
    1254    /**
    1255     * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read.
    1256     *
    1257     * @param <A>     The {@link BasicFileAttributes} type
    1258     * @param path    The Path to test.
    1259     * @param type    the {@link Class} of the file attributes required to read.
    1260     * @param options options indicating how to handle symbolic links.
    1261     * @return the file attributes or null if the attributes can't be read.
    1262     * @see Files#readAttributes(Path, Class, LinkOption...)
    1263     * @since 2.12.0
    1264     */
    1265    public static <A extends BasicFileAttributes> A readAttributes(final Path path, final Class<A> type, final LinkOption... options) {
    1266        try {
    1267            return path == null ? null : Files.readAttributes(path, type, options);
    1268        } catch (final UnsupportedOperationException | IOException e) {
    1269            // For example, on Windows.
    1270            return null;
    1271        }
    1272    }
    1273
    1274    /**
    1275     * Reads the BasicFileAttributes from the given path.
    1276     *
    1277     * @param path the path to read.
    1278     * @return the path attributes.
    1279     * @throws IOException if an I/O error occurs.
    1280     * @since 2.9.0
    1281     */
    1282    public static BasicFileAttributes readBasicFileAttributes(final Path path) throws IOException {
    1283        return Files.readAttributes(path, BasicFileAttributes.class);
    1284    }
    1285
    1286    /**
    1287     * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read.
    1288     *
    1289     * @param path    the path to read.
    1290     * @param options options indicating how to handle symbolic links.
    1291     * @return the path attributes.
    1292     * @since 2.12.0
    1293     */
    1294    public static BasicFileAttributes readBasicFileAttributes(final Path path, final LinkOption... options) {
    1295        return readAttributes(path, BasicFileAttributes.class, options);
    1296    }
    1297
    1298    /**
    1299     * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read.
    1300     *
    1301     * @param path the path to read.
    1302     * @return the path attributes.
    1303     * @since 2.9.0
    1304     * @deprecated Use {@link #readBasicFileAttributes(Path, LinkOption...)}.
    1305     */
    1306    @Deprecated
    1307    public static BasicFileAttributes readBasicFileAttributesUnchecked(final Path path) {
    1308        return readBasicFileAttributes(path, EMPTY_LINK_OPTION_ARRAY);
    1309    }
    1310
    1311    /**
    1312     * Reads the DosFileAttributes from the given path. Returns null if the attributes can't be read.
    1313     *
    1314     * @param path    the path to read.
    1315     * @param options options indicating how to handle symbolic links.
    1316     * @return the path attributes.
    1317     * @since 2.12.0
    1318     */
    1319    public static DosFileAttributes readDosFileAttributes(final Path path, final LinkOption... options) {
    1320        return readAttributes(path, DosFileAttributes.class, options);
    1321    }
    1322
    1323    private static Path readIfSymbolicLink(final Path path) throws IOException {
    1324        return path != null ? Files.isSymbolicLink(path) ? Files.readSymbolicLink(path) : path : null;
    1325    }
    1326
    1327    /**
    1328     * Reads the PosixFileAttributes or DosFileAttributes from the given path. Returns null if the attributes can't be read.
    1329     *
    1330     * @param path    The Path to read.
    1331     * @param options options indicating how to handle symbolic links.
    1332     * @return the file attributes.
    1333     * @since 2.12.0
    1334     */
    1335    public static BasicFileAttributes readOsFileAttributes(final Path path, final LinkOption... options) {
    1336        final PosixFileAttributes fileAttributes = readPosixFileAttributes(path, options);
    1337        return fileAttributes != null ? fileAttributes : readDosFileAttributes(path, options);
    1338    }
    1339
    1340    /**
    1341     * Reads the PosixFileAttributes from the given path. Returns null instead of throwing {@link UnsupportedOperationException}.
    1342     *
    1343     * @param path    The Path to read.
    1344     * @param options options indicating how to handle symbolic links.
    1345     * @return the file attributes.
    1346     * @since 2.12.0
    1347     */
    1348    public static PosixFileAttributes readPosixFileAttributes(final Path path, final LinkOption... options) {
    1349        return readAttributes(path, PosixFileAttributes.class, options);
    1350    }
    1351
    1352    /**
    1353     * Reads the given path as a String.
    1354     *
    1355     * @param path    The source path.
    1356     * @param charset How to convert bytes to a String, null uses the default Charset.
    1357     * @return a new String.
    1358     * @throws IOException if an I/O error occurs reading from the stream.
    1359     * @see Files#readAllBytes(Path)
    1360     * @since 2.12.0
    1361     */
    1362    public static String readString(final Path path, final Charset charset) throws IOException {
    1363        return new String(Files.readAllBytes(path), Charsets.toCharset(charset));
    1364    }
    1365
    1366    /**
    1367     * Relativizes all files in the given {@code collection} against a {@code parent}.
    1368     *
    1369     * @param collection The collection of paths to relativize.
    1370     * @param parent     relativizes against this parent path.
    1371     * @param sort       Whether to sort the result.
    1372     * @param comparator How to sort.
    1373     * @return A collection of relativized paths, optionally sorted.
    1374     */
    1375    static List<Path> relativize(final Collection<Path> collection, final Path parent, final boolean sort, final Comparator<? super Path> comparator) {
    1376        Stream<Path> stream = collection.stream().map(parent::relativize);
    1377        if (sort) {
    1378            stream = comparator == null ? stream.sorted() : stream.sorted(comparator);
    1379        }
    1380        return stream.collect(Collectors.toList());
    1381    }
    1382
    1383    /**
    1384     * Requires that the given {@link File} exists and throws an {@link IllegalArgumentException} if it doesn't.
    1385     *
    1386     * @param file          The {@link File} to check.
    1387     * @param fileParamName The parameter name to use in the exception message in case of {@code null} input.
    1388     * @param options       options indicating how to handle symbolic links.
    1389     * @return the given file.
    1390     * @throws NullPointerException     if the given {@link File} is {@code null}.
    1391     * @throws IllegalArgumentException if the given {@link File} does not exist.
    1392     */
    1393    private static Path requireExists(final Path file, final String fileParamName, final LinkOption... options) {
    1394        Objects.requireNonNull(file, fileParamName);
    1395        if (!exists(file, options)) {
    1396            throw new IllegalArgumentException("File system element for parameter '" + fileParamName + "' does not exist: '" + file + "'");
    1397        }
    1398        return file;
    1399    }
    1400
    1401    private static boolean setDosReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
    1402        final DosFileAttributeView dosFileAttributeView = getDosFileAttributeView(path, linkOptions);
    1403        if (dosFileAttributeView != null) {
    1404            dosFileAttributeView.setReadOnly(readOnly);
    1405            return true;
    1406        }
    1407        return false;
    1408    }
    1409
    1410    /**
    1411     * Sets the given {@code targetFile}'s last modified time to the value from {@code sourceFile}.
    1412     *
    1413     * @param sourceFile The source path to query.
    1414     * @param targetFile The target path to set.
    1415     * @throws NullPointerException if sourceFile is {@code null}.
    1416     * @throws NullPointerException if targetFile is {@code null}.
    1417     * @throws IOException          if setting the last-modified time failed.
    1418     * @since 2.12.0
    1419     */
    1420    public static void setLastModifiedTime(final Path sourceFile, final Path targetFile) throws IOException {
    1421        Objects.requireNonNull(sourceFile, "sourceFile");
    1422        Files.setLastModifiedTime(targetFile, getLastModifiedTime(sourceFile));
    1423    }
    1424
    1425    /**
    1426     * To delete a file in POSIX, you need Write and Execute permissions on its parent directory.
    1427     *
    1428     * @param parent               The parent path for a file element to delete which needs RW permissions.
    1429     * @param enableDeleteChildren true to set permissions to delete.
    1430     * @param linkOptions          options indicating how handle symbolic links.
    1431     * @return true if the operation was attempted and succeeded, false if parent is null.
    1432     * @throws IOException if an I/O error occurs.
    1433     */
    1434    private static boolean setPosixDeletePermissions(final Path parent, final boolean enableDeleteChildren, final LinkOption... linkOptions)
    1435            throws IOException {
    1436        // To delete a file in POSIX, you need write and execute permissions on its parent directory.
    1437        // @formatter:off
    1438        return setPosixPermissions(parent, enableDeleteChildren, Arrays.asList(
    1439            PosixFilePermission.OWNER_WRITE,
    1440            //PosixFilePermission.GROUP_WRITE,
    1441            //PosixFilePermission.OTHERS_WRITE,
    1442            PosixFilePermission.OWNER_EXECUTE
    1443            //PosixFilePermission.GROUP_EXECUTE,
    1444            //PosixFilePermission.OTHERS_EXECUTE
    1445            ), linkOptions);
    1446        // @formatter:on
    1447    }
    1448
    1449    /**
    1450     * Low-level POSIX permission operation to set permissions.
    1451     *
    1452     * @param path              Set this path's permissions.
    1453     * @param addPermissions    true to add, false to remove.
    1454     * @param updatePermissions the List of PosixFilePermission to add or remove.
    1455     * @param linkOptions       options indicating how handle symbolic links.
    1456     * @return true if the operation was attempted and succeeded, false if parent is null.
    1457     * @throws IOException if an I/O error occurs.
    1458     */
    1459    private static boolean setPosixPermissions(final Path path, final boolean addPermissions, final List<PosixFilePermission> updatePermissions,
    1460            final LinkOption... linkOptions) throws IOException {
    1461        if (path != null) {
    1462            final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
    1463            if (addPermissions) {
    1464                permissions.addAll(updatePermissions);
    1465            } else {
    1466                permissions.removeAll(updatePermissions);
    1467            }
    1468            Files.setPosixFilePermissions(path, permissions);
    1469            return true;
    1470        }
    1471        return false;
    1472    }
    1473
    1474    private static void setPosixReadOnlyFile(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
    1475        // Not Windows 10
    1476        final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
    1477        // @formatter:off
    1478        final List<PosixFilePermission> readPermissions = Arrays.asList(
    1479                PosixFilePermission.OWNER_READ
    1480                //PosixFilePermission.GROUP_READ,
    1481                //PosixFilePermission.OTHERS_READ
    1482            );
    1483        final List<PosixFilePermission> writePermissions = Arrays.asList(
    1484                PosixFilePermission.OWNER_WRITE
    1485                //PosixFilePermission.GROUP_WRITE,
    1486                //PosixFilePermission.OTHERS_WRITE
    1487            );
    1488        // @formatter:on
    1489        if (readOnly) {
    1490            // RO: We can read, we cannot write.
    1491            permissions.addAll(readPermissions);
    1492            permissions.removeAll(writePermissions);
    1493        } else {
    1494            // Not RO: We can read, we can write.
    1495            permissions.addAll(readPermissions);
    1496            permissions.addAll(writePermissions);
    1497        }
    1498        Files.setPosixFilePermissions(path, permissions);
    1499    }
    1500
    1501    /**
    1502     * Sets the given Path to the {@code readOnly} value.
    1503     * <p>
    1504     * This behavior is OS dependent.
    1505     * </p>
    1506     *
    1507     * @param path        The path to set.
    1508     * @param readOnly    true for read-only, false for not read-only.
    1509     * @param linkOptions options indicating how to handle symbolic links.
    1510     * @return The given path.
    1511     * @throws IOException if an I/O error occurs.
    1512     * @since 2.8.0
    1513     */
    1514    public static Path setReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
    1515        try {
    1516            // Windows is simplest
    1517            if (setDosReadOnly(path, readOnly, linkOptions)) {
    1518                return path;
    1519            }
    1520        } catch (final IOException ignored) {
    1521            // Retry with POSIX below.
    1522        }
    1523        final Path parent = getParent(path);
    1524        if (!isPosix(parent, linkOptions)) { // Test parent because we may not the permissions to test the file.
    1525            throw new IOException(String.format("DOS or POSIX file operations not available for '%s' %s", path, Arrays.toString(linkOptions)));
    1526        }
    1527        // POSIX
    1528        if (readOnly) {
    1529            // RO
    1530            // File, then parent dir (if any).
    1531            setPosixReadOnlyFile(path, readOnly, linkOptions);
    1532            setPosixDeletePermissions(parent, false, linkOptions);
    1533        } else {
    1534            // RE
    1535            // Parent dir (if any), then file.
    1536            setPosixDeletePermissions(parent, true, linkOptions);
    1537        }
    1538        return path;
    1539    }
    1540
    1541    /**
    1542     * Returns the size of the given file or directory. If the provided {@link Path} is a regular file, then the file's size is returned. If the argument is a
    1543     * directory, then the size of the directory is calculated recursively.
    1544     * <p>
    1545     * Note that overflow is not detected, and the return value may be negative if overflow occurs. See {@link #sizeOfAsBigInteger(Path)} for an alternative
    1546     * method that does not overflow.
    1547     * </p>
    1548     *
    1549     * @param path the regular file or directory to return the size of, must not be {@code null}.
    1550     * @return the length of the file, or recursive size of the directory, in bytes.
    1551     * @throws NullPointerException     if the file is {@code null}.
    1552     * @throws IllegalArgumentException if the file does not exist.
    1553     * @throws IOException              if an I/O error occurs.
    1554     * @since 2.12.0
    1555     */
    1556    public static long sizeOf(final Path path) throws IOException {
    1557        requireExists(path, "path");
    1558        return Files.isDirectory(path) ? sizeOfDirectory(path) : Files.size(path);
    1559    }
    1560
    1561    /**
    1562     * Returns the size of the given file or directory. If the provided {@link Path} is a regular file, then the file's size is returned. If the argument is a
    1563     * directory, then the size of the directory is calculated recursively.
    1564     *
    1565     * @param path the regular file or directory to return the size of (must not be {@code null}).
    1566     * @return the length of the file, or recursive size of the directory, provided (in bytes).
    1567     * @throws NullPointerException     if the file is {@code null}.
    1568     * @throws IllegalArgumentException if the file does not exist.
    1569     * @throws IOException              if an I/O error occurs.
    1570     * @since 2.12.0
    1571     */
    1572    public static BigInteger sizeOfAsBigInteger(final Path path) throws IOException {
    1573        requireExists(path, "path");
    1574        return Files.isDirectory(path) ? sizeOfDirectoryAsBigInteger(path) : BigInteger.valueOf(Files.size(path));
    1575    }
    1576
    1577    /**
    1578     * Counts the size of a directory recursively (sum of the size of all files).
    1579     * <p>
    1580     * Note that overflow is not detected, and the return value may be negative if overflow occurs. See {@link #sizeOfDirectoryAsBigInteger(Path)} for an
    1581     * alternative method that does not overflow.
    1582     * </p>
    1583     *
    1584     * @param directory directory to inspect, must not be {@code null}.
    1585     * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total is greater than {@link Long#MAX_VALUE}.
    1586     * @throws NullPointerException if the directory is {@code null}.
    1587     * @throws IOException          if an I/O error occurs.
    1588     * @since 2.12.0
    1589     */
    1590    public static long sizeOfDirectory(final Path directory) throws IOException {
    1591        return countDirectory(directory).getByteCounter().getLong();
    1592    }
    1593
    1594    /**
    1595     * Counts the size of a directory recursively (sum of the size of all files).
    1596     *
    1597     * @param directory directory to inspect, must not be {@code null}.
    1598     * @return size of directory in bytes, 0 if directory is security restricted.
    1599     * @throws NullPointerException if the directory is {@code null}.
    1600     * @throws IOException          if an I/O error occurs.
    1601     * @since 2.12.0
    1602     */
    1603    public static BigInteger sizeOfDirectoryAsBigInteger(final Path directory) throws IOException {
    1604        return countDirectoryAsBigInteger(directory).getByteCounter().getBigInteger();
    1605    }
    1606
    1607    /**
    1608     * Converts an array of {@link FileVisitOption} to a {@link Set}.
    1609     *
    1610     * @param fileVisitOptions input array.
    1611     * @return a new Set.
    1612     */
    1613    static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... fileVisitOptions) {
    1614        return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class) : Stream.of(fileVisitOptions).collect(Collectors.toSet());
    1615    }
    1616
    1617    /**
    1618     * Implements behavior similar to the Unix "touch" utility. Creates a new file with size 0, or, if the file exists, just updates the file's modified time.
    1619     * this method creates parent directories if they do not exist.
    1620     *
    1621     * @param file the file to touch.
    1622     * @return The given file.
    1623     * @throws NullPointerException if the parameter is {@code null}.
    1624     * @throws IOException          if setting the last-modified time failed or an I/O problem occurs.\
    1625     * @since 2.12.0
    1626     */
    1627    public static Path touch(final Path file) throws IOException {
    1628        Objects.requireNonNull(file, "file");
    1629        if (!Files.exists(file)) {
    1630            createParentDirectories(file);
    1631            Files.createFile(file);
    1632        } else {
    1633            FileTimes.setLastModifiedTime(file);
    1634        }
    1635        return file;
    1636    }
    1637
    1638    /**
    1639     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
    1640     *
    1641     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
    1642     *
    1643     * @param visitor   See {@link Files#walkFileTree(Path,FileVisitor)}.
    1644     * @param directory See {@link Files#walkFileTree(Path,FileVisitor)}.
    1645     * @param <T>       See {@link Files#walkFileTree(Path,FileVisitor)}.
    1646     * @return the given visitor.
    1647     *
    1648     * @throws NoSuchFileException  if the directory does not exist.
    1649     * @throws IOException          if an I/O error is thrown by a visitor method.
    1650     * @throws NullPointerException if the directory is {@code null}.
    1651     */
    1652    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path directory) throws IOException {
    1653        Files.walkFileTree(directory, visitor);
    1654        return visitor;
    1655    }
    1656
    1657    /**
    1658     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
    1659     *
    1660     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
    1661     *
    1662     * @param start    See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    1663     * @param options  See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    1664     * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    1665     * @param visitor  See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    1666     * @param <T>      See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    1667     * @return the given visitor.
    1668     *
    1669     * @throws IOException if an I/O error is thrown by a visitor method.
    1670     */
    1671    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path start, final Set<FileVisitOption> options,
    1672            final int maxDepth) throws IOException {
    1673        Files.walkFileTree(start, options, maxDepth, visitor);
    1674        return visitor;
    1675    }
    1676
    1677    /**
    1678     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
    1679     *
    1680     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
    1681     *
    1682     * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
    1683     * @param first   See {@link Paths#get(String,String[])}.
    1684     * @param more    See {@link Paths#get(String,String[])}.
    1685     * @param <T>     See {@link Files#walkFileTree(Path,FileVisitor)}.
    1686     * @return the given visitor.
    1687     *
    1688     * @throws IOException if an I/O error is thrown by a visitor method.
    1689     */
    1690    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final String first, final String... more) throws IOException {
    1691        return visitFileTree(visitor, Paths.get(first, more));
    1692    }
    1693
    1694    /**
    1695     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
    1696     *
    1697     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
    1698     *
    1699     * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
    1700     * @param uri     See {@link Paths#get(URI)}.
    1701     * @param <T>     See {@link Files#walkFileTree(Path,FileVisitor)}.
    1702     * @return the given visitor.
    1703     *
    1704     * @throws IOException if an I/O error is thrown by a visitor method.
    1705     */
    1706    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final URI uri) throws IOException {
    1707        return visitFileTree(visitor, Paths.get(uri));
    1708    }
    1709
    1710    /**
    1711     * Waits for the file system to propagate a file creation, with a timeout.
    1712     * <p>
    1713     * This method repeatedly tests {@link Files#exists(Path,LinkOption...)} until it returns true up to the maximum time given.
    1714     * </p>
    1715     *
    1716     * @param file    the file to check, must not be {@code null}.
    1717     * @param timeout the maximum time to wait.
    1718     * @param options options indicating how to handle symbolic links.
    1719     * @return true if file exists.
    1720     * @throws NullPointerException if the file is {@code null}.
    1721     * @since 2.12.0
    1722     */
    1723    public static boolean waitFor(final Path file, final Duration timeout, final LinkOption... options) {
    1724        Objects.requireNonNull(file, "file");
    1725        final Instant finishInstant = Instant.now().plus(timeout);
    1726        boolean interrupted = false;
    1727        final long minSleepMillis = 100;
    1728        try {
    1729            while (!exists(file, options)) {
    1730                final Instant now = Instant.now();
    1731                if (now.isAfter(finishInstant)) {
    1732                    return false;
    1733                }
    1734                try {
    1735                    ThreadUtils.sleep(Duration.ofMillis(Math.min(minSleepMillis, finishInstant.minusMillis(now.toEpochMilli()).toEpochMilli())));
    1736                } catch (final InterruptedException ignore) {
    1737                    interrupted = true;
    1738                } catch (final Exception ex) {
    1739                    break;
    1740                }
    1741            }
    1742        } finally {
    1743            if (interrupted) {
    1744                Thread.currentThread().interrupt();
    1745            }
    1746        }
    1747        return exists(file, options);
    1748    }
    1749
    1750    /**
    1751     * Returns a stream of filtered paths.
    1752     * <p>
    1753     * The returned {@link Stream} may wrap one or more {@link DirectoryStream}s. When you require timely disposal of file system resources, use a
    1754     * {@code try}-with-resources block to ensure invocation of the stream's {@link Stream#close()} method after the stream operations are completed. Calling a
    1755     * closed stream causes a {@link IllegalStateException}.
    1756     * </p>
    1757     *
    1758     * @param start          the start path
    1759     * @param pathFilter     the path filter
    1760     * @param maxDepth       the maximum depth of directories to walk.
    1761     * @param readAttributes whether to call the filters with file attributes (false passes null).
    1762     * @param options        the options to configure the walk.
    1763     * @return a filtered stream of paths.
    1764     * @throws IOException if an I/O error is thrown when accessing the starting file.
    1765     * @since 2.9.0
    1766     */
    1767    public static Stream<Path> walk(final Path start, final PathFilter pathFilter, final int maxDepth, final boolean readAttributes,
    1768            final FileVisitOption... options) throws IOException {
    1769        return Files.walk(start, maxDepth, options)
    1770                .filter(path -> pathFilter.accept(path, readAttributes ? readBasicFileAttributesUnchecked(path) : null) == FileVisitResult.CONTINUE);
    1771    }
    1772
    1773    private static <R> R withPosixFileAttributes(final Path path, final LinkOption[] linkOptions, final boolean overrideReadOnly,
    1774            final IOFunction<PosixFileAttributes, R> function) throws IOException {
    1775        final PosixFileAttributes posixFileAttributes = overrideReadOnly ? readPosixFileAttributes(path, linkOptions) : null;
    1776        try {
    1777            return function.apply(posixFileAttributes);
    1778        } finally {
    1779            if (posixFileAttributes != null && path != null && Files.exists(path, linkOptions)) {
    1780                Files.setPosixFilePermissions(path, posixFileAttributes.permissions());
    1781            }
    1782        }
    1783    }
    1784
    1785    /**
    1786     * Writes the given character sequence to a file at the given path.
    1787     *
    1788     * @param path         The target file.
    1789     * @param charSequence The character sequence text.
    1790     * @param charset      The Charset to encode the text.
    1791     * @param openOptions  options How to open the file.
    1792     * @return The given path.
    1793     * @throws IOException          if an I/O error occurs writing to or creating the file.
    1794     * @throws NullPointerException if either {@code path} or {@code charSequence} is {@code null}.
    1795     * @since 2.12.0
    1796     */
    1797    public static Path writeString(final Path path, final CharSequence charSequence, final Charset charset, final OpenOption... openOptions)
    1798            throws IOException {
    1799        // Check the text is not null before opening file.
    1800        Objects.requireNonNull(path, "path");
    1801        Objects.requireNonNull(charSequence, "charSequence");
    1802        Files.write(path, String.valueOf(charSequence).getBytes(Charsets.toCharset(charset)), openOptions);
    1803        return path;
    1804    }
    1805
    1806    /**
    1807     * Prevents instantiation.
    1808     */
    1809    private PathUtils() {
    1810        // do not instantiate.
    1811    }
    1812
    1813}