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