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