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