    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.IOException;
    021import java.io.InputStream;
    022import java.net.URI;
    023import java.net.URL;
    024import java.nio.file.CopyOption;
    025import java.nio.file.DirectoryStream;
    026import java.nio.file.FileVisitOption;
    027import java.nio.file.FileVisitor;
    028import java.nio.file.Files;
    029import java.nio.file.LinkOption;
    030import java.nio.file.NotDirectoryException;
    031import java.nio.file.OpenOption;
    032import java.nio.file.Path;
    033import java.nio.file.Paths;
    034import java.util.Arrays;
    035import java.util.Collection;
    036import java.util.Collections;
    037import java.util.Comparator;
    038import java.util.EnumSet;
    039import java.util.List;
    040import java.util.Set;
    041import java.util.stream.Collectors;
    042import java.util.stream.Stream;
    043
    044import org.apache.commons.io.IOUtils;
    045import org.apache.commons.io.file.Counters.PathCounters;
    046
    047/**
    048 * NIO Path utilities.
    049 *
    050 * @since 2.7
    051 */
    052public final class PathUtils {
    053
    054    /**
    055     * Accumulates file tree information in a {@link AccumulatorPathVisitor}.
    056     *
    057     * @param directory The directory to accumulate information.
    058     * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    059     * @param linkOptions Options indicating how symbolic links are handled.
    060     * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    061     * @throws IOException if an I/O error is thrown by a visitor method.
    062     * @return file tree information.
    063     */
    064    private static AccumulatorPathVisitor accumulate(final Path directory, final int maxDepth,
    065            final LinkOption[] linkOptions, final FileVisitOption[] fileVisitOptions) throws IOException {
    066        return visitFileTree(AccumulatorPathVisitor.withLongCounters(), directory,
    067                toFileVisitOptionSet(fileVisitOptions), maxDepth);
    068    }
    069
    070    /**
    071     * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted
    072     * relative lists when comparing directories.
    073     */
    074    private static class RelativeSortedPaths {
    075
    076        final boolean equals;
    077        final List<Path> relativeDirList1; // might need later?
    078        final List<Path> relativeDirList2; // might need later?
    079        final List<Path> relativeFileList1;
    080        final List<Path> relativeFileList2;
    081
    082        /**
    083         * Constructs and initializes a new instance by accumulating directory and file info.
    084         *
    085         * @param dir1 First directory to compare.
    086         * @param dir2 Seconds directory to compare.
    087         * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    088         * @param linkOptions Options indicating how symbolic links are handled.
    089         * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    090         * @throws IOException if an I/O error is thrown by a visitor method.
    091         */
    092        private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth,
    093                final LinkOption[] linkOptions, final FileVisitOption[] fileVisitOptions) throws IOException {
    094            List<Path> tmpRelativeDirList1 = null;
    095            List<Path> tmpRelativeDirList2 = null;
    096            List<Path> tmpRelativeFileList1 = null;
    097            List<Path> tmpRelativeFileList2 = null;
    098            if (dir1 == null && dir2 == null) {
    099                equals = true;
    100            } else if (dir1 == null ^ dir2 == null) {
    101                equals = false;
    102            } else {
    103                final boolean parentDirExists1 = Files.exists(dir1, linkOptions);
    104                final boolean parentDirExists2 = Files.exists(dir2, linkOptions);
    105                if (!parentDirExists1 || !parentDirExists2) {
    106                    equals = !parentDirExists1 && !parentDirExists2;
    107                } else {
    108                    AccumulatorPathVisitor visitor1 = accumulate(dir1, maxDepth, linkOptions, fileVisitOptions);
    109                    AccumulatorPathVisitor visitor2 = accumulate(dir2, maxDepth, linkOptions, fileVisitOptions);
    110                    if (visitor1.getDirList().size() != visitor2.getDirList().size()
    111                            || visitor1.getFileList().size() != visitor2.getFileList().size()) {
    112                        equals = false;
    113                    } else {
    114                        tmpRelativeDirList1 = visitor1.relativizeDirectories(dir1, true, null);
    115                        tmpRelativeDirList2 = visitor2.relativizeDirectories(dir2, true, null);
    116                        if (!tmpRelativeDirList1.equals(tmpRelativeDirList2)) {
    117                            equals = false;
    118                        } else {
    119                            tmpRelativeFileList1 = visitor1.relativizeFiles(dir1, true, null);
    120                            tmpRelativeFileList2 = visitor2.relativizeFiles(dir2, true, null);
    121                            equals = tmpRelativeFileList1.equals(tmpRelativeFileList2);
    122                        }
    123                    }
    124                }
    125            }
    126            relativeDirList1 = tmpRelativeDirList1;
    127            relativeDirList2 = tmpRelativeDirList2;
    128            relativeFileList1 = tmpRelativeFileList1;
    129            relativeFileList2 = tmpRelativeFileList2;
    130        }
    131    }
    132
    133    /**
    134     * Empty {@link FileVisitOption} array.
    135     */
    136    public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = new FileVisitOption[0];
    137
    138    /**
    139     * Empty {@link LinkOption} array.
    140     */
    141    public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = new LinkOption[0];
    142
    143    /**
    144     * Empty {@link OpenOption} array.
    145     */
    146    public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = new OpenOption[0];
    147
    148    /**
    149     * Cleans a directory including sub-directories without deleting directories.
    150     *
    151     * @param directory directory to clean.
    152     * @return The visitation path counters.
    153     * @throws IOException if an I/O error is thrown by a visitor method.
    154     */
    155    public static PathCounters cleanDirectory(final Path directory) throws IOException {
    156        return visitFileTree(CleaningPathVisitor.withLongCounters(), directory).getPathCounters();
    157    }
    158
    159    /**
    160     * Copies a directory to another directory.
    161     *
    162     * @param sourceDirectory The source directory.
    163     * @param targetDirectory The target directory.
    164     * @param copyOptions Specifies how the copying should be done.
    165     * @return The visitation path counters.
    166     * @throws IOException if an I/O error is thrown by a visitor method.
    167     */
    168    public static PathCounters copyDirectory(final Path sourceDirectory, final Path targetDirectory,
    169            final CopyOption... copyOptions) throws IOException {
    170        return visitFileTree(
    171                new CopyDirectoryVisitor(Counters.longPathCounters(), sourceDirectory, targetDirectory, copyOptions),
    172                sourceDirectory).getPathCounters();
    173    }
    174
    175    /**
    176     * Copies a URL to a directory.
    177     *
    178     * @param sourceFile The source URL.
    179     * @param targetFile The target file.
    180     * @param copyOptions Specifies how the copying should be done.
    181     * @return The target file
    182     * @throws IOException if an I/O error occurs
    183     * @see Files#copy(InputStream, Path, CopyOption...)
    184     */
    185    public static Path copyFile(final URL sourceFile, final Path targetFile, final CopyOption... copyOptions)
    186            throws IOException {
    187        try (final InputStream inputStream = sourceFile.openStream()) {
    188            Files.copy(inputStream, targetFile, copyOptions);
    189            return targetFile;
    190        }
    191    }
    192
    193    /**
    194     * Copies a file to a directory.
    195     *
    196     * @param sourceFile The source file.
    197     * @param targetDirectory The target directory.
    198     * @param copyOptions Specifies how the copying should be done.
    199     * @return The target file
    200     * @throws IOException if an I/O error occurs
    201     * @see Files#copy(Path, Path, CopyOption...)
    202     */
    203    public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory,
    204            final CopyOption... copyOptions) throws IOException {
    205        return Files.copy(sourceFile, targetDirectory.resolve(sourceFile.getFileName()), copyOptions);
    206    }
    207
    208    /**
    209     * Copies a URL to a directory.
    210     *
    211     * @param sourceFile The source URL.
    212     * @param targetDirectory The target directory.
    213     * @param copyOptions Specifies how the copying should be done.
    214     * @return The target file
    215     * @throws IOException if an I/O error occurs
    216     * @see Files#copy(InputStream, Path, CopyOption...)
    217     */
    218    public static Path copyFileToDirectory(final URL sourceFile, final Path targetDirectory,
    219            final CopyOption... copyOptions) throws IOException {
    220        try (final InputStream inputStream = sourceFile.openStream()) {
    221            Files.copy(inputStream, targetDirectory.resolve(sourceFile.getFile()), copyOptions);
    222            return targetDirectory;
    223        }
    224    }
    225
    226    /**
    227     * Counts aspects of a directory including sub-directories.
    228     *
    229     * @param directory directory to delete.
    230     * @return The visitor used to count the given directory.
    231     * @throws IOException if an I/O error is thrown by a visitor method.
    232     */
    233    public static PathCounters countDirectory(final Path directory) throws IOException {
    234        return visitFileTree(new CountingPathVisitor(Counters.longPathCounters()), directory).getPathCounters();
    235    }
    236
    237    /**
    238     * Deletes a file or directory. If the path is a directory, delete it and all sub-directories.
    239     * <p>
    240     * The difference between File.delete() and this method are:
    241     * </p>
    242     * <ul>
    243     * <li>A directory to delete does not have to be empty.</li>
    244     * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a
    245     * boolean.
    246     * </ul>
    247     *
    248     * @param path file or directory to delete, must not be {@code null}
    249     * @return The visitor used to delete the given directory.
    250     * @throws NullPointerException if the directory is {@code null}
    251     * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs.
    252     */
    253    public static PathCounters delete(final Path path) throws IOException {
    254        return Files.isDirectory(path) ? deleteDirectory(path) : deleteFile(path);
    255    }
    256
    257    /**
    258     * Deletes a directory including sub-directories.
    259     *
    260     * @param directory directory to delete.
    261     * @return The visitor used to delete the given directory.
    262     * @throws IOException if an I/O error is thrown by a visitor method.
    263     */
    264    public static PathCounters deleteDirectory(final Path directory) throws IOException {
    265        return visitFileTree(DeletingPathVisitor.withLongCounters(), directory).getPathCounters();
    266    }
    267
    268    /**
    269     * Deletes the given file.
    270     *
    271     * @param file The file to delete.
    272     * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
    273     * @throws IOException if an I/O error occurs.
    274     * @throws NotDirectoryException if the file is a directory.
    275     */
    276    public static PathCounters deleteFile(final Path file) throws IOException {
    277        if (Files.isDirectory(file)) {
    278            throw new NotDirectoryException(file.toString());
    279        }
    280        final PathCounters pathCounts = Counters.longPathCounters();
    281        final long size = Files.exists(file) ? Files.size(file) : 0;
    282        if (Files.deleteIfExists(file)) {
    283            pathCounts.getFileCounter().increment();
    284            pathCounts.getByteCounter().add(size);
    285        }
    286        return pathCounts;
    287    }
    288
    289    /**
    290     * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The
    291     * comparison includes all files in all sub-directories.
    292     *
    293     * @param path1 The first directory.
    294     * @param path2 The second directory.
    295     * @return Whether the two directories contain the same files while considering file contents.
    296     * @throws IOException if an I/O error is thrown by a visitor method
    297     */
    298    public static boolean directoryAndFileContentEquals(final Path path1, final Path path2) throws IOException {
    299        return directoryAndFileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY,
    300                EMPTY_FILE_VISIT_OPTION_ARRAY);
    301    }
    302
    303    /**
    304     * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The
    305     * comparison includes all files in all sub-directories.
    306     *
    307     * @param path1 The first directory.
    308     * @param path2 The second directory.
    309     * @param linkOptions options to follow links.
    310     * @param openOptions options to open files.
    311     * @param fileVisitOption options to configure traversal.
    312     * @return Whether the two directories contain the same files while considering file contents.
    313     * @throws IOException if an I/O error is thrown by a visitor method
    314     */
    315    public static boolean directoryAndFileContentEquals(final Path path1, final Path path2,
    316            final LinkOption[] linkOptions, final OpenOption[] openOptions, final FileVisitOption[] fileVisitOption)
    317            throws IOException {
    318        // First walk both file trees and gather normalized paths.
    319        if (path1 == null && path2 == null) {
    320            return true;
    321        }
    322        if (path1 == null ^ path2 == null) {
    323            return false;
    324        }
    325        if (!Files.exists(path1) && !Files.exists(path2)) {
    326            return true;
    327        }
    328        final RelativeSortedPaths relativeSortedPaths = new RelativeSortedPaths(path1, path2, Integer.MAX_VALUE,
    329                linkOptions, fileVisitOption);
    330        // If the normalized path names and counts are not the same, no need to compare contents.
    331        if (!relativeSortedPaths.equals) {
    332            return false;
    333        }
    334        // Both visitors contain the same normalized paths, we can compare file contents.
    335        final List<Path> fileList1 = relativeSortedPaths.relativeFileList1;
    336        final List<Path> fileList2 = relativeSortedPaths.relativeFileList2;
    337        for (Path path : fileList1) {
    338            final int binarySearch = Collections.binarySearch(fileList2, path);
    339            if (binarySearch > -1) {
    340                if (!fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) {
    341                    return false;
    342                }
    343            } else {
    344                throw new IllegalStateException(String.format("Unexpected mismatch."));
    345            }
    346        }
    347        return true;
    348    }
    349
    350    /**
    351     * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The
    352     * comparison includes all files in all sub-directories.
    353     *
    354     * @param path1 The first directory.
    355     * @param path2 The second directory.
    356     * @return Whether the two directories contain the same files without considering file contents.
    357     * @throws IOException if an I/O error is thrown by a visitor method
    358     */
    359    public static boolean directoryContentEquals(final Path path1, final Path path2) throws IOException {
    360        return directoryContentEquals(path1, path2, Integer.MAX_VALUE, EMPTY_LINK_OPTION_ARRAY,
    361                EMPTY_FILE_VISIT_OPTION_ARRAY);
    362    }
    363
    364    /**
    365     * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The
    366     * comparison includes all files in all sub-directories.
    367     *
    368     * @param path1 The first directory.
    369     * @param path2 The second directory.
    370     * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    371     * @param linkOptions options to follow links.
    372     * @param fileVisitOptions options to configure the traversal
    373     * @return Whether the two directories contain the same files without considering file contents.
    374     * @throws IOException if an I/O error is thrown by a visitor method
    375     */
    376    public static boolean directoryContentEquals(final Path path1, final Path path2, final int maxDepth,
    377            LinkOption[] linkOptions, FileVisitOption[] fileVisitOptions) throws IOException {
    378        return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, fileVisitOptions).equals;
    379    }
    380
    381    /**
    382     * Compares the file contents of two Paths to determine if they are equal or not.
    383     * <p>
    384     * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
    385     * </p>
    386     *
    387     * @param path1 the first stream.
    388     * @param path2 the second stream.
    389     * @return true if the content of the streams are equal or they both don't exist, false otherwise.
    390     * @throws NullPointerException if either input is null.
    391     * @throws IOException if an I/O error occurs.
    392     * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
    393     */
    394    public static boolean fileContentEquals(final Path path1, final Path path2) throws IOException {
    395        return fileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY);
    396    }
    397
    398    /**
    399     * Compares the file contents of two Paths to determine if they are equal or not.
    400     * <p>
    401     * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
    402     * </p>
    403     *
    404     * @param path1 the first stream.
    405     * @param path2 the second stream.
    406     * @param linkOptions options specifying how files are followed.
    407     * @param openOptions options specifying how files are opened.
    408     * @return true if the content of the streams are equal or they both don't exist, false otherwise.
    409     * @throws NullPointerException if either input is null.
    410     * @throws IOException if an I/O error occurs.
    411     * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
    412     */
    413    public static boolean fileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions,
    414            final OpenOption[] openOptions) throws IOException {
    415        if (path1 == null && path2 == null) {
    416            return true;
    417        }
    418        if (path1 == null ^ path2 == null) {
    419            return false;
    420        }
    421        final Path nPath1 = path1.normalize();
    422        final Path nPath2 = path2.normalize();
    423        final boolean path1Exists = Files.exists(nPath1, linkOptions);
    424        if (path1Exists != Files.exists(nPath2, linkOptions)) {
    425            return false;
    426        }
    427        if (!path1Exists) {
    428            // Two not existing files are equal?
    429            // Same as FileUtils
    430            return true;
    431        }
    432        if (Files.isDirectory(nPath1, linkOptions)) {
    433            // don't compare directory contents.
    434            throw new IOException("Can't compare directories, only files: " + nPath1);
    435        }
    436        if (Files.isDirectory(nPath2, linkOptions)) {
    437            // don't compare directory contents.
    438            throw new IOException("Can't compare directories, only files: " + nPath2);
    439        }
    440        if (Files.size(nPath1) != Files.size(nPath2)) {
    441            // lengths differ, cannot be equal
    442            return false;
    443        }
    444        if (path1.equals(path2)) {
    445            // same file
    446            return true;
    447        }
    448        try (final InputStream inputStream1 = Files.newInputStream(nPath1, openOptions);
    449                final InputStream inputStream2 = Files.newInputStream(nPath2, openOptions)) {
    450            return IOUtils.contentEquals(inputStream1, inputStream2);
    451        }
    452    }
    453
    454    /**
    455     * Returns whether the given file or directory is empty.
    456     *
    457     * @param path the the given file or directory to query.
    458     * @return whether the given file or directory is empty.
    459     * @throws IOException if an I/O error occurs
    460     */
    461    public static boolean isEmpty(final Path path) throws IOException {
    462        return Files.isDirectory(path) ? isEmptyDirectory(path) : isEmptyFile(path);
    463    }
    464
    465    /**
    466     * Returns whether the directory is empty.
    467     *
    468     * @param directory the the given directory to query.
    469     * @return whether the given directory is empty.
    470     * @throws IOException if an I/O error occurs
    471     */
    472    public static boolean isEmptyDirectory(final Path directory) throws IOException {
    473        try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
    474            if (directoryStream.iterator().hasNext()) {
    475                return false;
    476            }
    477        }
    478        return true;
    479    }
    480
    481    /**
    482     * Returns whether the given file is empty.
    483     *
    484     * @param file the the given file to query.
    485     * @return whether the given file is empty.
    486     * @throws IOException if an I/O error occurs
    487     */
    488    public static boolean isEmptyFile(final Path file) throws IOException {
    489        return Files.size(file) <= 0;
    490    }
    491
    492    /**
    493     * Relativizes all files in the given {@code collection} against a {@code parent}.
    494     *
    495     * @param collection The collection of paths to relativize.
    496     * @param parent relativizes against this parent path.
    497     * @param sort Whether to sort the result.
    498     * @param comparator How to sort.
    499     * @return A collection of relativized paths, optionally sorted.
    500     */
    501    static List<Path> relativize(Collection<Path> collection, Path parent, boolean sort,
    502            Comparator<? super Path> comparator) {
    503        Stream<Path> stream = collection.stream().map(e -> parent.relativize(e));
    504        if (sort) {
    505            stream = comparator == null ? stream.sorted() : stream.sorted(comparator);
    506        }
    507        return stream.collect(Collectors.toList());
    508    }
    509
    510    /**
    511     * Converts an array of {@link FileVisitOption} to a {@link Set}.
    512     *
    513     * @param fileVisitOptions input array.
    514     * @return a new Set.
    515     */
    516    static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... fileVisitOptions) {
    517        return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class)
    518                : Arrays.stream(fileVisitOptions).collect(Collectors.toSet());
    519    }
    520
    521    /**
    522     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
    523     *
    524     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
    525     *
    526     * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
    527     * @param directory See {@link Files#walkFileTree(Path,FileVisitor)}.
    528     * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
    529     * @return the given visitor.
    530     *
    531     * @throws IOException if an I/O error is thrown by a visitor method
    532     */
    533    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path directory)
    534            throws IOException {
    535        Files.walkFileTree(directory, visitor);
    536        return visitor;
    537    }
    538
    539    /**
    540     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
    541     *
    542     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
    543     *
    544     * @param start See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    545     * @param options See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    546     * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    547     * @param visitor See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    548     * @param <T> See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
    549     * @return the given visitor.
    550     *
    551     * @throws IOException if an I/O error is thrown by a visitor method
    552     */
    553    public static <T extends FileVisitor<? super Path>> T visitFileTree(T visitor, Path start,
    554            Set<FileVisitOption> options, int maxDepth) throws IOException {
    555        Files.walkFileTree(start, options, maxDepth, visitor);
    556        return visitor;
    557    }
    558
    559    /**
    560     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
    561     *
    562     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
    563     *
    564     * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
    565     * @param first See {@link Paths#get(String,String[])}.
    566     * @param more See {@link Paths#get(String,String[])}.
    567     * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
    568     * @return the given visitor.
    569     *
    570     * @throws IOException if an I/O error is thrown by a visitor method
    571     */
    572    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final String first,
    573            final String... more) throws IOException {
    574        return visitFileTree(visitor, Paths.get(first, more));
    575    }
    576
    577    /**
    578     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
    579     *
    580     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
    581     *
    582     * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
    583     * @param uri See {@link Paths#get(URI)}.
    584     * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
    585     * @return the given visitor.
    586     *
    587     * @throws IOException if an I/O error is thrown by a visitor method
    588     */
    589    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final URI uri)
    590            throws IOException {
    591        return visitFileTree(visitor, Paths.get(uri));
    592    }
    593
    594    /**
    595     * Does allow to instantiate.
    596     */
    597    private PathUtils() {
    598        // do not instantiate.
    599    }
    600
    601}