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