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    *      http://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.UncheckedIOException;
24  import java.net.URI;
25  import java.net.URL;
26  import java.nio.file.CopyOption;
27  import java.nio.file.DirectoryStream;
28  import java.nio.file.FileVisitOption;
29  import java.nio.file.FileVisitResult;
30  import java.nio.file.FileVisitor;
31  import java.nio.file.Files;
32  import java.nio.file.LinkOption;
33  import java.nio.file.NoSuchFileException;
34  import java.nio.file.NotDirectoryException;
35  import java.nio.file.OpenOption;
36  import java.nio.file.Path;
37  import java.nio.file.Paths;
38  import java.nio.file.attribute.AclEntry;
39  import java.nio.file.attribute.AclFileAttributeView;
40  import java.nio.file.attribute.BasicFileAttributes;
41  import java.nio.file.attribute.DosFileAttributeView;
42  import java.nio.file.attribute.FileAttribute;
43  import java.nio.file.attribute.PosixFileAttributeView;
44  import java.nio.file.attribute.PosixFileAttributes;
45  import java.nio.file.attribute.PosixFilePermission;
46  import java.util.ArrayList;
47  import java.util.Arrays;
48  import java.util.Collection;
49  import java.util.Collections;
50  import java.util.Comparator;
51  import java.util.EnumSet;
52  import java.util.List;
53  import java.util.Objects;
54  import java.util.Set;
55  import java.util.stream.Collector;
56  import java.util.stream.Collectors;
57  import java.util.stream.Stream;
58  
59  import org.apache.commons.io.IOExceptionList;
60  import org.apache.commons.io.IOUtils;
61  import org.apache.commons.io.file.Counters.PathCounters;
62  import org.apache.commons.io.filefilter.IOFileFilter;
63  
64  /**
65   * NIO Path utilities.
66   *
67   * @since 2.7
68   */
69  public final class PathUtils {
70  
71      /**
72       * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted
73       * relative lists when comparing directories.
74       */
75      private static class RelativeSortedPaths {
76  
77          final boolean equals;
78          // final List<Path> relativeDirList1; // might need later?
79          // final List<Path> relativeDirList2; // might need later?
80          final List<Path> relativeFileList1;
81          final List<Path> relativeFileList2;
82  
83          /**
84           * Constructs and initializes a new instance by accumulating directory and file info.
85           *
86           * @param dir1 First directory to compare.
87           * @param dir2 Seconds directory to compare.
88           * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
89           * @param linkOptions Options indicating how symbolic links are handled.
90           * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
91           * @throws IOException if an I/O error is thrown by a visitor method.
92           */
93          private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth,
94              final LinkOption[] linkOptions, final FileVisitOption[] fileVisitOptions) throws IOException {
95              final List<Path> tmpRelativeDirList1;
96              final List<Path> tmpRelativeDirList2;
97              List<Path> tmpRelativeFileList1 = null;
98              List<Path> tmpRelativeFileList2 = null;
99              if (dir1 == null && dir2 == null) {
100                 equals = true;
101             } else if (dir1 == null ^ dir2 == null) {
102                 equals = false;
103             } else {
104                 final boolean parentDirNotExists1 = Files.notExists(dir1, linkOptions);
105                 final boolean parentDirNotExists2 = Files.notExists(dir2, linkOptions);
106                 if (parentDirNotExists1 || parentDirNotExists2) {
107                     equals = parentDirNotExists1 && parentDirNotExists2;
108                 } else {
109                     final AccumulatorPathVisitor visitor1 = accumulate(dir1, maxDepth, fileVisitOptions);
110                     final AccumulatorPathVisitor visitor2 = accumulate(dir2, maxDepth, fileVisitOptions);
111                     if (visitor1.getDirList().size() != visitor2.getDirList().size()
112                         || visitor1.getFileList().size() != visitor2.getFileList().size()) {
113                         equals = false;
114                     } else {
115                         tmpRelativeDirList1 = visitor1.relativizeDirectories(dir1, true, null);
116                         tmpRelativeDirList2 = visitor2.relativizeDirectories(dir2, true, null);
117                         if (!tmpRelativeDirList1.equals(tmpRelativeDirList2)) {
118                             equals = false;
119                         } else {
120                             tmpRelativeFileList1 = visitor1.relativizeFiles(dir1, true, null);
121                             tmpRelativeFileList2 = visitor2.relativizeFiles(dir2, true, null);
122                             equals = tmpRelativeFileList1.equals(tmpRelativeFileList2);
123                         }
124                     }
125                 }
126             }
127             // relativeDirList1 = tmpRelativeDirList1;
128             // relativeDirList2 = tmpRelativeDirList2;
129             relativeFileList1 = tmpRelativeFileList1;
130             relativeFileList2 = tmpRelativeFileList2;
131         }
132     }
133 
134     /**
135      * Empty {@link CopyOption} array.
136      *
137      * @since 2.8.0
138      */
139     public static final CopyOption[] EMPTY_COPY_OPTIONS = {};
140 
141     /**
142      * Empty {@link LinkOption} array.
143      *
144      * @since 2.8.0
145      */
146     public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = {};
147 
148     /**
149      * Empty {@link FileVisitOption} array.
150      */
151     public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = {};
152 
153     /**
154      * Empty {@link LinkOption} array.
155      */
156     public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = {};
157 
158     /**
159      * {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
160      *
161      * @since 2.9.0
162      */
163     public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = {LinkOption.NOFOLLOW_LINKS};
164 
165     /**
166      * Empty {@link OpenOption} array.
167      */
168     public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = {};
169 
170     /**
171      * Empty {@link Path} array.
172      *
173      * @since 2.9.0
174      */
175     public static final Path[] EMPTY_PATH_ARRAY = {};
176 
177     /**
178      * Accumulates file tree information in a {@link AccumulatorPathVisitor}.
179      *
180      * @param directory The directory to accumulate information.
181      * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
182      * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
183      * @throws IOException if an I/O error is thrown by a visitor method.
184      * @return file tree information.
185      */
186     private static AccumulatorPathVisitor accumulate(final Path directory, final int maxDepth,
187         final FileVisitOption[] fileVisitOptions) throws IOException {
188         return visitFileTree(AccumulatorPathVisitor.withLongCounters(), directory,
189             toFileVisitOptionSet(fileVisitOptions), maxDepth);
190     }
191 
192     /**
193      * Cleans a directory including sub-directories without deleting directories.
194      *
195      * @param directory directory to clean.
196      * @return The visitation path counters.
197      * @throws IOException if an I/O error is thrown by a visitor method.
198      */
199     public static PathCounters cleanDirectory(final Path directory) throws IOException {
200         return cleanDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
201     }
202 
203     /**
204      * Cleans a directory including sub-directories without deleting directories.
205      *
206      * @param directory directory to clean.
207      * @param deleteOptions How to handle deletion.
208      * @return The visitation path counters.
209      * @throws IOException if an I/O error is thrown by a visitor method.
210      * @since 2.8.0
211      */
212     public static PathCounters cleanDirectory(final Path directory, final DeleteOption... deleteOptions)
213         throws IOException {
214         return visitFileTree(new CleaningPathVisitor(Counters.longPathCounters(), deleteOptions), directory)
215             .getPathCounters();
216     }
217 
218     /**
219      * Copies a directory to another directory.
220      *
221      * @param sourceDirectory The source directory.
222      * @param targetDirectory The target directory.
223      * @param copyOptions Specifies how the copying should be done.
224      * @return The visitation path counters.
225      * @throws IOException if an I/O error is thrown by a visitor method.
226      */
227     public static PathCounters copyDirectory(final Path sourceDirectory, final Path targetDirectory,
228         final CopyOption... copyOptions) throws IOException {
229         final Path absoluteSource = sourceDirectory.toAbsolutePath();
230         return visitFileTree(
231             new CopyDirectoryVisitor(Counters.longPathCounters(), absoluteSource, targetDirectory, copyOptions),
232             absoluteSource).getPathCounters();
233     }
234 
235     /**
236      * Copies a URL to a directory.
237      *
238      * @param sourceFile The source URL.
239      * @param targetFile The target file.
240      * @param copyOptions Specifies how the copying should be done.
241      * @return The target file
242      * @throws IOException if an I/O error occurs.
243      * @see Files#copy(InputStream, Path, CopyOption...)
244      */
245     public static Path copyFile(final URL sourceFile, final Path targetFile, final CopyOption... copyOptions)
246         throws IOException {
247         try (final InputStream inputStream = sourceFile.openStream()) {
248             Files.copy(inputStream, targetFile, copyOptions);
249             return targetFile;
250         }
251     }
252 
253     /**
254      * Copies a file to a directory.
255      *
256      * @param sourceFile The source file.
257      * @param targetDirectory The target directory.
258      * @param copyOptions Specifies how the copying should be done.
259      * @return The target file
260      * @throws IOException if an I/O error occurs.
261      * @see Files#copy(Path, Path, CopyOption...)
262      */
263     public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory,
264         final CopyOption... copyOptions) throws IOException {
265         return Files.copy(sourceFile, targetDirectory.resolve(sourceFile.getFileName()), copyOptions);
266     }
267 
268     /**
269      * Copies a URL to a directory.
270      *
271      * @param sourceFile The source URL.
272      * @param targetDirectory The target directory.
273      * @param copyOptions Specifies how the copying should be done.
274      * @return The target file
275      * @throws IOException if an I/O error occurs.
276      * @see Files#copy(InputStream, Path, CopyOption...)
277      */
278     public static Path copyFileToDirectory(final URL sourceFile, final Path targetDirectory,
279         final CopyOption... copyOptions) throws IOException {
280         try (final InputStream inputStream = sourceFile.openStream()) {
281             Files.copy(inputStream, targetDirectory.resolve(sourceFile.getFile()), copyOptions);
282             return targetDirectory;
283         }
284     }
285 
286     /**
287      * Counts aspects of a directory including sub-directories.
288      *
289      * @param directory directory to delete.
290      * @return The visitor used to count the given directory.
291      * @throws IOException if an I/O error is thrown by a visitor method.
292      */
293     public static PathCounters countDirectory(final Path directory) throws IOException {
294         return visitFileTree(new CountingPathVisitor(Counters.longPathCounters()), directory).getPathCounters();
295     }
296 
297     /**
298      * Creates the parent directories for the given {@code path}.
299      *
300      * @param path The path to a file (or directory).
301      * @param attrs An optional list of file attributes to set atomically when creating the directories.
302      * @return The Path for the {@code path}'s parent directory or null if the given path has no parent.
303      * @throws IOException if an I/O error occurs.
304      * @since 2.9.0
305      */
306     public static Path createParentDirectories(final Path path, final FileAttribute<?>... attrs) throws IOException {
307         final Path parent = path.getParent();
308         if (parent == null) {
309             return null;
310         }
311         return Files.createDirectories(parent, attrs);
312     }
313 
314     /**
315      * Gets the current directory.
316      *
317      * @return the current directory.
318      *
319      * @since 2.9.0
320      */
321     public static Path current() {
322         return Paths.get("");
323     }
324 
325     /**
326      * Deletes a file or directory. If the path is a directory, delete it and all sub-directories.
327      * <p>
328      * The difference between File.delete() and this method are:
329      * </p>
330      * <ul>
331      * <li>A directory to delete does not have to be empty.</li>
332      * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a
333      * boolean.
334      * </ul>
335      *
336      * @param path file or directory to delete, must not be {@code null}
337      * @return The visitor used to delete the given directory.
338      * @throws NullPointerException if the directory is {@code null}
339      * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs.
340      */
341     public static PathCounters delete(final Path path) throws IOException {
342         return delete(path, EMPTY_DELETE_OPTION_ARRAY);
343     }
344 
345     /**
346      * Deletes a file or directory. If the path is a directory, delete it and all sub-directories.
347      * <p>
348      * The difference between File.delete() and this method are:
349      * </p>
350      * <ul>
351      * <li>A directory to delete does not have to be empty.</li>
352      * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a
353      * boolean.
354      * </ul>
355      *
356      * @param path file or directory to delete, must not be {@code null}
357      * @param deleteOptions How to handle deletion.
358      * @return The visitor used to delete the given directory.
359      * @throws NullPointerException if the directory is {@code null}
360      * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs.
361      * @since 2.8.0
362      */
363     public static PathCounters delete(final Path path, final DeleteOption... deleteOptions) throws IOException {
364         // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
365         return Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) ? deleteDirectory(path, deleteOptions)
366             : deleteFile(path, deleteOptions);
367     }
368 
369     /**
370      * Deletes a file or directory. If the path is a directory, delete it and all sub-directories.
371      * <p>
372      * The difference between File.delete() and this method are:
373      * </p>
374      * <ul>
375      * <li>A directory to delete does not have to be empty.</li>
376      * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a
377      * boolean.
378      * </ul>
379      *
380      * @param path file or directory to delete, must not be {@code null}
381      * @param linkOptions How to handle symbolic links.
382      * @param deleteOptions How to handle deletion.
383      * @return The visitor used to delete the given directory.
384      * @throws NullPointerException if the directory is {@code null}
385      * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs.
386      * @since 2.9.0
387      */
388     public static PathCounters delete(final Path path, final LinkOption[] linkOptions,
389         final DeleteOption... deleteOptions) throws IOException {
390         // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
391         return Files.isDirectory(path, linkOptions) ? deleteDirectory(path, linkOptions, deleteOptions)
392             : deleteFile(path, linkOptions, deleteOptions);
393     }
394 
395     /**
396      * Deletes a directory including sub-directories.
397      *
398      * @param directory directory to delete.
399      * @return The visitor used to delete the given directory.
400      * @throws IOException if an I/O error is thrown by a visitor method.
401      */
402     public static PathCounters deleteDirectory(final Path directory) throws IOException {
403         return deleteDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
404     }
405 
406     /**
407      * Deletes a directory including sub-directories.
408      *
409      * @param directory directory to delete.
410      * @param deleteOptions How to handle deletion.
411      * @return The visitor used to delete the given directory.
412      * @throws IOException if an I/O error is thrown by a visitor method.
413      * @since 2.8.0
414      */
415     public static PathCounters deleteDirectory(final Path directory, final DeleteOption... deleteOptions)
416         throws IOException {
417         return visitFileTree(
418             new DeletingPathVisitor(Counters.longPathCounters(), PathUtils.NOFOLLOW_LINK_OPTION_ARRAY, deleteOptions),
419             directory).getPathCounters();
420     }
421 
422     /**
423      * Deletes a directory including sub-directories.
424      *
425      * @param directory directory to delete.
426      * @param linkOptions How to handle symbolic links.
427      * @param deleteOptions How to handle deletion.
428      * @return The visitor used to delete the given directory.
429      * @throws IOException if an I/O error is thrown by a visitor method.
430      * @since 2.9.0
431      */
432     public static PathCounters deleteDirectory(final Path directory, final LinkOption[] linkOptions,
433         final DeleteOption... deleteOptions) throws IOException {
434         return visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions),
435             directory).getPathCounters();
436     }
437 
438     /**
439      * Deletes the given file.
440      *
441      * @param file The file to delete.
442      * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
443      * @throws IOException if an I/O error occurs.
444      * @throws NoSuchFileException if the file is a directory.
445      */
446     public static PathCounters deleteFile(final Path file) throws IOException {
447         return deleteFile(file, EMPTY_DELETE_OPTION_ARRAY);
448     }
449 
450     /**
451      * Deletes the given file.
452      *
453      * @param file The file to delete.
454      * @param deleteOptions How to handle deletion.
455      * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
456      * @throws IOException if an I/O error occurs.
457      * @throws NoSuchFileException if the file is a directory.
458      * @since 2.8.0
459      */
460     public static PathCounters deleteFile(final Path file, final DeleteOption... deleteOptions) throws IOException {
461         // Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files.
462         return deleteFile(file, NOFOLLOW_LINK_OPTION_ARRAY, deleteOptions);
463     }
464 
465     /**
466      * Deletes the given file.
467      *
468      * @param file The file to delete.
469      * @param linkOptions How to handle symbolic links.
470      * @param deleteOptions How to handle deletion.
471      * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
472      * @throws IOException if an I/O error occurs.
473      * @throws NoSuchFileException if the file is a directory.
474      * @since 2.9.0
475      */
476     public static PathCounters deleteFile(final Path file, final LinkOption[] linkOptions,
477         final DeleteOption... deleteOptions) throws NoSuchFileException, IOException {
478         if (Files.isDirectory(file, linkOptions)) {
479             throw new NoSuchFileException(file.toString());
480         }
481         final PathCounters pathCounts = Counters.longPathCounters();
482         final boolean exists = Files.exists(file, linkOptions);
483         final long size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
484         if (overrideReadOnly(deleteOptions) && exists) {
485             setReadOnly(file, false, linkOptions);
486         }
487         if (Files.deleteIfExists(file)) {
488             pathCounts.getFileCounter().increment();
489             pathCounts.getByteCounter().add(size);
490         }
491         return pathCounts;
492     }
493 
494     /**
495      * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The
496      * comparison includes all files in all sub-directories.
497      *
498      * @param path1 The first directory.
499      * @param path2 The second directory.
500      * @return Whether the two directories contain the same files while considering file contents.
501      * @throws IOException if an I/O error is thrown by a visitor method
502      */
503     public static boolean directoryAndFileContentEquals(final Path path1, final Path path2) throws IOException {
504         return directoryAndFileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY,
505             EMPTY_FILE_VISIT_OPTION_ARRAY);
506     }
507 
508     /**
509      * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The
510      * comparison includes all files in all sub-directories.
511      *
512      * @param path1 The first directory.
513      * @param path2 The second directory.
514      * @param linkOptions options to follow links.
515      * @param openOptions options to open files.
516      * @param fileVisitOption options to configure traversal.
517      * @return Whether the two directories contain the same files while considering file contents.
518      * @throws IOException if an I/O error is thrown by a visitor method
519      */
520     public static boolean directoryAndFileContentEquals(final Path path1, final Path path2,
521         final LinkOption[] linkOptions, final OpenOption[] openOptions, final FileVisitOption[] fileVisitOption)
522         throws IOException {
523         // First walk both file trees and gather normalized paths.
524         if (path1 == null && path2 == null) {
525             return true;
526         }
527         if (path1 == null || path2 == null) {
528             return false;
529         }
530         if (Files.notExists(path1) && Files.notExists(path2)) {
531             return true;
532         }
533         final RelativeSortedPaths relativeSortedPaths = new RelativeSortedPaths(path1, path2, Integer.MAX_VALUE,
534             linkOptions, fileVisitOption);
535         // If the normalized path names and counts are not the same, no need to compare contents.
536         if (!relativeSortedPaths.equals) {
537             return false;
538         }
539         // Both visitors contain the same normalized paths, we can compare file contents.
540         final List<Path> fileList1 = relativeSortedPaths.relativeFileList1;
541         final List<Path> fileList2 = relativeSortedPaths.relativeFileList2;
542         for (final Path path : fileList1) {
543             final int binarySearch = Collections.binarySearch(fileList2, path);
544             if (binarySearch <= -1) {
545                 throw new IllegalStateException("Unexpected mismatch.");
546             }
547             if (!fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) {
548                 return false;
549             }
550         }
551         return true;
552     }
553 
554     /**
555      * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The
556      * comparison includes all files in all sub-directories.
557      *
558      * @param path1 The first directory.
559      * @param path2 The second directory.
560      * @return Whether the two directories contain the same files without considering file contents.
561      * @throws IOException if an I/O error is thrown by a visitor method
562      */
563     public static boolean directoryContentEquals(final Path path1, final Path path2) throws IOException {
564         return directoryContentEquals(path1, path2, Integer.MAX_VALUE, EMPTY_LINK_OPTION_ARRAY,
565             EMPTY_FILE_VISIT_OPTION_ARRAY);
566     }
567 
568     /**
569      * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The
570      * comparison includes all files in all sub-directories.
571      *
572      * @param path1 The first directory.
573      * @param path2 The second directory.
574      * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
575      * @param linkOptions options to follow links.
576      * @param fileVisitOptions options to configure the traversal
577      * @return Whether the two directories contain the same files without considering file contents.
578      * @throws IOException if an I/O error is thrown by a visitor method
579      */
580     public static boolean directoryContentEquals(final Path path1, final Path path2, final int maxDepth,
581         final LinkOption[] linkOptions, final FileVisitOption[] fileVisitOptions) throws IOException {
582         return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, fileVisitOptions).equals;
583     }
584 
585     /**
586      * Compares the file contents of two Paths to determine if they are equal or not.
587      * <p>
588      * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
589      * </p>
590      *
591      * @param path1 the first stream.
592      * @param path2 the second stream.
593      * @return true if the content of the streams are equal or they both don't exist, false otherwise.
594      * @throws NullPointerException if either input is null.
595      * @throws IOException if an I/O error occurs.
596      * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
597      */
598     public static boolean fileContentEquals(final Path path1, final Path path2) throws IOException {
599         return fileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY);
600     }
601 
602     /**
603      * Compares the file contents of two Paths to determine if they are equal or not.
604      * <p>
605      * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
606      * </p>
607      *
608      * @param path1 the first stream.
609      * @param path2 the second stream.
610      * @param linkOptions options specifying how files are followed.
611      * @param openOptions options specifying how files are opened.
612      * @return true if the content of the streams are equal or they both don't exist, false otherwise.
613      * @throws NullPointerException if either input is null.
614      * @throws IOException if an I/O error occurs.
615      * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
616      */
617     public static boolean fileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions,
618         final OpenOption[] openOptions) throws IOException {
619         if (path1 == null && path2 == null) {
620             return true;
621         }
622         if (path1 == null || path2 == null) {
623             return false;
624         }
625         final Path nPath1 = path1.normalize();
626         final Path nPath2 = path2.normalize();
627         final boolean path1Exists = Files.exists(nPath1, linkOptions);
628         if (path1Exists != Files.exists(nPath2, linkOptions)) {
629             return false;
630         }
631         if (!path1Exists) {
632             // Two not existing files are equal?
633             // Same as FileUtils
634             return true;
635         }
636         if (Files.isDirectory(nPath1, linkOptions)) {
637             // don't compare directory contents.
638             throw new IOException("Can't compare directories, only files: " + nPath1);
639         }
640         if (Files.isDirectory(nPath2, linkOptions)) {
641             // don't compare directory contents.
642             throw new IOException("Can't compare directories, only files: " + nPath2);
643         }
644         if (Files.size(nPath1) != Files.size(nPath2)) {
645             // lengths differ, cannot be equal
646             return false;
647         }
648         if (path1.equals(path2)) {
649             // same file
650             return true;
651         }
652         try (final InputStream inputStream1 = Files.newInputStream(nPath1, openOptions);
653             final InputStream inputStream2 = Files.newInputStream(nPath2, openOptions)) {
654             return IOUtils.contentEquals(inputStream1, inputStream2);
655         }
656     }
657 
658     /**
659      * <p>
660      * Applies an {@link IOFileFilter} to the provided {@link File} objects. The resulting array is a subset of the
661      * original file list that matches the provided filter.
662      * </p>
663      *
664      * <p>
665      * The {@link Set} returned by this method is not guaranteed to be thread safe.
666      * </p>
667      *
668      * <pre>
669      * Set&lt;File&gt; allFiles = ...
670      * Set&lt;File&gt; javaFiles = FileFilterUtils.filterSet(allFiles,
671      *     FileFilterUtils.suffixFileFilter(".java"));
672      * </pre>
673      *
674      * @param filter the filter to apply to the set of files.
675      * @param paths the array of files to apply the filter to.
676      *
677      * @return a subset of {@code files} that is accepted by the file filter.
678      * @throws IllegalArgumentException if the filter is {@code null} or {@code files} contains a {@code null}
679      *         value.
680      *
681      * @since 2.9.0
682      */
683     public static Path[] filter(final PathFilter filter, final Path... paths) {
684         Objects.requireNonNull(filter, "filter");
685         if (paths == null) {
686             return EMPTY_PATH_ARRAY;
687         }
688         return filterPaths(filter, Stream.of(paths), Collectors.toList()).toArray(EMPTY_PATH_ARRAY);
689     }
690 
691     private static <R, A> R filterPaths(final PathFilter filter, final Stream<Path> stream,
692         final Collector<? super Path, A, R> collector) {
693         Objects.requireNonNull(filter, "filter");
694         Objects.requireNonNull(collector, "collector");
695         if (stream == null) {
696             return Stream.<Path>empty().collect(collector);
697         }
698         return stream.filter(p -> {
699             try {
700                 return p != null && filter.accept(p, readBasicFileAttributes(p)) == FileVisitResult.CONTINUE;
701             } catch (final IOException e) {
702                 return false;
703             }
704         }).collect(collector);
705     }
706 
707     /**
708      * Reads the access control list from a file attribute view.
709      *
710      * @param sourcePath the path to the file.
711      * @return a file attribute view of the specified type, or null if the attribute view type is not available.
712      * @throws IOException if an I/O error occurs.
713      * @since 2.8.0
714      */
715     public static List<AclEntry> getAclEntryList(final Path sourcePath) throws IOException {
716         final AclFileAttributeView fileAttributeView = Files.getFileAttributeView(sourcePath,
717             AclFileAttributeView.class);
718         return fileAttributeView == null ? null : fileAttributeView.getAcl();
719     }
720 
721     /**
722      * Tests whether the specified {@code Path} is a directory or not. Implemented as a
723      * null-safe delegate to {@code Files.isDirectory(Path path, LinkOption... options)}.
724      *
725      * @param   path the path to the file.
726      * @param   options options indicating how symbolic links are handled
727      * @return  {@code true} if the file is a directory; {@code false} if
728      *          the path is null, the file does not exist, is not a directory, or it cannot
729      *          be determined if the file is a directory or not.
730      * @throws SecurityException     In the case of the default provider, and a security manager is installed, the
731      *                               {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read
732      *                               access to the directory.
733      * @since 2.9.0
734      */
735     public static boolean isDirectory(final Path path, final LinkOption... options) {
736         return path != null && Files.isDirectory(path, options);
737     }
738 
739     /**
740      * Tests whether the given file or directory is empty.
741      *
742      * @param path the file or directory to query.
743      * @return whether the file or directory is empty.
744      * @throws IOException if an I/O error occurs.
745      */
746     public static boolean isEmpty(final Path path) throws IOException {
747         return Files.isDirectory(path) ? isEmptyDirectory(path) : isEmptyFile(path);
748     }
749 
750     /**
751      * Tests whether the directory is empty.
752      *
753      * @param directory the directory to query.
754      * @return whether the directory is empty.
755      * @throws NotDirectoryException if the file could not otherwise be opened because it is not a directory
756      *                               <i>(optional specific exception)</i>.
757      * @throws IOException           if an I/O error occurs.
758      * @throws SecurityException     In the case of the default provider, and a security manager is installed, the
759      *                               {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read
760      *                               access to the directory.
761      */
762     public static boolean isEmptyDirectory(final Path directory) throws IOException {
763         try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
764             return !directoryStream.iterator().hasNext();
765         }
766     }
767 
768     /**
769      * Tests whether the given file is empty.
770      *
771      * @param file the file to query.
772      * @return whether the file is empty.
773      * @throws IOException       if an I/O error occurs.
774      * @throws SecurityException In the case of the default provider, and a security manager is installed, its
775      *                           {@link SecurityManager#checkRead(String) checkRead} method denies read access to the
776      *                           file.
777      */
778     public static boolean isEmptyFile(final Path file) throws IOException {
779         return Files.size(file) <= 0;
780     }
781 
782     /**
783      * Tests if the specified {@code Path} is newer than the specified time reference.
784      *
785      * @param file the {@code Path} of which the modification date must be compared
786      * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
787      * @param options options indicating how symbolic links are handled * @return true if the {@code Path} exists and
788      *        has been modified after the given time reference.
789      * @return true if the {@code Path} exists and has been modified after the given time reference.
790      * @throws IOException if an I/O error occurs.
791      * @throws NullPointerException if the file is {@code null}
792      * @since 2.9.0
793      */
794     public static boolean isNewer(final Path file, final long timeMillis, final LinkOption... options)
795         throws IOException {
796         Objects.requireNonNull(file, "file");
797         if (Files.notExists(file)) {
798             return false;
799         }
800         return Files.getLastModifiedTime(file, options).toMillis() > timeMillis;
801     }
802 
803     /**
804      * Tests whether the specified {@code Path} is a regular file or not. Implemented as a
805      * null-safe delegate to {@code Files.isRegularFile(Path path, LinkOption... options)}.
806      *
807      * @param   path the path to the file.
808      * @param   options options indicating how symbolic links are handled
809      * @return  {@code true} if the file is a regular file; {@code false} if
810      *          the path is null, the file does not exist, is not a directory, or it cannot
811      *          be determined if the file is a regular file or not.
812      * @throws SecurityException     In the case of the default provider, and a security manager is installed, the
813      *                               {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read
814      *                               access to the directory.
815      * @since 2.9.0
816      */
817     public static boolean isRegularFile(final Path path, final LinkOption... options) {
818         return path != null && Files.isRegularFile(path, options);
819     }
820 
821     /**
822      * Creates a new DirectoryStream for Paths rooted at the given directory.
823      *
824      * @param dir the path to the directory to stream.
825      * @param pathFilter the directory stream filter.
826      * @return a new instance.
827      * @throws IOException if an I/O error occurs.
828      */
829     public static DirectoryStream<Path> newDirectoryStream(final Path dir, final PathFilter pathFilter)
830         throws IOException {
831         return Files.newDirectoryStream(dir, new DirectoryStreamFilter(pathFilter));
832     }
833 
834     /**
835      * Returns true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
836      *
837      * @param deleteOptions the array to test
838      * @return true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
839      */
840     private static boolean overrideReadOnly(final DeleteOption... deleteOptions) {
841         if (deleteOptions == null) {
842             return false;
843         }
844         return Stream.of(deleteOptions).anyMatch(e -> e == StandardDeleteOption.OVERRIDE_READ_ONLY);
845     }
846 
847     /**
848      * Shorthand for {@code Files.readAttributes(path, BasicFileAttributes.class)}
849      *
850      * @param path the path to read.
851      * @return the path attributes.
852      * @throws IOException if an I/O error occurs.
853      * @since 2.9.0
854      */
855     public static BasicFileAttributes readBasicFileAttributes(final Path path) throws IOException {
856         return Files.readAttributes(path, BasicFileAttributes.class);
857     }
858 
859     /**
860      * Shorthand for {@code Files.readAttributes(path, BasicFileAttributes.class)} while wrapping {@link IOException}
861      * as {@link UncheckedIOException}.
862      *
863      * @param path the path to read.
864      * @return the path attributes.
865      * @throws UncheckedIOException if an I/O error occurs
866      * @since 2.9.0
867      */
868     public static BasicFileAttributes readBasicFileAttributesUnchecked(final Path path) {
869         try {
870             return readBasicFileAttributes(path);
871         } catch (final IOException e) {
872             throw new UncheckedIOException(e);
873         }
874     }
875 
876     /**
877      * Relativizes all files in the given {@code collection} against a {@code parent}.
878      *
879      * @param collection The collection of paths to relativize.
880      * @param parent relativizes against this parent path.
881      * @param sort Whether to sort the result.
882      * @param comparator How to sort.
883      * @return A collection of relativized paths, optionally sorted.
884      */
885     static List<Path> relativize(final Collection<Path> collection, final Path parent, final boolean sort,
886         final Comparator<? super Path> comparator) {
887         Stream<Path> stream = collection.stream().map(parent::relativize);
888         if (sort) {
889             stream = comparator == null ? stream.sorted() : stream.sorted(comparator);
890         }
891         return stream.collect(Collectors.toList());
892     }
893 
894     /**
895      * Sets the given Path to the {@code readOnly} value.
896      * <p>
897      * This behavior is OS dependent.
898      * </p>
899      *
900      * @param path The path to set.
901      * @param readOnly true for read-only, false for not read-only.
902      * @param linkOptions options indicating how symbolic links are handled.
903      * @return The given path.
904      * @throws IOException if an I/O error occurs.
905      * @since 2.8.0
906      */
907     public static Path setReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions)
908         throws IOException {
909         final List<Exception> causeList = new ArrayList<>(2);
910         final DosFileAttributeView fileAttributeView = Files.getFileAttributeView(path, DosFileAttributeView.class,
911             linkOptions);
912         if (fileAttributeView != null) {
913             try {
914                 fileAttributeView.setReadOnly(readOnly);
915                 return path;
916             } catch (final IOException e) {
917                 // ignore for now, retry with PosixFileAttributeView
918                 causeList.add(e);
919             }
920         }
921         final PosixFileAttributeView posixFileAttributeView = Files.getFileAttributeView(path,
922             PosixFileAttributeView.class, linkOptions);
923         if (posixFileAttributeView != null) {
924             // Works on Windows but not on Ubuntu:
925             // Files.setAttribute(path, "unix:readonly", readOnly, options);
926             // java.lang.IllegalArgumentException: 'unix:readonly' not recognized
927             final PosixFileAttributes readAttributes = posixFileAttributeView.readAttributes();
928             final Set<PosixFilePermission> permissions = readAttributes.permissions();
929             permissions.remove(PosixFilePermission.OWNER_WRITE);
930             permissions.remove(PosixFilePermission.GROUP_WRITE);
931             permissions.remove(PosixFilePermission.OTHERS_WRITE);
932             try {
933                 return Files.setPosixFilePermissions(path, permissions);
934             } catch (final IOException e) {
935                 causeList.add(e);
936             }
937         }
938         if (!causeList.isEmpty()) {
939             throw new IOExceptionList(path.toString(), causeList);
940         }
941         throw new IOException(
942             String.format("No DosFileAttributeView or PosixFileAttributeView for '%s' (linkOptions=%s)", path,
943                 Arrays.toString(linkOptions)));
944     }
945 
946     /**
947      * Converts an array of {@link FileVisitOption} to a {@link Set}.
948      *
949      * @param fileVisitOptions input array.
950      * @return a new Set.
951      */
952     static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... fileVisitOptions) {
953         return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class)
954             : Stream.of(fileVisitOptions).collect(Collectors.toSet());
955     }
956 
957     /**
958      * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
959      *
960      * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
961      *
962      * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
963      * @param directory See {@link Files#walkFileTree(Path,FileVisitor)}.
964      * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
965      * @return the given visitor.
966      *
967      * @throws IOException if an I/O error is thrown by a visitor method
968      */
969     public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path directory)
970         throws IOException {
971         Files.walkFileTree(directory, visitor);
972         return visitor;
973     }
974 
975     /**
976      * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
977      *
978      * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
979      *
980      * @param start See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
981      * @param options See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
982      * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
983      * @param visitor See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
984      * @param <T> See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
985      * @return the given visitor.
986      *
987      * @throws IOException if an I/O error is thrown by a visitor method
988      */
989     public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path start,
990         final Set<FileVisitOption> options, final int maxDepth) throws IOException {
991         Files.walkFileTree(start, options, maxDepth, visitor);
992         return visitor;
993     }
994 
995     /**
996      * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
997      *
998      * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
999      *
1000      * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
1001      * @param first See {@link Paths#get(String,String[])}.
1002      * @param more See {@link Paths#get(String,String[])}.
1003      * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
1004      * @return the given visitor.
1005      *
1006      * @throws IOException if an I/O error is thrown by a visitor method
1007      */
1008     public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final String first,
1009         final String... more) throws IOException {
1010         return visitFileTree(visitor, Paths.get(first, more));
1011     }
1012 
1013     /**
1014      * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
1015      *
1016      * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
1017      *
1018      * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
1019      * @param uri See {@link Paths#get(URI)}.
1020      * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
1021      * @return the given visitor.
1022      *
1023      * @throws IOException if an I/O error is thrown by a visitor method
1024      */
1025     public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final URI uri)
1026         throws IOException {
1027         return visitFileTree(visitor, Paths.get(uri));
1028     }
1029 
1030     /**
1031      * Returns a stream of filtered paths.
1032      *
1033      * @param start the start path
1034      * @param pathFilter the path filter
1035      * @param maxDepth the maximum depth of directories to walk.
1036      * @param readAttributes whether to call the filters with file attributes (false passes null).
1037      * @param options the options to configure the walk.
1038      * @return a filtered stream of paths.
1039      * @throws IOException if an I/O error is thrown when accessing the starting file.
1040      * @since 2.9.0
1041      */
1042     public static Stream<Path> walk(final Path start, final PathFilter pathFilter, final int maxDepth,
1043         final boolean readAttributes, final FileVisitOption... options) throws IOException {
1044         return Files.walk(start, maxDepth, options).filter(path -> pathFilter.accept(path,
1045             readAttributes ? readBasicFileAttributesUnchecked(path) : null) == FileVisitResult.CONTINUE);
1046     }
1047 
1048     /**
1049      * Does allow to instantiate.
1050      */
1051     private PathUtils() {
1052         // do not instantiate.
1053     }
1054 
1055 }