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