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