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