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.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.RandomAccessFileMode;
075import org.apache.commons.io.RandomAccessFiles;
076import org.apache.commons.io.ThreadUtils;
077import org.apache.commons.io.file.Counters.PathCounters;
078import org.apache.commons.io.file.attribute.FileTimes;
079import org.apache.commons.io.filefilter.IOFileFilter;
080import org.apache.commons.io.function.IOFunction;
081import org.apache.commons.io.function.IOSupplier;
082
083/**
084 * NIO Path utilities.
085 *
086 * @since 2.7
087 */
088public final class PathUtils {
089
090    /**
091     * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted relative lists when comparing directories.
092     */
093    private static final 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, final FileAttribute<?>... attrs) throws IOException {
394        Path parent = getParent(path);
395        parent = linkOption == LinkOption.NOFOLLOW_LINKS ? parent : readIfSymbolicLink(parent);
396        if (parent == null) {
397            return null;
398        }
399        final boolean exists = linkOption == null ? Files.exists(parent) : Files.exists(parent, linkOption);
400        return exists ? parent : Files.createDirectories(parent, attrs);
401    }
402
403    /**
404     * Gets the current directory.
405     *
406     * @return the current directory.
407     *
408     * @since 2.9.0
409     */
410    public static Path current() {
411        return Paths.get(".");
412    }
413
414    /**
415     * Deletes a file or directory. If the path is a directory, delete it and all subdirectories.
416     * <p>
417     * The difference between File.delete() and this method are:
418     * </p>
419     * <ul>
420     * <li>A directory to delete does not have to be empty.</li>
421     * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a boolean.
422     * </ul>
423     *
424     * @param path file or directory to delete, must not be {@code null}
425     * @return The visitor used to delete the given directory.
426     * @throws NullPointerException if the directory is {@code null}
427     * @throws IOException          if an I/O error is thrown by a visitor method or if an I/O error occurs.
428     */
429    public static PathCounters delete(final Path path) throws IOException {
430        return delete(path, EMPTY_DELETE_OPTION_ARRAY);
431    }
432
433    /**
434     * Deletes a file or directory. If the path is a directory, delete it and all subdirectories.
435     * <p>
436     * The difference between File.delete() and this method are:
437     * </p>
438     * <ul>
439     * <li>A directory to delete does not have to be empty.</li>
440     * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a boolean.
441     * </ul>
442     *
443     * @param path          file or directory to delete, must not be {@code null}
444     * @param deleteOptions How to handle deletion.
445     * @return The visitor used to delete the given directory.
446     * @throws NullPointerException if the directory is {@code null}
447     * @throws IOException          if an I/O error is thrown by a visitor method or if an I/O error occurs.
448     * @since 2.8.0
449     */
450    public static PathCounters delete(final Path path, final DeleteOption... deleteOptions) throws IOException {
451        // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
452        return Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) ? deleteDirectory(path, deleteOptions) : deleteFile(path, deleteOptions);
453    }
454
455    /**
456     * Deletes a file or directory. If the path is a directory, delete it and all subdirectories.
457     * <p>
458     * The difference between File.delete() and this method are:
459     * </p>
460     * <ul>
461     * <li>A directory to delete does not have to be empty.</li>
462     * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a boolean.
463     * </ul>
464     *
465     * @param path          file or directory to delete, must not be {@code null}
466     * @param linkOptions   How to handle symbolic links.
467     * @param deleteOptions How to handle deletion.
468     * @return The visitor used to delete the given directory.
469     * @throws NullPointerException if the directory is {@code null}
470     * @throws IOException          if an I/O error is thrown by a visitor method or if an I/O error occurs.
471     * @since 2.9.0
472     */
473    public static PathCounters delete(final Path path, final LinkOption[] linkOptions, final DeleteOption... deleteOptions) throws IOException {
474        // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
475        return Files.isDirectory(path, linkOptions) ? deleteDirectory(path, linkOptions, deleteOptions) : deleteFile(path, linkOptions, deleteOptions);
476    }
477
478    /**
479     * Deletes a directory including subdirectories.
480     *
481     * @param directory directory to delete.
482     * @return The visitor used to delete the given directory.
483     * @throws IOException if an I/O error is thrown by a visitor method.
484     */
485    public static PathCounters deleteDirectory(final Path directory) throws IOException {
486        return deleteDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
487    }
488
489    /**
490     * Deletes a directory including subdirectories.
491     *
492     * @param directory     directory to delete.
493     * @param deleteOptions How to handle deletion.
494     * @return The visitor used to delete the given directory.
495     * @throws IOException if an I/O error is thrown by a visitor method.
496     * @since 2.8.0
497     */
498    public static PathCounters deleteDirectory(final Path directory, final DeleteOption... deleteOptions) throws IOException {
499        final LinkOption[] linkOptions = PathUtils.noFollowLinkOptionArray();
500        // POSIX ops will noop on non-POSIX.
501        return withPosixFileAttributes(getParent(directory), linkOptions, overrideReadOnly(deleteOptions),
502                pfa -> visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), directory).getPathCounters());
503    }
504
505    /**
506     * Deletes a directory including subdirectories.
507     *
508     * @param directory     directory to delete.
509     * @param linkOptions   How to handle symbolic links.
510     * @param deleteOptions How to handle deletion.
511     * @return The visitor used to delete the given directory.
512     * @throws IOException if an I/O error is thrown by a visitor method.
513     * @since 2.9.0
514     */
515    public static PathCounters deleteDirectory(final Path directory, final LinkOption[] linkOptions, final DeleteOption... deleteOptions) throws IOException {
516        return visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), directory).getPathCounters();
517    }
518
519    /**
520     * Deletes the given file.
521     *
522     * @param file The file to delete.
523     * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
524     * @throws IOException         if an I/O error occurs.
525     * @throws NoSuchFileException if the file is a directory.
526     */
527    public static PathCounters deleteFile(final Path file) throws IOException {
528        return deleteFile(file, EMPTY_DELETE_OPTION_ARRAY);
529    }
530
531    /**
532     * Deletes the given file.
533     *
534     * @param file          The file to delete.
535     * @param deleteOptions How to handle deletion.
536     * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
537     * @throws IOException         if an I/O error occurs.
538     * @throws NoSuchFileException if the file is a directory.
539     * @since 2.8.0
540     */
541    public static PathCounters deleteFile(final Path file, final DeleteOption... deleteOptions) throws IOException {
542        // Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files.
543        return deleteFile(file, noFollowLinkOptionArray(), deleteOptions);
544    }
545
546    /**
547     * Deletes the given file.
548     *
549     * @param file          The file to delete.
550     * @param linkOptions   How to handle symbolic links.
551     * @param deleteOptions How to handle deletion.
552     * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
553     * @throws IOException         if an I/O error occurs.
554     * @throws NoSuchFileException if the file is a directory.
555     * @since 2.9.0
556     */
557    public static PathCounters deleteFile(final Path file, final LinkOption[] linkOptions, final DeleteOption... deleteOptions)
558            throws NoSuchFileException, IOException {
559        //
560        // TODO Needs clean up
561        //
562        if (Files.isDirectory(file, linkOptions)) {
563            throw new NoSuchFileException(file.toString());
564        }
565        final PathCounters pathCounts = Counters.longPathCounters();
566        boolean exists = exists(file, linkOptions);
567        long size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
568        try {
569            if (Files.deleteIfExists(file)) {
570                pathCounts.getFileCounter().increment();
571                pathCounts.getByteCounter().add(size);
572                return pathCounts;
573            }
574        } catch (final AccessDeniedException ignored) {
575            // Ignore and try again below.
576        }
577        final Path parent = getParent(file);
578        PosixFileAttributes posixFileAttributes = null;
579        try {
580            if (overrideReadOnly(deleteOptions)) {
581                posixFileAttributes = readPosixFileAttributes(parent, linkOptions);
582                setReadOnly(file, false, linkOptions);
583            }
584            // Read size _after_ having read/execute access on POSIX.
585            exists = exists(file, linkOptions);
586            size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
587            if (Files.deleteIfExists(file)) {
588                pathCounts.getFileCounter().increment();
589                pathCounts.getByteCounter().add(size);
590            }
591        } finally {
592            if (posixFileAttributes != null) {
593                Files.setPosixFilePermissions(parent, posixFileAttributes.permissions());
594            }
595        }
596        return pathCounts;
597    }
598
599    /**
600     * Delegates to {@link File#deleteOnExit()}.
601     *
602     * @param path the path to delete.
603     * @since 3.13.0
604     */
605    public static void deleteOnExit(final Path path) {
606        Objects.requireNonNull(path.toFile()).deleteOnExit();
607    }
608
609    /**
610     * 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
611     * subdirectories.
612     *
613     * @param path1 The first directory.
614     * @param path2 The second directory.
615     * @return Whether the two directories contain the same files while considering file contents.
616     * @throws IOException if an I/O error is thrown by a visitor method.
617     */
618    public static boolean directoryAndFileContentEquals(final Path path1, final Path path2) throws IOException {
619        return directoryAndFileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY, EMPTY_FILE_VISIT_OPTION_ARRAY);
620    }
621
622    /**
623     * 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
624     * subdirectories.
625     *
626     * @param path1           The first directory.
627     * @param path2           The second directory.
628     * @param linkOptions     options to follow links.
629     * @param openOptions     options to open files.
630     * @param fileVisitOption options to configure traversal.
631     * @return Whether the two directories contain the same files while considering file contents.
632     * @throws IOException if an I/O error is thrown by a visitor method.
633     */
634    public static boolean directoryAndFileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, final OpenOption[] openOptions,
635            final FileVisitOption[] fileVisitOption) throws IOException {
636        // First walk both file trees and gather normalized paths.
637        if (path1 == null && path2 == null) {
638            return true;
639        }
640        if (path1 == null || path2 == null) {
641            return false;
642        }
643        if (notExists(path1) && notExists(path2)) {
644            return true;
645        }
646        final RelativeSortedPaths relativeSortedPaths = new RelativeSortedPaths(path1, path2, Integer.MAX_VALUE, linkOptions, fileVisitOption);
647        // If the normalized path names and counts are not the same, no need to compare contents.
648        if (!relativeSortedPaths.equals) {
649            return false;
650        }
651        // Both visitors contain the same normalized paths, we can compare file contents.
652        final List<Path> fileList1 = relativeSortedPaths.relativeFileList1;
653        final List<Path> fileList2 = relativeSortedPaths.relativeFileList2;
654        for (final Path path : fileList1) {
655            final int binarySearch = Collections.binarySearch(fileList2, path);
656            if (binarySearch <= -1) {
657                throw new IllegalStateException("Unexpected mismatch.");
658            }
659            if (!fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) {
660                return false;
661            }
662        }
663        return true;
664    }
665
666    /**
667     * 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
668     * subdirectories.
669     *
670     * @param path1 The first directory.
671     * @param path2 The second directory.
672     * @return Whether the two directories contain the same files without considering file contents.
673     * @throws IOException if an I/O error is thrown by a visitor method.
674     */
675    public static boolean directoryContentEquals(final Path path1, final Path path2) throws IOException {
676        return directoryContentEquals(path1, path2, Integer.MAX_VALUE, EMPTY_LINK_OPTION_ARRAY, EMPTY_FILE_VISIT_OPTION_ARRAY);
677    }
678
679    /**
680     * 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
681     * subdirectories.
682     *
683     * @param path1            The first directory.
684     * @param path2            The second directory.
685     * @param maxDepth         See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
686     * @param linkOptions      options to follow links.
687     * @param fileVisitOptions options to configure the traversal
688     * @return Whether the two directories contain the same files without considering file contents.
689     * @throws IOException if an I/O error is thrown by a visitor method.
690     */
691    public static boolean directoryContentEquals(final Path path1, final Path path2, final int maxDepth, final LinkOption[] linkOptions,
692            final FileVisitOption[] fileVisitOptions) throws IOException {
693        return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, fileVisitOptions).equals;
694    }
695
696    private static boolean exists(final Path path, final LinkOption... options) {
697        Objects.requireNonNull(path, "path");
698        return options != null ? Files.exists(path, options) : Files.exists(path);
699    }
700
701    /**
702     * Compares the file contents of two Paths to determine if they are equal or not.
703     * <p>
704     * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
705     * </p>
706     *
707     * @param path1 the first stream.
708     * @param path2 the second stream.
709     * @return true if the content of the streams are equal or they both don't exist, false otherwise.
710     * @throws NullPointerException if either input is null.
711     * @throws IOException          if an I/O error occurs.
712     * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
713     */
714    public static boolean fileContentEquals(final Path path1, final Path path2) throws IOException {
715        return fileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY);
716    }
717
718    /**
719     * Compares the file contents of two Paths to determine if they are equal or not.
720     * <p>
721     * File content is accessed through {@link RandomAccessFileMode#create(Path)}.
722     * </p>
723     *
724     * @param path1       the first stream.
725     * @param path2       the second stream.
726     * @param linkOptions options specifying how files are followed.
727     * @param openOptions ignored.
728     * @return true if the content of the streams are equal or they both don't exist, false otherwise.
729     * @throws NullPointerException if openOptions is null.
730     * @throws IOException          if an I/O error occurs.
731     * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
732     */
733    public static boolean fileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, final OpenOption[] openOptions)
734            throws IOException {
735        if (path1 == null && path2 == null) {
736            return true;
737        }
738        if (path1 == null || path2 == null) {
739            return false;
740        }
741        final Path nPath1 = path1.normalize();
742        final Path nPath2 = path2.normalize();
743        final boolean path1Exists = exists(nPath1, linkOptions);
744        if (path1Exists != exists(nPath2, linkOptions)) {
745            return false;
746        }
747        if (!path1Exists) {
748            // Two not existing files are equal?
749            // Same as FileUtils
750            return true;
751        }
752        if (Files.isDirectory(nPath1, linkOptions)) {
753            // don't compare directory contents.
754            throw new IOException("Can't compare directories, only files: " + nPath1);
755        }
756        if (Files.isDirectory(nPath2, linkOptions)) {
757            // don't compare directory contents.
758            throw new IOException("Can't compare directories, only files: " + nPath2);
759        }
760        if (Files.size(nPath1) != Files.size(nPath2)) {
761            // lengths differ, cannot be equal
762            return false;
763        }
764        if (path1.equals(path2)) {
765            // same file
766            return true;
767        }
768        // Faster:
769        try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(path1.toRealPath(linkOptions));
770                RandomAccessFile raf2 = RandomAccessFileMode.READ_ONLY.create(path2.toRealPath(linkOptions))) {
771            return RandomAccessFiles.contentEquals(raf1, raf2);
772        } catch (UnsupportedOperationException e) {
773            // Slower:
774            // Handle
775            // java.lang.UnsupportedOperationException
776            // at com.sun.nio.zipfs.ZipPath.toFile(ZipPath.java:656)
777            try (InputStream inputStream1 = Files.newInputStream(nPath1, openOptions);
778                    InputStream inputStream2 = Files.newInputStream(nPath2, openOptions)) {
779                return IOUtils.contentEquals(inputStream1, inputStream2);
780            }
781        }
782    }
783
784    /**
785     * <p>
786     * 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
787     * filter.
788     * </p>
789     *
790     * <p>
791     * The {@link Set} returned by this method is not guaranteed to be thread safe.
792     * </p>
793     *
794     * <pre>
795     * Set&lt;File&gt; allFiles = ...
796     * Set&lt;File&gt; javaFiles = FileFilterUtils.filterSet(allFiles,
797     *     FileFilterUtils.suffixFileFilter(".java"));
798     * </pre>
799     *
800     * @param filter the filter to apply to the set of files.
801     * @param paths  the array of files to apply the filter to.
802     *
803     * @return a subset of {@code files} that is accepted by the file filter.
804     * @throws NullPointerException     if the filter is {@code null}
805     * @throws IllegalArgumentException if {@code files} contains a {@code null} value.
806     *
807     * @since 2.9.0
808     */
809    public static Path[] filter(final PathFilter filter, final Path... paths) {
810        Objects.requireNonNull(filter, "filter");
811        if (paths == null) {
812            return EMPTY_PATH_ARRAY;
813        }
814        return filterPaths(filter, Stream.of(paths), Collectors.toList()).toArray(EMPTY_PATH_ARRAY);
815    }
816
817    private static <R, A> R filterPaths(final PathFilter filter, final Stream<Path> stream, final Collector<? super Path, A, R> collector) {
818        Objects.requireNonNull(filter, "filter");
819        Objects.requireNonNull(collector, "collector");
820        if (stream == null) {
821            return Stream.<Path>empty().collect(collector);
822        }
823        return stream.filter(p -> {
824            try {
825                return p != null && filter.accept(p, readBasicFileAttributes(p)) == FileVisitResult.CONTINUE;
826            } catch (final IOException e) {
827                return false;
828            }
829        }).collect(collector);
830    }
831
832    /**
833     * Reads the access control list from a file attribute view.
834     *
835     * @param sourcePath the path to the file.
836     * @return a file attribute view of the given type, or null if the attribute view type is not available.
837     * @throws IOException if an I/O error occurs.
838     * @since 2.8.0
839     */
840    public static List<AclEntry> getAclEntryList(final Path sourcePath) throws IOException {
841        final AclFileAttributeView fileAttributeView = getAclFileAttributeView(sourcePath);
842        return fileAttributeView == null ? null : fileAttributeView.getAcl();
843    }
844
845    /**
846     * Shorthand for {@code Files.getFileAttributeView(path, AclFileAttributeView.class)}.
847     *
848     * @param path    the path to the file.
849     * @param options how to handle symbolic links.
850     * @return a AclFileAttributeView, or {@code null} if the attribute view type is not available.
851     * @since 2.12.0
852     */
853    public static AclFileAttributeView getAclFileAttributeView(final Path path, final LinkOption... options) {
854        return Files.getFileAttributeView(path, AclFileAttributeView.class, options);
855    }
856
857    /**
858     * Shorthand for {@code Files.getFileAttributeView(path, DosFileAttributeView.class)}.
859     *
860     * @param path    the path to the file.
861     * @param options how to handle symbolic links.
862     * @return a DosFileAttributeView, or {@code null} if the attribute view type is not available.
863     * @since 2.12.0
864     */
865    public static DosFileAttributeView getDosFileAttributeView(final Path path, final LinkOption... options) {
866        return Files.getFileAttributeView(path, DosFileAttributeView.class, options);
867    }
868
869    /**
870     * Gets the file's last modified time or null if the file does not exist.
871     * <p>
872     * The method provides a workaround for bug <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a> where {@link File#lastModified()}
873     * looses milliseconds and always ends in 000. This bug is in OpenJDK 8 and 9, and fixed in 11.
874     * </p>
875     *
876     * @param file the file to query.
877     * @return the file's last modified time.
878     * @throws IOException Thrown if an I/O error occurs.
879     * @since 2.12.0
880     */
881    public static FileTime getLastModifiedFileTime(final File file) throws IOException {
882        return getLastModifiedFileTime(file.toPath(), null, EMPTY_LINK_OPTION_ARRAY);
883    }
884
885    /**
886     * Gets the file's last modified time or null if the file does not exist.
887     *
888     * @param path            the file to query.
889     * @param defaultIfAbsent Returns this file time of the file does not exist, may be null.
890     * @param options         options indicating how symbolic links are handled.
891     * @return the file's last modified time.
892     * @throws IOException Thrown if an I/O error occurs.
893     * @since 2.12.0
894     */
895    public static FileTime getLastModifiedFileTime(final Path path, final FileTime defaultIfAbsent, final LinkOption... options) throws IOException {
896        return Files.exists(path) ? getLastModifiedTime(path, options) : defaultIfAbsent;
897    }
898
899    /**
900     * Gets the file's last modified time or null if the file does not exist.
901     *
902     * @param path    the file to query.
903     * @param options options indicating how symbolic links are handled.
904     * @return the file's last modified time.
905     * @throws IOException Thrown if an I/O error occurs.
906     * @since 2.12.0
907     */
908    public static FileTime getLastModifiedFileTime(final Path path, final LinkOption... options) throws IOException {
909        return getLastModifiedFileTime(path, null, options);
910    }
911
912    /**
913     * Gets the file's last modified time or null if the file does not exist.
914     *
915     * @param uri the file to query.
916     * @return the file's last modified time.
917     * @throws IOException Thrown if an I/O error occurs.
918     * @since 2.12.0
919     */
920    public static FileTime getLastModifiedFileTime(final URI uri) throws IOException {
921        return getLastModifiedFileTime(Paths.get(uri), null, EMPTY_LINK_OPTION_ARRAY);
922    }
923
924    /**
925     * Gets the file's last modified time or null if the file does not exist.
926     *
927     * @param url the file to query.
928     * @return the file's last modified time.
929     * @throws IOException        Thrown if an I/O error occurs.
930     * @throws URISyntaxException if the URL is not formatted strictly according to RFC2396 and cannot be converted to a URI.
931     * @since 2.12.0
932     */
933    public static FileTime getLastModifiedFileTime(final URL url) throws IOException, URISyntaxException {
934        return getLastModifiedFileTime(url.toURI());
935    }
936
937    private static FileTime getLastModifiedTime(final Path path, final LinkOption... options) throws IOException {
938        return Files.getLastModifiedTime(Objects.requireNonNull(path, "path"), options);
939    }
940
941    private static Path getParent(final Path path) {
942        return path == null ? null : path.getParent();
943    }
944
945    /**
946     * Shorthand for {@code Files.getFileAttributeView(path, PosixFileAttributeView.class)}.
947     *
948     * @param path    the path to the file.
949     * @param options how to handle symbolic links.
950     * @return a PosixFileAttributeView, or {@code null} if the attribute view type is not available.
951     * @since 2.12.0
952     */
953    public static PosixFileAttributeView getPosixFileAttributeView(final Path path, final LinkOption... options) {
954        return Files.getFileAttributeView(path, PosixFileAttributeView.class, options);
955    }
956
957    /**
958     * Gets a {@link Path} representing the system temporary directory.
959     *
960     * @return the system temporary directory.
961     * @since 2.12.0
962     */
963    public static Path getTempDirectory() {
964        return Paths.get(FileUtils.getTempDirectoryPath());
965    }
966
967    /**
968     * Tests whether the given {@link Path} is a directory or not. Implemented as a null-safe delegate to
969     * {@code Files.isDirectory(Path path, LinkOption... options)}.
970     *
971     * @param path    the path to the file.
972     * @param options options indicating how to handle symbolic links
973     * @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
974     *         determined if the file is a directory or not.
975     * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
976     *                           checkRead} method is invoked to check read access to the directory.
977     * @since 2.9.0
978     */
979    public static boolean isDirectory(final Path path, final LinkOption... options) {
980        return path != null && Files.isDirectory(path, options);
981    }
982
983    /**
984     * Tests whether the given file or directory is empty.
985     *
986     * @param path the file or directory to query.
987     * @return whether the file or directory is empty.
988     * @throws IOException if an I/O error occurs.
989     */
990    public static boolean isEmpty(final Path path) throws IOException {
991        return Files.isDirectory(path) ? isEmptyDirectory(path) : isEmptyFile(path);
992    }
993
994    /**
995     * Tests whether the directory is empty.
996     *
997     * @param directory the directory to query.
998     * @return whether the directory is empty.
999     * @throws NotDirectoryException if the file could not otherwise be opened because it is not a directory <i>(optional specific exception)</i>.
1000     * @throws IOException           if an I/O error occurs.
1001     * @throws SecurityException     In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
1002     *                               checkRead} method is invoked to check read access to the directory.
1003     */
1004    public static boolean isEmptyDirectory(final Path directory) throws IOException {
1005        try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
1006            return !directoryStream.iterator().hasNext();
1007        }
1008    }
1009
1010    /**
1011     * Tests whether the given file is empty.
1012     *
1013     * @param file the file to query.
1014     * @return whether the file is empty.
1015     * @throws IOException       if an I/O error occurs.
1016     * @throws SecurityException In the case of the default provider, and a security manager is installed, its {@link SecurityManager#checkRead(String)
1017     *                           checkRead} method denies read access to the file.
1018     */
1019    public static boolean isEmptyFile(final Path file) throws IOException {
1020        return Files.size(file) <= 0;
1021    }
1022
1023    /**
1024     * Tests if the given {@link Path} is newer than the given time reference.
1025     *
1026     * @param file    the {@link Path} to test.
1027     * @param czdt    the time reference.
1028     * @param options options indicating how to handle symbolic links.
1029     * @return true if the {@link Path} exists and has been modified after the given time reference.
1030     * @throws IOException          if an I/O error occurs.
1031     * @throws NullPointerException if the file is {@code null}.
1032     * @since 2.12.0
1033     */
1034    public static boolean isNewer(final Path file, final ChronoZonedDateTime<?> czdt, final LinkOption... options) throws IOException {
1035        Objects.requireNonNull(czdt, "czdt");
1036        return isNewer(file, czdt.toInstant(), options);
1037    }
1038
1039    /**
1040     * Tests if the given {@link Path} is newer than the given time reference.
1041     *
1042     * @param file     the {@link Path} to test.
1043     * @param fileTime the time reference.
1044     * @param options  options indicating how to handle symbolic links.
1045     * @return true if the {@link Path} exists and has been modified after the given time reference.
1046     * @throws IOException          if an I/O error occurs.
1047     * @throws NullPointerException if the file is {@code null}.
1048     * @since 2.12.0
1049     */
1050    public static boolean isNewer(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
1051        if (notExists(file)) {
1052            return false;
1053        }
1054        return compareLastModifiedTimeTo(file, fileTime, options) > 0;
1055    }
1056
1057    /**
1058     * Tests if the given {@link Path} is newer than the given time reference.
1059     *
1060     * @param file    the {@link Path} to test.
1061     * @param instant the time reference.
1062     * @param options options indicating how to handle symbolic links.
1063     * @return true if the {@link Path} exists and has been modified after the given time reference.
1064     * @throws IOException          if an I/O error occurs.
1065     * @throws NullPointerException if the file is {@code null}.
1066     * @since 2.12.0
1067     */
1068    public static boolean isNewer(final Path file, final Instant instant, final LinkOption... options) throws IOException {
1069        return isNewer(file, FileTime.from(instant), options);
1070    }
1071
1072    /**
1073     * Tests if the given {@link Path} is newer than the given time reference.
1074     *
1075     * @param file       the {@link Path} to test.
1076     * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
1077     * @param options    options indicating how to handle symbolic links.
1078     * @return true if the {@link Path} exists and has been modified after the given time reference.
1079     * @throws IOException          if an I/O error occurs.
1080     * @throws NullPointerException if the file is {@code null}.
1081     * @since 2.9.0
1082     */
1083    public static boolean isNewer(final Path file, final long timeMillis, final LinkOption... options) throws IOException {
1084        return isNewer(file, FileTime.fromMillis(timeMillis), options);
1085    }
1086
1087    /**
1088     * Tests if the given {@link Path} is newer than the reference {@link Path}.
1089     *
1090     * @param file      the {@link File} to test.
1091     * @param reference the {@link File} of which the modification date is used.
1092     * @return true if the {@link File} exists and has been modified more recently than the reference {@link File}.
1093     * @throws IOException if an I/O error occurs.
1094     * @since 2.12.0
1095     */
1096    public static boolean isNewer(final Path file, final Path reference) throws IOException {
1097        return isNewer(file, getLastModifiedTime(reference));
1098    }
1099
1100    /**
1101     * Tests if the given {@link Path} is older than the given time reference.
1102     *
1103     * @param file     the {@link Path} to test.
1104     * @param fileTime the time reference.
1105     * @param options  options indicating how to handle symbolic links.
1106     * @return true if the {@link Path} exists and has been modified before the given time reference.
1107     * @throws IOException          if an I/O error occurs.
1108     * @throws NullPointerException if the file is {@code null}.
1109     * @since 2.12.0
1110     */
1111    public static boolean isOlder(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
1112        if (notExists(file)) {
1113            return false;
1114        }
1115        return compareLastModifiedTimeTo(file, fileTime, options) < 0;
1116    }
1117
1118    /**
1119     * Tests if the given {@link Path} is older than the given time reference.
1120     *
1121     * @param file    the {@link Path} to test.
1122     * @param instant the time reference.
1123     * @param options options indicating how to handle symbolic links.
1124     * @return true if the {@link Path} exists and has been modified before the given time reference.
1125     * @throws IOException          if an I/O error occurs.
1126     * @throws NullPointerException if the file is {@code null}.
1127     * @since 2.12.0
1128     */
1129    public static boolean isOlder(final Path file, final Instant instant, final LinkOption... options) throws IOException {
1130        return isOlder(file, FileTime.from(instant), options);
1131    }
1132
1133    /**
1134     * Tests if the given {@link Path} is older than the given time reference.
1135     *
1136     * @param file       the {@link Path} to test.
1137     * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
1138     * @param options    options indicating how to handle symbolic links.
1139     * @return true if the {@link Path} exists and has been modified before the given time reference.
1140     * @throws IOException          if an I/O error occurs.
1141     * @throws NullPointerException if the file is {@code null}.
1142     * @since 2.12.0
1143     */
1144    public static boolean isOlder(final Path file, final long timeMillis, final LinkOption... options) throws IOException {
1145        return isOlder(file, FileTime.fromMillis(timeMillis), options);
1146    }
1147
1148    /**
1149     * Tests if the given {@link Path} is older than the reference {@link Path}.
1150     *
1151     * @param file      the {@link File} to test.
1152     * @param reference the {@link File} of which the modification date is used.
1153     * @return true if the {@link File} exists and has been modified before than the reference {@link File}.
1154     * @throws IOException if an I/O error occurs.
1155     * @since 2.12.0
1156     */
1157    public static boolean isOlder(final Path file, final Path reference) throws IOException {
1158        return isOlder(file, getLastModifiedTime(reference));
1159    }
1160
1161    /**
1162     * Tests whether the given path is on a POSIX file system.
1163     *
1164     * @param test    The Path to test.
1165     * @param options options indicating how to handle symbolic links.
1166     * @return true if test is on a POSIX file system.
1167     * @since 2.12.0
1168     */
1169    public static boolean isPosix(final Path test, final LinkOption... options) {
1170        return exists(test, options) && readPosixFileAttributes(test, options) != null;
1171    }
1172
1173    /**
1174     * Tests whether the given {@link Path} is a regular file or not. Implemented as a null-safe delegate to
1175     * {@code Files.isRegularFile(Path path, LinkOption... options)}.
1176     *
1177     * @param path    the path to the file.
1178     * @param options options indicating how to handle symbolic links.
1179     * @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
1180     *         determined if the file is a regular file or not.
1181     * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
1182     *                           checkRead} method is invoked to check read access to the directory.
1183     * @since 2.9.0
1184     */
1185    public static boolean isRegularFile(final Path path, final LinkOption... options) {
1186        return path != null && Files.isRegularFile(path, options);
1187    }
1188
1189    /**
1190     * Creates a new DirectoryStream for Paths rooted at the given directory.
1191     * <p>
1192     * 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
1193     * resources held for the open directory.
1194     * </p>
1195     *
1196     * @param dir        the path to the directory to stream.
1197     * @param pathFilter the directory stream filter.
1198     * @return a new instance.
1199     * @throws IOException if an I/O error occurs.
1200     */
1201    public static DirectoryStream<Path> newDirectoryStream(final Path dir, final PathFilter pathFilter) throws IOException {
1202        return Files.newDirectoryStream(dir, new DirectoryStreamFilter(pathFilter));
1203    }
1204
1205    /**
1206     * Creates a new OutputStream by opening or creating a file, returning an output stream that may be used to write bytes to the file.
1207     *
1208     * @param path   the Path.
1209     * @param append Whether or not to append.
1210     * @return a new OutputStream.
1211     * @throws IOException if an I/O error occurs.
1212     * @see Files#newOutputStream(Path, OpenOption...)
1213     * @since 2.12.0
1214     */
1215    public static OutputStream newOutputStream(final Path path, final boolean append) throws IOException {
1216        return newOutputStream(path, EMPTY_LINK_OPTION_ARRAY, append ? OPEN_OPTIONS_APPEND : OPEN_OPTIONS_TRUNCATE);
1217    }
1218
1219    static OutputStream newOutputStream(final Path path, final LinkOption[] linkOptions, final OpenOption... openOptions) throws IOException {
1220        if (!exists(path, linkOptions)) {
1221            createParentDirectories(path, linkOptions != null && linkOptions.length > 0 ? linkOptions[0] : NULL_LINK_OPTION);
1222        }
1223        final List<OpenOption> list = new ArrayList<>(Arrays.asList(openOptions != null ? openOptions : EMPTY_OPEN_OPTION_ARRAY));
1224        list.addAll(Arrays.asList(linkOptions != null ? linkOptions : EMPTY_LINK_OPTION_ARRAY));
1225        return Files.newOutputStream(path, list.toArray(EMPTY_OPEN_OPTION_ARRAY));
1226    }
1227
1228    /**
1229     * Copy of the {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
1230     *
1231     * @return Copy of the {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
1232     */
1233    public static LinkOption[] noFollowLinkOptionArray() {
1234        return NOFOLLOW_LINK_OPTION_ARRAY.clone();
1235    }
1236
1237    private static boolean notExists(final Path path, final LinkOption... options) {
1238        return Files.notExists(Objects.requireNonNull(path, "path"), options);
1239    }
1240
1241    /**
1242     * Returns true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
1243     *
1244     * @param deleteOptions the array to test
1245     * @return true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
1246     */
1247    private static boolean overrideReadOnly(final DeleteOption... deleteOptions) {
1248        if (deleteOptions == null) {
1249            return false;
1250        }
1251        return Stream.of(deleteOptions).anyMatch(e -> e == StandardDeleteOption.OVERRIDE_READ_ONLY);
1252    }
1253
1254    /**
1255     * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read.
1256     *
1257     * @param <A>     The {@link BasicFileAttributes} type
1258     * @param path    The Path to test.
1259     * @param type    the {@link Class} of the file attributes required to read.
1260     * @param options options indicating how to handle symbolic links.
1261     * @return the file attributes or null if the attributes can't be read.
1262     * @see Files#readAttributes(Path, Class, LinkOption...)
1263     * @since 2.12.0
1264     */
1265    public static <A extends BasicFileAttributes> A readAttributes(final Path path, final Class<A> type, final LinkOption... options) {
1266        try {
1267            return path == null ? null : Files.readAttributes(path, type, options);
1268        } catch (final UnsupportedOperationException | IOException e) {
1269            // For example, on Windows.
1270            return null;
1271        }
1272    }
1273
1274    /**
1275     * Reads the BasicFileAttributes from the given path.
1276     *
1277     * @param path the path to read.
1278     * @return the path attributes.
1279     * @throws IOException if an I/O error occurs.
1280     * @since 2.9.0
1281     */
1282    public static BasicFileAttributes readBasicFileAttributes(final Path path) throws IOException {
1283        return Files.readAttributes(path, BasicFileAttributes.class);
1284    }
1285
1286    /**
1287     * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read.
1288     *
1289     * @param path    the path to read.
1290     * @param options options indicating how to handle symbolic links.
1291     * @return the path attributes.
1292     * @since 2.12.0
1293     */
1294    public static BasicFileAttributes readBasicFileAttributes(final Path path, final LinkOption... options) {
1295        return readAttributes(path, BasicFileAttributes.class, options);
1296    }
1297
1298    /**
1299     * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read.
1300     *
1301     * @param path the path to read.
1302     * @return the path attributes.
1303     * @since 2.9.0
1304     * @deprecated Use {@link #readBasicFileAttributes(Path, LinkOption...)}.
1305     */
1306    @Deprecated
1307    public static BasicFileAttributes readBasicFileAttributesUnchecked(final Path path) {
1308        return readBasicFileAttributes(path, EMPTY_LINK_OPTION_ARRAY);
1309    }
1310
1311    /**
1312     * Reads the DosFileAttributes from the given path. Returns null if the attributes can't be read.
1313     *
1314     * @param path    the path to read.
1315     * @param options options indicating how to handle symbolic links.
1316     * @return the path attributes.
1317     * @since 2.12.0
1318     */
1319    public static DosFileAttributes readDosFileAttributes(final Path path, final LinkOption... options) {
1320        return readAttributes(path, DosFileAttributes.class, options);
1321    }
1322
1323    private static Path readIfSymbolicLink(final Path path) throws IOException {
1324        return path != null ? Files.isSymbolicLink(path) ? Files.readSymbolicLink(path) : path : null;
1325    }
1326
1327    /**
1328     * Reads the PosixFileAttributes or DosFileAttributes from the given path. Returns null if the attributes can't be read.
1329     *
1330     * @param path    The Path to read.
1331     * @param options options indicating how to handle symbolic links.
1332     * @return the file attributes.
1333     * @since 2.12.0
1334     */
1335    public static BasicFileAttributes readOsFileAttributes(final Path path, final LinkOption... options) {
1336        final PosixFileAttributes fileAttributes = readPosixFileAttributes(path, options);
1337        return fileAttributes != null ? fileAttributes : readDosFileAttributes(path, options);
1338    }
1339
1340    /**
1341     * Reads the PosixFileAttributes from the given path. Returns null instead of throwing {@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 is returned. If the argument is a
1543     * 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 {@link #sizeOfAsBigInteger(Path)} for an alternative
1546     * 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 is returned. If the argument is a
1563     * 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 {@link #sizeOfDirectoryAsBigInteger(Path)} for an
1581     * 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 greater than {@link Long#MAX_VALUE}.
1586     * @throws NullPointerException if the directory is {@code null}.
1587     * @throws IOException          if an I/O error occurs.
1588     * @since 2.12.0
1589     */
1590    public static long sizeOfDirectory(final Path directory) throws IOException {
1591        return countDirectory(directory).getByteCounter().getLong();
1592    }
1593
1594    /**
1595     * Counts the size of a directory recursively (sum of the size of all files).
1596     *
1597     * @param directory directory to inspect, must not be {@code null}.
1598     * @return size of directory in bytes, 0 if directory is security restricted.
1599     * @throws NullPointerException if the directory is {@code null}.
1600     * @throws IOException          if an I/O error occurs.
1601     * @since 2.12.0
1602     */
1603    public static BigInteger sizeOfDirectoryAsBigInteger(final Path directory) throws IOException {
1604        return countDirectoryAsBigInteger(directory).getByteCounter().getBigInteger();
1605    }
1606
1607    /**
1608     * Converts an array of {@link FileVisitOption} to a {@link Set}.
1609     *
1610     * @param fileVisitOptions input array.
1611     * @return a new Set.
1612     */
1613    static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... fileVisitOptions) {
1614        return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class) : Stream.of(fileVisitOptions).collect(Collectors.toSet());
1615    }
1616
1617    /**
1618     * 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.
1619     * this method creates parent directories if they do not exist.
1620     *
1621     * @param file the file to touch.
1622     * @return The given file.
1623     * @throws NullPointerException if the parameter is {@code null}.
1624     * @throws IOException          if setting the last-modified time failed or an I/O problem occurs.\
1625     * @since 2.12.0
1626     */
1627    public static Path touch(final Path file) throws IOException {
1628        Objects.requireNonNull(file, "file");
1629        if (!Files.exists(file)) {
1630            createParentDirectories(file);
1631            Files.createFile(file);
1632        } else {
1633            FileTimes.setLastModifiedTime(file);
1634        }
1635        return file;
1636    }
1637
1638    /**
1639     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
1640     *
1641     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
1642     *
1643     * @param visitor   See {@link Files#walkFileTree(Path,FileVisitor)}.
1644     * @param directory See {@link Files#walkFileTree(Path,FileVisitor)}.
1645     * @param <T>       See {@link Files#walkFileTree(Path,FileVisitor)}.
1646     * @return the given visitor.
1647     *
1648     * @throws NoSuchFileException  if the directory does not exist.
1649     * @throws IOException          if an I/O error is thrown by a visitor method.
1650     * @throws NullPointerException if the directory is {@code null}.
1651     */
1652    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path directory) throws IOException {
1653        Files.walkFileTree(directory, visitor);
1654        return visitor;
1655    }
1656
1657    /**
1658     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
1659     *
1660     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
1661     *
1662     * @param start    See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
1663     * @param options  See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
1664     * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
1665     * @param visitor  See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
1666     * @param <T>      See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
1667     * @return the given visitor.
1668     *
1669     * @throws IOException if an I/O error is thrown by a visitor method.
1670     */
1671    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path start, final Set<FileVisitOption> options,
1672            final int maxDepth) throws IOException {
1673        Files.walkFileTree(start, options, maxDepth, visitor);
1674        return visitor;
1675    }
1676
1677    /**
1678     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
1679     *
1680     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
1681     *
1682     * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
1683     * @param first   See {@link Paths#get(String,String[])}.
1684     * @param more    See {@link Paths#get(String,String[])}.
1685     * @param <T>     See {@link Files#walkFileTree(Path,FileVisitor)}.
1686     * @return the given visitor.
1687     *
1688     * @throws IOException if an I/O error is thrown by a visitor method.
1689     */
1690    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final String first, final String... more) throws IOException {
1691        return visitFileTree(visitor, Paths.get(first, more));
1692    }
1693
1694    /**
1695     * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
1696     *
1697     * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
1698     *
1699     * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
1700     * @param uri     See {@link Paths#get(URI)}.
1701     * @param <T>     See {@link Files#walkFileTree(Path,FileVisitor)}.
1702     * @return the given visitor.
1703     *
1704     * @throws IOException if an I/O error is thrown by a visitor method.
1705     */
1706    public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final URI uri) throws IOException {
1707        return visitFileTree(visitor, Paths.get(uri));
1708    }
1709
1710    /**
1711     * Waits for the file system to propagate a file creation, with a timeout.
1712     * <p>
1713     * This method repeatedly tests {@link Files#exists(Path,LinkOption...)} until it returns true up to the maximum time given.
1714     * </p>
1715     *
1716     * @param file    the file to check, must not be {@code null}.
1717     * @param timeout the maximum time to wait.
1718     * @param options options indicating how to handle symbolic links.
1719     * @return true if file exists.
1720     * @throws NullPointerException if the file is {@code null}.
1721     * @since 2.12.0
1722     */
1723    public static boolean waitFor(final Path file, final Duration timeout, final LinkOption... options) {
1724        Objects.requireNonNull(file, "file");
1725        final Instant finishInstant = Instant.now().plus(timeout);
1726        boolean interrupted = false;
1727        final long minSleepMillis = 100;
1728        try {
1729            while (!exists(file, options)) {
1730                final Instant now = Instant.now();
1731                if (now.isAfter(finishInstant)) {
1732                    return false;
1733                }
1734                try {
1735                    ThreadUtils.sleep(Duration.ofMillis(Math.min(minSleepMillis, finishInstant.minusMillis(now.toEpochMilli()).toEpochMilli())));
1736                } catch (final InterruptedException ignore) {
1737                    interrupted = true;
1738                } catch (final Exception ex) {
1739                    break;
1740                }
1741            }
1742        } finally {
1743            if (interrupted) {
1744                Thread.currentThread().interrupt();
1745            }
1746        }
1747        return exists(file, options);
1748    }
1749
1750    /**
1751     * Returns a stream of filtered paths.
1752     * <p>
1753     * The returned {@link Stream} may wrap one or more {@link DirectoryStream}s. When you require timely disposal of file system resources, use a
1754     * {@code try}-with-resources block to ensure invocation of the stream's {@link Stream#close()} method after the stream operations are completed. Calling a
1755     * closed stream causes a {@link IllegalStateException}.
1756     * </p>
1757     *
1758     * @param start          the start path
1759     * @param pathFilter     the path filter
1760     * @param maxDepth       the maximum depth of directories to walk.
1761     * @param readAttributes whether to call the filters with file attributes (false passes null).
1762     * @param options        the options to configure the walk.
1763     * @return a filtered stream of paths.
1764     * @throws IOException if an I/O error is thrown when accessing the starting file.
1765     * @since 2.9.0
1766     */
1767    public static Stream<Path> walk(final Path start, final PathFilter pathFilter, final int maxDepth, final boolean readAttributes,
1768            final FileVisitOption... options) throws IOException {
1769        return Files.walk(start, maxDepth, options)
1770                .filter(path -> pathFilter.accept(path, readAttributes ? readBasicFileAttributesUnchecked(path) : null) == FileVisitResult.CONTINUE);
1771    }
1772
1773    private static <R> R withPosixFileAttributes(final Path path, final LinkOption[] linkOptions, final boolean overrideReadOnly,
1774            final IOFunction<PosixFileAttributes, R> function) throws IOException {
1775        final PosixFileAttributes posixFileAttributes = overrideReadOnly ? readPosixFileAttributes(path, linkOptions) : null;
1776        try {
1777            return function.apply(posixFileAttributes);
1778        } finally {
1779            if (posixFileAttributes != null && path != null && Files.exists(path, linkOptions)) {
1780                Files.setPosixFilePermissions(path, posixFileAttributes.permissions());
1781            }
1782        }
1783    }
1784
1785    /**
1786     * Writes the given character sequence to a file at the given path.
1787     *
1788     * @param path         The target file.
1789     * @param charSequence The character sequence text.
1790     * @param charset      The Charset to encode the text.
1791     * @param openOptions  options How to open the file.
1792     * @return The given path.
1793     * @throws IOException          if an I/O error occurs writing to or creating the file.
1794     * @throws NullPointerException if either {@code path} or {@code charSequence} is {@code null}.
1795     * @since 2.12.0
1796     */
1797    public static Path writeString(final Path path, final CharSequence charSequence, final Charset charset, final OpenOption... openOptions)
1798            throws IOException {
1799        // Check the text is not null before opening file.
1800        Objects.requireNonNull(path, "path");
1801        Objects.requireNonNull(charSequence, "charSequence");
1802        Files.write(path, String.valueOf(charSequence).getBytes(Charsets.toCharset(charset)), openOptions);
1803        return path;
1804    }
1805
1806    /**
1807     * Prevents instantiation.
1808     */
1809    private PathUtils() {
1810        // do not instantiate.
1811    }
1812
1813}