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