View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.io.file;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.io.RandomAccessFile;
25  import java.math.BigInteger;
26  import java.net.URI;
27  import java.net.URISyntaxException;
28  import java.net.URL;
29  import java.nio.charset.Charset;
30  import java.nio.file.AccessDeniedException;
31  import java.nio.file.CopyOption;
32  import java.nio.file.DirectoryStream;
33  import java.nio.file.FileSystem;
34  import java.nio.file.FileVisitOption;
35  import java.nio.file.FileVisitResult;
36  import java.nio.file.FileVisitor;
37  import java.nio.file.Files;
38  import java.nio.file.LinkOption;
39  import java.nio.file.NoSuchFileException;
40  import java.nio.file.NotDirectoryException;
41  import java.nio.file.OpenOption;
42  import java.nio.file.Path;
43  import java.nio.file.Paths;
44  import java.nio.file.StandardOpenOption;
45  import java.nio.file.attribute.AclEntry;
46  import java.nio.file.attribute.AclFileAttributeView;
47  import java.nio.file.attribute.BasicFileAttributes;
48  import java.nio.file.attribute.DosFileAttributeView;
49  import java.nio.file.attribute.DosFileAttributes;
50  import java.nio.file.attribute.FileAttribute;
51  import java.nio.file.attribute.FileTime;
52  import java.nio.file.attribute.PosixFileAttributeView;
53  import java.nio.file.attribute.PosixFileAttributes;
54  import java.nio.file.attribute.PosixFilePermission;
55  import java.time.Duration;
56  import java.time.Instant;
57  import java.time.chrono.ChronoZonedDateTime;
58  import java.util.ArrayList;
59  import java.util.Arrays;
60  import java.util.Collection;
61  import java.util.Collections;
62  import java.util.Comparator;
63  import java.util.EnumSet;
64  import java.util.HashSet;
65  import java.util.Iterator;
66  import java.util.List;
67  import java.util.Objects;
68  import java.util.Set;
69  import java.util.function.Function;
70  import java.util.stream.Collector;
71  import java.util.stream.Collectors;
72  import java.util.stream.Stream;
73  import java.util.stream.StreamSupport;
74  
75  import org.apache.commons.io.Charsets;
76  import org.apache.commons.io.FileUtils;
77  import org.apache.commons.io.FilenameUtils;
78  import org.apache.commons.io.IOUtils;
79  import org.apache.commons.io.RandomAccessFileMode;
80  import org.apache.commons.io.RandomAccessFiles;
81  import org.apache.commons.io.ThreadUtils;
82  import org.apache.commons.io.file.Counters.PathCounters;
83  import org.apache.commons.io.file.attribute.FileTimes;
84  import org.apache.commons.io.filefilter.IOFileFilter;
85  import org.apache.commons.io.function.IOFunction;
86  import org.apache.commons.io.function.IOSupplier;
87  
88  /**
89   * NIO Path utilities.
90   *
91   * @since 2.7
92   */
93  public final class PathUtils {
94  
95      /**
96       * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted relative lists when comparing directories.
97       */
98      private static final class RelativeSortedPaths {
99  
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 }