001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.io.file;
019
020import java.io.File;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.UncheckedIOException;
024import java.net.URI;
025import java.net.URL;
026import java.nio.file.CopyOption;
027import java.nio.file.DirectoryStream;
028import java.nio.file.FileVisitOption;
029import java.nio.file.FileVisitResult;
030import java.nio.file.FileVisitor;
031import java.nio.file.Files;
032import java.nio.file.LinkOption;
033import java.nio.file.NoSuchFileException;
034import java.nio.file.NotDirectoryException;
035import java.nio.file.OpenOption;
036import java.nio.file.Path;
037import java.nio.file.Paths;
038import java.nio.file.attribute.AclEntry;
039import java.nio.file.attribute.AclFileAttributeView;
040import java.nio.file.attribute.BasicFileAttributes;
041import java.nio.file.attribute.DosFileAttributeView;
042import java.nio.file.attribute.FileAttribute;
043import java.nio.file.attribute.PosixFileAttributeView;
044import java.nio.file.attribute.PosixFileAttributes;
045import java.nio.file.attribute.PosixFilePermission;
046import java.util.ArrayList;
047import java.util.Arrays;
048import java.util.Collection;
049import java.util.Collections;
050import java.util.Comparator;
051import java.util.EnumSet;
052import java.util.List;
053import java.util.Objects;
054import java.util.Set;
055import java.util.stream.Collector;
056import java.util.stream.Collectors;
057import java.util.stream.Stream;
058
059import org.apache.commons.io.IOExceptionList;
060import org.apache.commons.io.IOUtils;
061import org.apache.commons.io.file.Counters.PathCounters;
062import org.apache.commons.io.filefilter.IOFileFilter;
063
064/**
065 * NIO Path utilities.
066 *
067 * @since 2.7
068 */
069public final class PathUtils {
070
071    /**
072     * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted
073     * relative lists when comparing directories.
074     */
075    private static class RelativeSortedPaths {
076
077        final boolean equals;
078        // final List<Path> relativeDirList1; // might need later?
079        // final List<Path> relativeDirList2; // might need later?
080        final List<Path> relativeFileList1;
081        final List<Path> relativeFileList2;
082
083        /**
084         * Constructs and initializes a new instance by accumulating directory and file info.
085         *
086         * @param dir1 First directory to compare.
087         * @param dir2 Seconds directory to compare.
088         * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
089         * @param linkOptions Options indicating how symbolic links are handled.
090         * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
091         * @throws IOException if an I/O error is thrown by a visitor method.
092         */
093        private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth,
094            final LinkOption[] linkOptions, final FileVisitOption[] fileVisitOptions) throws IOException {
095            final List<Path> tmpRelativeDirList1;
096            final List<Path> tmpRelativeDirList2;
097            List<Path> tmpRelativeFileList1 = null;
098            List<Path> tmpRelativeFileList2 = null;
099            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}