    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.io.file;
    019
    020import java.io.File;
    021import java.io.IOException;
    022import java.io.InputStream;
    023import java.io.UncheckedIOException;
    024import java.net.URI;
    025import java.net.URL;
    026import java.nio.file.CopyOption;
    027import java.nio.file.DirectoryStream;
    028import java.nio.file.FileVisitOption;
    029import java.nio.file.FileVisitResult;
    030import java.nio.file.FileVisitor;
    031import java.nio.file.Files;
    032import java.nio.file.LinkOption;
    033import java.nio.file.NoSuchFileException;
    034import java.nio.file.NotDirectoryException;
    035import java.nio.file.OpenOption;
    036import java.nio.file.Path;
    037import java.nio.file.Paths;
    038import java.nio.file.attribute.AclEntry;
    039import java.nio.file.attribute.AclFileAttributeView;
    040import java.nio.file.attribute.BasicFileAttributes;
    041import java.nio.file.attribute.DosFileAttributeView;
    042import java.nio.file.attribute.FileAttribute;
    043import java.nio.file.attribute.PosixFileAttributeView;
    044import java.nio.file.attribute.PosixFileAttributes;
    045import java.nio.file.attribute.PosixFilePermission;
    046import java.util.ArrayList;
    047import java.util.Arrays;
    048import java.util.Collection;
    049import java.util.Collections;
    050import java.util.Comparator;
    051import java.util.EnumSet;
    052import java.util.List;
    053import java.util.Objects;
    054import java.util.Set;
    055import java.util.stream.Collector;
    056import java.util.stream.Collectors;
    057import java.util.stream.Stream;
    058
    059import org.apache.commons.io.IOExceptionList;
    060import org.apache.commons.io.IOUtils;
    061import org.apache.commons.io.file.Counters.PathCounters;
    062import org.apache.commons.io.filefilter.IOFileFilter;
    063
    064/**
    065 * NIO Path utilities.
    066 *
    067 * @since 2.7
    068 */
    069public final class PathUtils {
    070
    071    /**
    072     * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted
    073     * relative lists when comparing directories.
    074     */
    075    private static class RelativeSortedPaths {
    076
    077        final boolean equals;
    078        // final List<Path> relativeDirList1; // might need later?
    079        // final List<Path> relativeDirList2; // might need later?
    080        final List<Path> relativeFileList1;
    081        final List<Path> relativeFileList2;
    082
    083        /**
    084         * Constructs and initializes a new instance by accumulating directory and file info.
    085         *
    086         * @param dir1 First directory to compare.
    087         * @param dir2 Seconds directory to compare.
    088         * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    089         * @param linkOptions Options indicating how symbolic links are handled.
    090         * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    091         * @throws IOException if an I/O error is thrown by a visitor method.
    092         */
    093        private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth,
    094            final LinkOption[] linkOptions, final FileVisitOption[] fileVisitOptions) throws IOException {
    095            final List<Path> tmpRelativeDirList1;
    096            final List<Path> tmpRelativeDirList2;
    097            List<Path> tmpRelativeFileList1 = null;
    098            List<Path> tmpRelativeFileList2 = null;
    099            if (dir1 == null && dir2 == null) {
    100                equals = true;
    101            } else if (dir1 == null ^ dir2 == null) {
    102                equals = false;
    103            } else {
    104                final boolean parentDirNotExists1 = Files.notExists(dir1, linkOptions);
    105                final boolean parentDirNotExists2 = Files.notExists(dir2, linkOptions);
    106                if (parentDirNotExists1 || parentDirNotExists2) {
    107                    equals = parentDirNotExists1 && parentDirNotExists2;
    108                } else {
    109                    final AccumulatorPathVisitor visitor1 = accumulate(dir1, maxDepth, fileVisitOptions);
    110                    final AccumulatorPathVisitor visitor2 = accumulate(dir2, maxDepth, fileVisitOptions);
    111                    if (visitor1.getDirList().size() != visitor2.getDirList().size()
    112                        || visitor1.getFileList().size() != visitor2.getFileList().size()) {
    113                        equals = false;
    114                    } else {
    115                        tmpRelativeDirList1 = visitor1.relativizeDirectories(dir1, true, null);
    116                        tmpRelativeDirList2 = visitor2.relativizeDirectories(dir2, true, null);
    117                        if (!tmpRelativeDirList1.equals(tmpRelativeDirList2)) {
    118                            equals = false;
    119                        } else {
    120                            tmpRelativeFileList1 = visitor1.relativizeFiles(dir1, true, null);
    121                            tmpRelativeFileList2 = visitor2.relativizeFiles(dir2, true, null);
    122                            equals = tmpRelativeFileList1.equals(tmpRelativeFileList2);
    123                        }
    124                    }
    125                }
    126            }
    127            // relativeDirList1 = tmpRelativeDirList1;
    128            // relativeDirList2 = tmpRelativeDirList2;
    129            relativeFileList1 = tmpRelativeFileList1;
    130            relativeFileList2 = tmpRelativeFileList2;
    131        }
    132    }
    133
    134    /**
    135     * Empty {@link CopyOption} array.
    136     *
    137     * @since 2.8.0
    138     */
    139    public static final CopyOption[] EMPTY_COPY_OPTIONS = {};
    140
    141    /**
    142     * Empty {@link LinkOption} array.
    143     *
    144     * @since 2.8.0
    145     */
    146    public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = {};
    147
    148    /**
    149     * Empty {@link FileVisitOption} array.
    150     */
    151    public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = {};
    152
    153    /**
    154     * Empty {@link LinkOption} array.
    155     */
    156    public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = {};
    157
    158    /**
    159     * {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
    160     *
    161     * @since 2.9.0
    162     */
    163    public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = {LinkOption.NOFOLLOW_LINKS};
    164
    165    /**
    166     * Empty {@link OpenOption} array.
    167     */
    168    public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = {};
    169
    170    /**
    171     * Empty {@link Path} array.
    172     *
    173     * @since 2.9.0
    174     */
    175    public static final Path[] EMPTY_PATH_ARRAY = {};
    176
    177    /**
    178     * Accumulates file tree information in a {@link AccumulatorPathVisitor}.
    179     *
    180     * @param directory The directory to accumulate information.
    181     * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    182     * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    183     * @throws IOException if an I/O error is thrown by a visitor method.
    184     * @return file tree information.
    185     */
    186    private static AccumulatorPathVisitor accumulate(final Path directory, final int maxDepth,
    187        final FileVisitOption[] fileVisitOptions) throws IOException {
    188        return visitFileTree(AccumulatorPathVisitor.withLongCounters(), directory,
    189            toFileVisitOptionSet(fileVisitOptions), maxDepth);
    190    }
    191
    192    /**
    193     * Cleans a directory including sub-directories without deleting directories.
    194     *
    195     * @param directory directory to clean.
    196     * @return The visitation path counters.
    197     * @throws IOException if an I/O error is thrown by a visitor method.
    198     */
    199    public static PathCounters cleanDirectory(final Path directory) throws IOException {
    200        return cleanDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
    201    }
    202
    203    /**
    204     * Cleans a directory including sub-directories without deleting directories.
    205     *
    206     * @param directory directory to clean.
    207     * @param deleteOptions How to handle deletion.
    208     * @return The visitation path counters.
    209     * @throws IOException if an I/O error is thrown by a visitor method.
    210     * @since 2.8.0
    211     */
    212    public static PathCounters cleanDirectory(final Path directory, final DeleteOption... deleteOptions)
    213        throws IOException {
    214        return visitFileTree(new CleaningPathVisitor(Counters.longPathCounters(), deleteOptions), directory)
    215            .getPathCounters();
    216    }
    217
    218    /**
    219     * Copies a directory to another directory.
    220     *
    221     * @param sourceDirectory The source directory.
    222     * @param targetDirectory The target directory.
    223     * @param copyOptions Specifies how the copying should be done.
    224     * @return The visitation path counters.
    225     * @throws IOException if an I/O error is thrown by a visitor method.
    226     */
    227    public static PathCounters copyDirectory(final Path sourceDirectory, final Path targetDirectory,
    228        final CopyOption... copyOptions) throws IOException {
    229        final Path absoluteSource = sourceDirectory.toAbsolutePath();
    230        return visitFileTree(
    231            new CopyDirectoryVisitor(Counters.longPathCounters(), absoluteSource, targetDirectory, copyOptions),
    232            absoluteSource).getPathCounters();
    233    }
    234
    235    /**
    236     * Copies a URL to a directory.
    237     *
    238     * @param sourceFile The source URL.
    239     * @param targetFile The target file.
    240     * @param copyOptions Specifies how the copying should be done.
    241     * @return The target file
    242     * @throws IOException if an I/O error occurs.
    243     * @see Files#copy(InputStream, Path, CopyOption...)
    244     */
    245    public static Path copyFile(final URL sourceFile, final Path targetFile, final CopyOption... copyOptions)
    246        throws IOException {
    247        try (final InputStream inputStream = sourceFile.openStream()) {
    248            Files.copy(inputStream, targetFile, copyOptions);
    249            return targetFile;
    250        }
    251    }
    252
    253    /**
    254     * Copies a file to a directory.
    255     *
    256     * @param sourceFile The source file.
    257     * @param targetDirectory The target directory.
    258     * @param copyOptions Specifies how the copying should be done.
    259     * @return The target file
    260     * @throws IOException if an I/O error occurs.
    261     * @see Files#copy(Path, Path, CopyOption...)
    262     */
    263    public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory,
    264        final CopyOption... copyOptions) throws IOException {
    265        return Files.copy(sourceFile, targetDirectory.resolve(sourceFile.getFileName()), copyOptions);
    266    }
    267
    268    /**
    269     * Copies a URL to a directory.
    270     *
    271     * @param sourceFile The source URL.
    272     * @param targetDirectory The target directory.
    273     * @param copyOptions Specifies how the copying should be done.
    274     * @return The target file
    275     * @throws IOException if an I/O error occurs.
    276     * @see Files#copy(InputStream, Path, CopyOption...)
    277     */
    278    public static Path copyFileToDirectory(final URL sourceFile, final Path targetDirectory,
    279        final CopyOption... copyOptions) throws IOException {
    280        try (final InputStream inputStream = sourceFile.openStream()) {
    281            Files.copy(inputStream, targetDirectory.resolve(sourceFile.getFile()), copyOptions);
    282            return targetDirectory;
    283        }
    284    }
    285
    286    /**
    287     * Counts aspects of a directory including sub-directories.
    288     *
    289     * @param directory directory to delete.
    290     * @return The visitor used to count the given directory.
    291     * @throws IOException if an I/O error is thrown by a visitor method.
    292     */
    293    public static PathCounters countDirectory(final Path directory) throws IOException {
    294        return visitFileTree(new CountingPathVisitor(Counters.longPathCounters()), directory).getPathCounters();
    295    }
    296
    297    /**
    298     * Creates the parent directories for the given {@code path}.
    299     *
    300     * @param path The path to a file (or directory).
    301     * @param attrs An optional list of file attributes to set atomically when creating the directories.
    302     * @return The Path for the {@code path}'s parent directory or null if the given path has no parent.
    303     * @throws IOException if an I/O error occurs.
    304     * @since 2.9.0
    305     */
    306    public static Path createParentDirectories(final Path path, final FileAttribute<?>... attrs) throws IOException {
    307        final Path parent = path.getParent();
    308        if (parent == null) {
    309            return null;
    310        }
    311        return Files.createDirectories(parent, attrs);
    312    }
    313
    314    /**
    315     * Gets the current directory.
    316     *
    317     * @return the current directory.
    318     *
    319     * @since 2.9.0
    320     */
    321    public static Path current() {
    322        return Paths.get("");
    323    }
    324
    325    /**
    326     * Deletes a file or directory. If the path is a directory, delete it and all sub-directories.
    327     * <p>
    328     * The difference between File.delete() and this method are:
    329     * </p>
    330     * <ul>
    331     * <li>A directory to delete does not have to be empty.</li>
    332     * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a
    333     * boolean.
    334     * </ul>
    335     *
    336     * @param path file or directory to delete, must not be {@code null}
    337     * @return The visitor used to delete the given directory.
    338     * @throws NullPointerException if the directory is {@code null}
    339     * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs.
    340     */
    341    public static PathCounters delete(final Path path) throws IOException {
    342        return delete(path, EMPTY_DELETE_OPTION_ARRAY);
    343    }
    344
    345    /**
    346     * Deletes a file or directory. If the path is a directory, delete it and all sub-directories.
    347     * <p>
    348     * The difference between File.delete() and this method are:
    349     * </p>
    350     * <ul>
    351     * <li>A directory to delete does not have to be empty.</li>
    352     * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a
    353     * boolean.
    354     * </ul>
    355     *
    356     * @param path file or directory to delete, must not be {@code null}
    357     * @param deleteOptions How to handle deletion.
    358     * @return The visitor used to delete the given directory.
    359     * @throws NullPointerException if the directory is {@code null}
    360     * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs.
    361     * @since 2.8.0
    362     */
    363    public static PathCounters delete(final Path path, final DeleteOption... deleteOptions) throws IOException {
    364        // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
    365        return Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) ? deleteDirectory(path, deleteOptions)
    366            : deleteFile(path, deleteOptions);
    367    }
    368
    369    /**
    370     * Deletes a file or directory. If the path is a directory, delete it and all sub-directories.
    371     * <p>
    372     * The difference between File.delete() and this method are:
    373     * </p>
    374     * <ul>
    375     * <li>A directory to delete does not have to be empty.</li>
    376     * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a
    377     * boolean.
    378     * </ul>
    379     *
    380     * @param path file or directory to delete, must not be {@code null}
    381     * @param linkOptions How to handle symbolic links.
    382     * @param deleteOptions How to handle deletion.
    383     * @return The visitor used to delete the given directory.
    384     * @throws NullPointerException if the directory is {@code null}
    385     * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs.
    386     * @since 2.9.0
    387     */
    388    public static PathCounters delete(final Path path, final LinkOption[] linkOptions,
    389        final DeleteOption... deleteOptions) throws IOException {
    390        // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
    391        return Files.isDirectory(path, linkOptions) ? deleteDirectory(path, linkOptions, deleteOptions)
    392            : deleteFile(path, linkOptions, deleteOptions);
    393    }
    394
    395    /**
    396     * Deletes a directory including sub-directories.
    397     *
    398     * @param directory directory to delete.
    399     * @return The visitor used to delete the given directory.
    400     * @throws IOException if an I/O error is thrown by a visitor method.
    401     */
    402    public static PathCounters deleteDirectory(final Path directory) throws IOException {
    403        return deleteDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
    404    }
    405
    406    /**
    407     * Deletes a directory including sub-directories.
    408     *
    409     * @param directory directory to delete.
    410     * @param deleteOptions How to handle deletion.
    411     * @return The visitor used to delete the given directory.
    412     * @throws IOException if an I/O error is thrown by a visitor method.
    413     * @since 2.8.0
    414     */
    415    public static PathCounters deleteDirectory(final Path directory, final DeleteOption... deleteOptions)
    416        throws IOException {
    417        return visitFileTree(
    418            new DeletingPathVisitor(Counters.longPathCounters(), PathUtils.NOFOLLOW_LINK_OPTION_ARRAY, deleteOptions),
    419            directory).getPathCounters();
    420    }
    421
    422    /**
    423     * Deletes a directory including sub-directories.
    424     *
    425     * @param directory directory to delete.
    426     * @param linkOptions How to handle symbolic links.
    427     * @param deleteOptions How to handle deletion.
    428     * @return The visitor used to delete the given directory.
    429     * @throws IOException if an I/O error is thrown by a visitor method.
    430     * @since 2.9.0
    431     */
    432    public static PathCounters deleteDirectory(final Path directory, final LinkOption[] linkOptions,
    433        final DeleteOption... deleteOptions) throws IOException {
    434        return visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions),
    435            directory).getPathCounters();
    436    }
    437
    438    /**
    439     * Deletes the given file.
    440     *
    441     * @param file The file to delete.
    442     * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
    443     * @throws IOException if an I/O error occurs.
    444     * @throws NoSuchFileException if the file is a directory.
    445     */
    446    public static PathCounters deleteFile(final Path file) throws IOException {
    447        return deleteFile(file, EMPTY_DELETE_OPTION_ARRAY);
    448    }
    449
    450    /**
    451     * Deletes the given file.
    452     *
    453     * @param file The file to delete.
    454     * @param deleteOptions How to handle deletion.
    455     * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
    456     * @throws IOException if an I/O error occurs.
    457     * @throws NoSuchFileException if the file is a directory.
    458     * @since 2.8.0
    459     */
    460    public static PathCounters deleteFile(final Path file, final DeleteOption... deleteOptions) throws IOException {
    461        // Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files.
    462        return deleteFile(file, NOFOLLOW_LINK_OPTION_ARRAY, deleteOptions);
    463    }
    464
    465    /**
    466     * Deletes the given file.
    467     *
    468     * @param file The file to delete.
    469     * @param linkOptions How to handle symbolic links.
    470     * @param deleteOptions How to handle deletion.
    471     * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
    472     * @throws IOException if an I/O error occurs.
    473     * @throws NoSuchFileException if the file is a directory.
    474     * @since 2.9.0
    475     */
    476    public static PathCounters deleteFile(final Path file, final LinkOption[] linkOptions,
    477        final DeleteOption... deleteOptions) throws NoSuchFileException, IOException {
    478        if (Files.isDirectory(file, linkOptions)) {
    479            throw new NoSuchFileException(file.toString());
    480        }
    481        final PathCounters pathCounts = Counters.longPathCounters();
    482        final boolean exists = Files.exists(file, linkOptions);
    483        final long size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
    484        if (overrideReadOnly(deleteOptions) && exists) {
    485            setReadOnly(file, false, linkOptions);
    486        }
    487        if (Files.deleteIfExists(file)) {
    488            pathCounts.getFileCounter().increment();
    489            pathCounts.getByteCounter().add(size);
    490        }
    491        return pathCounts;
    492    }
    493
    494    /**
    495     * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The
    496     * comparison includes all files in all sub-directories.
    497     *
    498     * @param path1 The first directory.
    499     * @param path2 The second directory.
    500     * @return Whether the two directories contain the same files while considering file contents.
    501     * @throws IOException if an I/O error is thrown by a visitor method
    502     */
    503    public static boolean directoryAndFileContentEquals(final Path path1, final Path path2) throws IOException {
    504        return directoryAndFileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY,
    505            EMPTY_FILE_VISIT_OPTION_ARRAY);
    506    }
    507
    508    /**
    509     * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The
    510     * comparison includes all files in all sub-directories.
    511     *
    512     * @param path1 The first directory.
    513     * @param path2 The second directory.
    514     * @param linkOptions options to follow links.
    515     * @param openOptions options to open files.
    516     * @param fileVisitOption options to configure traversal.
    517     * @return Whether the two directories contain the same files while considering file contents.
    518     * @throws IOException if an I/O error is thrown by a visitor method
    519     */
    520    public static boolean directoryAndFileContentEquals(final Path path1, final Path path2,
    521        final LinkOption[] linkOptions, final OpenOption[] openOptions, final FileVisitOption[] fileVisitOption)
    522        throws IOException {
    523        // First walk both file trees and gather normalized paths.
    524        if (path1 == null && path2 == null) {
    525            return true;
    526        }
    527        if (path1 == null || path2 == null) {
    528            return false;
    529        }
    530        if (Files.notExists(path1) && Files.notExists(path2)) {
    531            return true;
    532        }
    533        final RelativeSortedPaths relativeSortedPaths = new RelativeSortedPaths(path1, path2, Integer.MAX_VALUE,
    534            linkOptions, fileVisitOption);
    535        // If the normalized path names and counts are not the same, no need to compare contents.
    536        if (!relativeSortedPaths.equals) {
    537            return false;
    538        }
    539        // Both visitors contain the same normalized paths, we can compare file contents.
    540        final List<Path> fileList1 = relativeSortedPaths.relativeFileList1;
    541        final List<Path> fileList2 = relativeSortedPaths.relativeFileList2;
    542        for (final Path path : fileList1) {
    543            final int binarySearch = Collections.binarySearch(fileList2, path);
    544            if (binarySearch <= -1) {
    545                throw new IllegalStateException("Unexpected mismatch.");
    546            }
    547            if (!fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) {
    548                return false;
    549            }
    550        }
    551        return true;
    552    }
    553
    554    /**
    555     * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The
    556     * comparison includes all files in all sub-directories.
    557     *
    558     * @param path1 The first directory.
    559     * @param path2 The second directory.
    560     * @return Whether the two directories contain the same files without considering file contents.
    561     * @throws IOException if an I/O error is thrown by a visitor method
    562     */
    563    public static boolean directoryContentEquals(final Path path1, final Path path2) throws IOException {
    564        return directoryContentEquals(path1, path2, Integer.MAX_VALUE, EMPTY_LINK_OPTION_ARRAY,
    565            EMPTY_FILE_VISIT_OPTION_ARRAY);
    566    }
    567
    568    /**
    569     * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The
    570     * comparison includes all files in all sub-directories.
    571     *
    572     * @param path1 The first directory.
    573     * @param path2 The second directory.
    574     * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    575     * @param linkOptions options to follow links.
    576     * @param fileVisitOptions options to configure the traversal
    577     * @return Whether the two directories contain the same files without considering file contents.
    578     * @throws IOException if an I/O error is thrown by a visitor method
    579     */
    580    public static boolean directoryContentEquals(final Path path1, final Path path2, final int maxDepth,
    581        final LinkOption[] linkOptions, final FileVisitOption[] fileVisitOptions) throws IOException {
    582        return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, fileVisitOptions).equals;
    583    }
    584
    585    /**
    586     * Compares the file contents of two Paths to determine if they are equal or not.
    587     * <p>
    588     * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
    589     * </p>
    590     *
    591     * @param path1 the first stream.
    592     * @param path2 the second stream.
    593     * @return true if the content of the streams are equal or they both don't exist, false otherwise.
    594     * @throws NullPointerException if either input is null.
    595     * @throws IOException if an I/O error occurs.
    596     * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
    597     */
    598    public static boolean fileContentEquals(final Path path1, final Path path2) throws IOException {
    599        return fileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY);
    600    }
    601
    602    /**
    603     * Compares the file contents of two Paths to determine if they are equal or not.
    604     * <p>
    605     * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
    606     * </p>
    607     *
    608     * @param path1 the first stream.
    609     * @param path2 the second stream.
    610     * @param linkOptions options specifying how files are followed.
    611     * @param openOptions options specifying how files are opened.
    612     * @return true if the content of the streams are equal or they both don't exist, false otherwise.
    613     * @throws NullPointerException if either input is null.
    614     * @throws IOException if an I/O error occurs.
    615     * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
    616     */
    617    public static boolean fileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions,
    618        final OpenOption[] openOptions) throws IOException {
    619        if (path1 == null && path2 == null) {
    620            return true;
    621        }
    622        if (path1 == null || path2 == null) {
    623            return false;
    624        }
    625        final Path nPath1 = path1.normalize();
    626        final Path nPath2 = path2.normalize();
    627        final boolean path1Exists = Files.exists(nPath1, linkOptions);
    628        if (path1Exists != Files.exists(nPath2, linkOptions)) {
    629            return false;
    630        }
    631        if (!path1Exists) {
    632            // Two not existing files are equal?
    633            // Same as FileUtils
    634            return true;
    635        }
    636        if (Files.isDirectory(nPath1, linkOptions)) {
    637            // don't compare directory contents.
    638            throw new IOException("Can't compare directories, only files: " + nPath1);
    639        }
    640        if (Files.isDirectory(nPath2, linkOptions)) {
    641            // don't compare directory contents.
    642            throw new IOException("Can't compare directories, only files: " + nPath2);
    643        }
    644        if (Files.size(nPath1) != Files.size(nPath2)) {
    645            // lengths differ, cannot be equal
    646            return false;
    647        }
    648        if (path1.equals(path2)) {
    649            // same file
    650            return true;
    651        }
    652        try (final InputStream inputStream1 = Files.newInputStream(nPath1, openOptions);
    653            final InputStream inputStream2 = Files.newInputStream(nPath2, openOptions)) {
    654            return IOUtils.contentEquals(inputStream1, inputStream2);
    655        }
    656    }
    657
    658    /**
    659     * <p>
    660     * Applies an {@link IOFileFilter} to the provided {@link File} objects. The resulting array is a subset of the
    661     * original file list that matches the provided filter.
    662     * </p>
    663     *
    664     * <p>
    665     * The {@link Set} returned by this method is not guaranteed to be thread safe.
    666     * </p>
    667     *
    668     * <pre>
    669     * Set&lt;File&gt; allFiles = ...
    670     * Set&lt;File&gt; javaFiles = FileFilterUtils.filterSet(allFiles,
    671     *     FileFilterUtils.suffixFileFilter(".java"));
    672     * </pre>
    673     *
    674     * @param filter the filter to apply to the set of files.
    675     * @param paths the array of files to apply the filter to.
    676     *
    677     * @return a subset of {@code files} that is accepted by the file filter.
    678     * @throws IllegalArgumentException if the filter is {@code null} or {@code files} contains a {@code null}
    679     *         value.
    680     *
    681     * @since 2.9.0
    682     */
    683    public static Path[] filter(final PathFilter filter, final Path... paths) {
    684        Objects.requireNonNull(filter, "filter");
    685        if (paths == null) {
    686            return EMPTY_PATH_ARRAY;
    687        }
    688        return filterPaths(filter, Stream.of(paths), Collectors.toList()).toArray(EMPTY_PATH_ARRAY);
    689    }
    690
    691    private static <R, A> R filterPaths(final PathFilter filter, final Stream<Path> stream,
    692        final Collector<? super Path, A, R> collector) {
    693        Objects.requireNonNull(filter, "filter");
    694        Objects.requireNonNull(collector, "collector");
    695        if (stream == null) {
    696            return Stream.<Path>empty().collect(collector);
    697        }
    698        return stream.filter(p -> {
    699            try {
    700                return p != null && filter.accept(p, readBasicFileAttributes(p)) == FileVisitResult.CONTINUE;
    701            } catch (final IOException e) {
    702                return false;
    703            }
    704        }).collect(collector);
    705    }
    706
    707    /**
    708     * Reads the access control list from a file attribute view.
    709     *
    710     * @param sourcePath the path to the file.
    711     * @return a file attribute view of the specified type, or null if the attribute view type is not available.
    712     * @throws IOException if an I/O error occurs.
    713     * @since 2.8.0
    714     */
    715    public static List<AclEntry> getAclEntryList(final Path sourcePath) throws IOException {
    716        final AclFileAttributeView fileAttributeView = Files.getFileAttributeView(sourcePath,
    717            AclFileAttributeView.class);
    718        return fileAttributeView == null ? null : fileAttributeView.getAcl();
    719    }
    720
    721    /**
    722     * Tests whether the specified {@code Path} is a directory or not. Implemented as a
    723     * null-safe delegate to {@code Files.isDirectory(Path path, LinkOption... options)}.
    724     *
    725     * @param   path the path to the file.
    726     * @param   options options indicating how symbolic links are handled
    727     * @return  {@code true} if the file is a directory; {@code false} if
    728     *          the path is null, the file does not exist, is not a directory, or it cannot
    729     *          be determined if the file is a directory or not.
    730     * @throws SecurityException     In the case of the default provider, and a security manager is installed, the
    731     *                               {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read
    732     *                               access to the directory.
    733     * @since 2.9.0
    734     */
    735    public static boolean isDirectory(final Path path, final LinkOption... options) {
    736        return path != null && Files.isDirectory(path, options);
    737    }
    738
    739    /**
    740     * Tests whether the given file or directory is empty.
    741     *
    742     * @param path the file or directory to query.
    743     * @return whether the file or directory is empty.
    744     * @throws IOException if an I/O error occurs.
    745     */
    746    public static boolean isEmpty(final Path path) throws IOException {
    747        return Files.isDirectory(path) ? isEmptyDirectory(path) : isEmptyFile(path);
    748    }
    749
    750    /**
    751     * Tests whether the directory is empty.
    752     *
    753     * @param directory the directory to query.
    754     * @return whether the directory is empty.
    755     * @throws NotDirectoryException if the file could not otherwise be opened because it is not a directory
    756     *                               <i>(optional specific exception)</i>.
    757     * @throws IOException           if an I/O error occurs.
    758     * @throws SecurityException     In the case of the default provider, and a security manager is installed, the
    759     *                               {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read
    760     *                               access to the directory.
    761     */
    762    public static boolean isEmptyDirectory(final Path directory) throws IOException {
    763        try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
    764            return !directoryStream.iterator().hasNext();
    765        }
    766    }
    767
    768    /**
    769     * Tests whether the given file is empty.
    770     *
    771     * @param file the file to query.
    772     * @return whether the file is empty.
    773     * @throws IOException       if an I/O error occurs.
    774     * @throws SecurityException In the case of the default provider, and a security manager is installed, its
    775     *                           {@link SecurityManager#checkRead(String) checkRead} method denies read access to the
    776     *                           file.
    777     */
    778    public static boolean isEmptyFile(final Path file) throws IOException {
    779        return Files.size(file) <= 0;
    780    }
    781
    782    /**
    783     * Tests if the specified {@code Path} is newer than the specified time reference.
    784     *
    785     * @param file the {@code Path} of which the modification date must be compared
    786     * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
    787     * @param options options indicating how symbolic links are handled * @return true if the {@code Path} exists and
    788     *        has been modified after the given time reference.
    789     * @return true if the {@code Path} exists and has been modified after the given time reference.
    790     * @throws IOException if an I/O error occurs.
    791     * @throws NullPointerException if the file is {@code null}
    792     * @since 2.9.0
    793     */
    794    public static boolean isNewer(final Path file, final long timeMillis, final LinkOption... options)
    795        throws IOException {
    796        Objects.requireNonNull(file, "file");
    797        if (Files.notExists(file)) {
    798            return false;
    799        }
    800        return Files.getLastModifiedTime(file, options).toMillis() > timeMillis;
    801    }
    802
    803    /**
    804     * Tests whether the specified {@code Path} is a regular file or not. Implemented as a
    805     * null-safe delegate to {@code Files.isRegularFile(Path path, LinkOption... options)}.
    806     *
    807     * @param   path the path to the file.
    808     * @param   options options indicating how symbolic links are handled
    809     * @return  {@code true} if the file is a regular file; {@code false} if
    810     *          the path is null, the file does not exist, is not a directory, or it cannot
    811     *          be determined if the file is a regular file or not.
    812     * @throws SecurityException     In the case of the default provider, and a security manager is installed, the
    813     *                               {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read
    814     *                               access to the directory.
    815     * @since 2.9.0
    816     */
    817    public static boolean isRegularFile(final Path path, final LinkOption... options) {
    818        return path != null && Files.isRegularFile(path, options);
    819    }
    820
    821    /**
    822     * Creates a new DirectoryStream for Paths rooted at the given directory.
    823     *
    824     * @param dir the path to the directory to stream.
    825     * @param pathFilter the directory stream filter.
    826     * @return a new instance.
    827     * @throws IOException if an I/O error occurs.
    828     */
    829    public static DirectoryStream<Path> newDirectoryStream(final Path dir, final PathFilter pathFilter)
    830        throws IOException {
    831        return Files.newDirectoryStream(dir, new DirectoryStreamFilter(pathFilter));
    832    }
    833
    834    /**
    835     * Returns true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
    836     *
    837     * @param deleteOptions the array to test
    838     * @return true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
    839     */
    840    private static boolean overrideReadOnly(final DeleteOption... deleteOptions) {
    841        if (deleteOptions == null) {
    842            return false;
    843        }
    844        return Stream.of(deleteOptions).anyMatch(e -> e == StandardDeleteOption.OVERRIDE_READ_ONLY);
    845    }
    846
    847    /**
    848     * Shorthand for {@code Files.readAttributes(path, BasicFileAttributes.class)}
    849     *
    850     * @param path the path to read.
    851     * @return the path attributes.
    852     * @throws IOException if an I/O error occurs.
    853     * @since 2.9.0
    854     */
    855    public static BasicFileAttributes readBasicFileAttributes(final Path path) throws IOException {
    856        return Files.readAttributes(path, BasicFileAttributes.class);
    857    }
    858
    859    /**
    860     * Shorthand for {@code Files.readAttributes(path, BasicFileAttributes.class)} while wrapping {@link IOException}
    861     * as {@link UncheckedIOException}.
    862     *
    863     * @param path the path to read.
    864     * @return the path attributes.
    865     * @throws UncheckedIOException if an I/O error occurs
    866     * @since 2.9.0
    867     */
    868    public static BasicFileAttributes readBasicFileAttributesUnchecked(final Path path) {
    869        try {
    870            return readBasicFileAttributes(path);
    871        } catch (final IOException e) {
    872            throw new UncheckedIOException(e);
    873        }
    874    }
    875
    876    /**
    877     * Relativizes all files in the given {@code collection} against a {@code parent}.
    878     *
    879     * @param collection The collection of paths to relativize.
    880     * @param parent relativizes against this parent path.
    881     * @param sort Whether to sort the result.
    882     * @param comparator How to sort.
    883     * @return A collection of relativized paths, optionally sorted.
    884     */
    885    static List<Path> relativize(final Collection<Path> collection, final Path parent, final boolean sort,
    886        final Comparator<? super Path> comparator) {
    887        Stream<Path> stream = collection.stream().map(parent::relativize);
    888        if (sort) {
    889            stream = comparator == null ? stream.sorted() : stream.sorted(comparator);
    890        }
    891        return stream.collect(Collectors.toList());
    892    }
    893
    894    /**
    895     * Sets the given Path to the {@code readOnly} value.
    896     * <p>
    897     * This behavior is OS dependent.
    898     * </p>
    899     *
    900     * @param path The path to set.
    901     * @param readOnly true for read-only, false for not read-only.
    902     * @param linkOptions options indicating how symbolic links are handled.
    903     * @return The given path.
    904     * @throws IOException if an I/O error occurs.
    905     * @since 2.8.0
    906     */
    907    public static Path setReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions)
    908        throws IOException {
    909        final List<Exception> causeList = new ArrayList<>(2);
    910        final DosFileAttributeView fileAttributeView = Files.getFileAttributeView(path, DosFileAttributeView.class,
    911            linkOptions);
    912        if (fileAttributeView != null) {
    913            try {
    914                fileAttributeView.setReadOnly(readOnly);
    915                return path;
    916            } catch (final IOException e) {
    917                // ignore for now, retry with PosixFileAttributeView
    918                causeList.add(e);
    919            }
    920        }
    921        final PosixFileAttributeView posixFileAttributeView = Files.getFileAttributeView(path,
    922            PosixFileAttributeView.class, linkOptions);
    923        if (posixFileAttributeView != null) {
    924            // Works on Windows but not on Ubuntu:
    925            // Files.setAttribute(path, "unix:readonly", readOnly, options);
    926            // java.lang.IllegalArgumentException: 'unix:readonly' not recognized
    927            final PosixFileAttributes readAttributes = posixFileAttributeView.readAttributes();
    928            final Set<PosixFilePermission> permissions = readAttributes.permissions();
    929            permissions.remove(PosixFilePermission.OWNER_WRITE);
    930            permissions.remove(PosixFilePermission.GROUP_WRITE);
    931            permissions.remove(PosixFilePermission.OTHERS_WRITE);
    932            try {
    933                return Files.setPosixFilePermissions(path, permissions);
    934            } catch (final IOException e) {
    935                causeList.add(e);
    936            }
    937        }
    938        if (!causeList.isEmpty()) {
    939            throw new IOExceptionList(path.toString(), causeList);
    940        }
    941        throw new IOException(
    942            String.format("No DosFileAttributeView or PosixFileAttributeView for '%s' (linkOptions=%s)", path,
    943                Arrays.toString(linkOptions)));
    944    }
    945
    946    /**
    947     * Converts an array of {@link FileVisitOption} to a {@link Set}.
    948     *
    949     * @param fileVisitOptions input array.
    950     * @return a new Set.
    951     */
    952    static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... fileVisitOptions) {
    953        return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class)
    954            : Stream.of(fileVisitOptions).collect(Collectors.toSet());
    955    }
    956
    957    /**
    958     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
    959     *
    960     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
    961     *
    962     * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
    963     * @param directory See {@link Files#walkFileTree(Path,FileVisitor)}.
    964     * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
    965     * @return the given visitor.
    966     *
    967     * @throws IOException if an I/O error is thrown by a visitor method
    968     */
    969    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path directory)
    970        throws IOException {
    971        Files.walkFileTree(directory, visitor);
    972        return visitor;
    973    }
    974
    975    /**
    976     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
    977     *
    978     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
    979     *
    980     * @param start See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    981     * @param options See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    982     * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    983     * @param visitor See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    984     * @param <T> See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    985     * @return the given visitor.
    986     *
    987     * @throws IOException if an I/O error is thrown by a visitor method
    988     */
    989    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path start,
    990        final Set<FileVisitOption> options, final int maxDepth) throws IOException {
    991        Files.walkFileTree(start, options, maxDepth, visitor);
    992        return visitor;
    993    }
    994
    995    /**
    996     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
    997     *
    998     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
    999     *
    1000     * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
    1001     * @param first See {@link Paths#get(String,String[])}.
    1002     * @param more See {@link Paths#get(String,String[])}.
    1003     * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
    1004     * @return the given visitor.
    1005     *
    1006     * @throws IOException if an I/O error is thrown by a visitor method
    1007     */
    1008    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final String first,
    1009        final String... more) throws IOException {
    1010        return visitFileTree(visitor, Paths.get(first, more));
    1011    }
    1012
    1013    /**
    1014     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
    1015     *
    1016     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
    1017     *
    1018     * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
    1019     * @param uri See {@link Paths#get(URI)}.
    1020     * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
    1021     * @return the given visitor.
    1022     *
    1023     * @throws IOException if an I/O error is thrown by a visitor method
    1024     */
    1025    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final URI uri)
    1026        throws IOException {
    1027        return visitFileTree(visitor, Paths.get(uri));
    1028    }
    1029
    1030    /**
    1031     * Returns a stream of filtered paths.
    1032     *
    1033     * @param start the start path
    1034     * @param pathFilter the path filter
    1035     * @param maxDepth the maximum depth of directories to walk.
    1036     * @param readAttributes whether to call the filters with file attributes (false passes null).
    1037     * @param options the options to configure the walk.
    1038     * @return a filtered stream of paths.
    1039     * @throws IOException if an I/O error is thrown when accessing the starting file.
    1040     * @since 2.9.0
    1041     */
    1042    public static Stream<Path> walk(final Path start, final PathFilter pathFilter, final int maxDepth,
    1043        final boolean readAttributes, final FileVisitOption... options) throws IOException {
    1044        return Files.walk(start, maxDepth, options).filter(path -> pathFilter.accept(path,
    1045            readAttributes ? readBasicFileAttributesUnchecked(path) : null) == FileVisitResult.CONTINUE);
    1046    }
    1047
    1048    /**
    1049     * Does allow to instantiate.
    1050     */
    1051    private PathUtils() {
    1052        // do not instantiate.
    1053    }
    1054
    1055}