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