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