PathUtils.java

  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. package org.apache.commons.io.file;

  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.OutputStream;
  22. import java.io.RandomAccessFile;
  23. import java.math.BigInteger;
  24. import java.net.URI;
  25. import java.net.URISyntaxException;
  26. import java.net.URL;
  27. import java.nio.charset.Charset;
  28. import java.nio.file.AccessDeniedException;
  29. import java.nio.file.CopyOption;
  30. import java.nio.file.DirectoryStream;
  31. import java.nio.file.FileSystem;
  32. import java.nio.file.FileVisitOption;
  33. import java.nio.file.FileVisitResult;
  34. import java.nio.file.FileVisitor;
  35. import java.nio.file.Files;
  36. import java.nio.file.LinkOption;
  37. import java.nio.file.NoSuchFileException;
  38. import java.nio.file.NotDirectoryException;
  39. import java.nio.file.OpenOption;
  40. import java.nio.file.Path;
  41. import java.nio.file.Paths;
  42. import java.nio.file.StandardOpenOption;
  43. import java.nio.file.attribute.AclEntry;
  44. import java.nio.file.attribute.AclFileAttributeView;
  45. import java.nio.file.attribute.BasicFileAttributes;
  46. import java.nio.file.attribute.DosFileAttributeView;
  47. import java.nio.file.attribute.DosFileAttributes;
  48. import java.nio.file.attribute.FileAttribute;
  49. import java.nio.file.attribute.FileTime;
  50. import java.nio.file.attribute.PosixFileAttributeView;
  51. import java.nio.file.attribute.PosixFileAttributes;
  52. import java.nio.file.attribute.PosixFilePermission;
  53. import java.time.Duration;
  54. import java.time.Instant;
  55. import java.time.chrono.ChronoZonedDateTime;
  56. import java.util.ArrayList;
  57. import java.util.Arrays;
  58. import java.util.Collection;
  59. import java.util.Collections;
  60. import java.util.Comparator;
  61. import java.util.EnumSet;
  62. import java.util.HashSet;
  63. import java.util.Iterator;
  64. import java.util.List;
  65. import java.util.Objects;
  66. import java.util.Set;
  67. import java.util.function.Function;
  68. import java.util.stream.Collector;
  69. import java.util.stream.Collectors;
  70. import java.util.stream.Stream;
  71. import java.util.stream.StreamSupport;

  72. import org.apache.commons.io.Charsets;
  73. import org.apache.commons.io.FileUtils;
  74. import org.apache.commons.io.FilenameUtils;
  75. import org.apache.commons.io.IOUtils;
  76. import org.apache.commons.io.RandomAccessFileMode;
  77. import org.apache.commons.io.RandomAccessFiles;
  78. import org.apache.commons.io.ThreadUtils;
  79. import org.apache.commons.io.file.Counters.PathCounters;
  80. import org.apache.commons.io.file.attribute.FileTimes;
  81. import org.apache.commons.io.filefilter.IOFileFilter;
  82. import org.apache.commons.io.function.IOFunction;
  83. import org.apache.commons.io.function.IOSupplier;

  84. /**
  85.  * NIO Path utilities.
  86.  *
  87.  * @since 2.7
  88.  */
  89. public final class PathUtils {

  90.     /**
  91.      * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted relative lists when comparing directories.
  92.      */
  93.     private static final class RelativeSortedPaths {

  94.         /**
  95.          * Compares lists of paths regardless of their file systems.
  96.          *
  97.          * @param list1 the first list.
  98.          * @param list2 the second list.
  99.          * @return whether the lists are equal.
  100.          */
  101.         private static boolean equalsIgnoreFileSystem(final List<Path> list1, final List<Path> list2) {
  102.             if (list1.size() != list2.size()) {
  103.                 return false;
  104.             }
  105.             // compare both lists using iterators
  106.             final Iterator<Path> iterator1 = list1.iterator();
  107.             final Iterator<Path> iterator2 = list2.iterator();
  108.             while (iterator1.hasNext() && iterator2.hasNext()) {
  109.                 if (!equalsIgnoreFileSystem(iterator1.next(), iterator2.next())) {
  110.                     return false;
  111.                 }
  112.             }
  113.             return true;
  114.         }

  115.         private static boolean equalsIgnoreFileSystem(final Path path1, final Path path2) {
  116.             final FileSystem fileSystem1 = path1.getFileSystem();
  117.             final FileSystem fileSystem2 = path2.getFileSystem();
  118.             if (fileSystem1 == fileSystem2) {
  119.                 return path1.equals(path2);
  120.             }
  121.             final String separator1 = fileSystem1.getSeparator();
  122.             final String separator2 = fileSystem2.getSeparator();
  123.             final String string1 = path1.toString();
  124.             final String string2 = path2.toString();
  125.             if (Objects.equals(separator1, separator2)) {
  126.                 // Separators are the same, so we can use toString comparison
  127.                 return Objects.equals(string1, string2);
  128.             }
  129.             // Compare paths from different file systems component by component.
  130.             return extractKey(separator1, string1).equals(extractKey(separator2, string2));
  131.         }

  132.         static String extractKey(final String separator, final String string) {
  133.             // Replace the file separator in a path string with a string that is not legal in a path on Windows, Linux, and macOS.
  134.             return string.replaceAll("\\" + separator, ">");
  135.         }

  136.         final boolean equals;
  137.         // final List<Path> relativeDirList1; // might need later?
  138.         // final List<Path> relativeDirList2; // might need later?
  139.         final List<Path> relativeFileList1;
  140.         final List<Path> relativeFileList2;

  141.         /**
  142.          * Constructs and initializes a new instance by accumulating directory and file info.
  143.          *
  144.          * @param dir1             First directory to compare.
  145.          * @param dir2             Seconds directory to compare.
  146.          * @param maxDepth         See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
  147.          * @param linkOptions      Options indicating how symbolic links are handled.
  148.          * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
  149.          * @throws IOException if an I/O error is thrown by a visitor method.
  150.          */
  151.         private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth, final LinkOption[] linkOptions,
  152.                 final FileVisitOption[] fileVisitOptions) throws IOException {
  153.             final List<Path> tmpRelativeDirList1;
  154.             final List<Path> tmpRelativeDirList2;
  155.             List<Path> tmpRelativeFileList1 = null;
  156.             List<Path> tmpRelativeFileList2 = null;
  157.             if (dir1 == null && dir2 == null) {
  158.                 equals = true;
  159.             } else if (dir1 == null ^ dir2 == null) {
  160.                 equals = false;
  161.             } else {
  162.                 final boolean parentDirNotExists1 = Files.notExists(dir1, linkOptions);
  163.                 final boolean parentDirNotExists2 = Files.notExists(dir2, linkOptions);
  164.                 if (parentDirNotExists1 || parentDirNotExists2) {
  165.                     equals = parentDirNotExists1 && parentDirNotExists2;
  166.                 } else {
  167.                     final AccumulatorPathVisitor visitor1 = accumulate(dir1, maxDepth, fileVisitOptions);
  168.                     final AccumulatorPathVisitor visitor2 = accumulate(dir2, maxDepth, fileVisitOptions);
  169.                     if (visitor1.getDirList().size() != visitor2.getDirList().size() || visitor1.getFileList().size() != visitor2.getFileList().size()) {
  170.                         equals = false;
  171.                     } else {
  172.                         tmpRelativeDirList1 = visitor1.relativizeDirectories(dir1, true, null);
  173.                         tmpRelativeDirList2 = visitor2.relativizeDirectories(dir2, true, null);
  174.                         if (!equalsIgnoreFileSystem(tmpRelativeDirList1, tmpRelativeDirList2)) {
  175.                             equals = false;
  176.                         } else {
  177.                             tmpRelativeFileList1 = visitor1.relativizeFiles(dir1, true, null);
  178.                             tmpRelativeFileList2 = visitor2.relativizeFiles(dir2, true, null);
  179.                             equals = equalsIgnoreFileSystem(tmpRelativeFileList1, tmpRelativeFileList2);
  180.                         }
  181.                     }
  182.                 }
  183.             }
  184.             // relativeDirList1 = tmpRelativeDirList1;
  185.             // relativeDirList2 = tmpRelativeDirList2;
  186.             relativeFileList1 = tmpRelativeFileList1;
  187.             relativeFileList2 = tmpRelativeFileList2;
  188.         }
  189.     }

  190.     private static final OpenOption[] OPEN_OPTIONS_TRUNCATE = { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING };
  191.     private static final OpenOption[] OPEN_OPTIONS_APPEND = { StandardOpenOption.CREATE, StandardOpenOption.APPEND };
  192.     /**
  193.      * Empty {@link CopyOption} array.
  194.      *
  195.      * @since 2.8.0
  196.      */
  197.     public static final CopyOption[] EMPTY_COPY_OPTIONS = {};
  198.     /**
  199.      * Empty {@link DeleteOption} array.
  200.      *
  201.      * @since 2.8.0
  202.      */
  203.     public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = {};
  204.     /**
  205.      * Empty {@link FileAttribute} array.
  206.      *
  207.      * @since 2.13.0
  208.      */
  209.     public static final FileAttribute<?>[] EMPTY_FILE_ATTRIBUTE_ARRAY = {};
  210.     /**
  211.      * Empty {@link FileVisitOption} array.
  212.      */
  213.     public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = {};
  214.     /**
  215.      * Empty {@link LinkOption} array.
  216.      */
  217.     public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = {};
  218.     /**
  219.      * {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
  220.      *
  221.      * @since 2.9.0
  222.      * @deprecated Use {@link #noFollowLinkOptionArray()}.
  223.      */
  224.     @Deprecated
  225.     public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = { LinkOption.NOFOLLOW_LINKS };
  226.     /**
  227.      * A LinkOption used to follow link in this class, the inverse of {@link LinkOption#NOFOLLOW_LINKS}.
  228.      *
  229.      * @since 2.12.0
  230.      */
  231.     static final LinkOption NULL_LINK_OPTION = null;
  232.     /**
  233.      * Empty {@link OpenOption} array.
  234.      */
  235.     public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = {};
  236.     /**
  237.      * Empty {@link Path} array.
  238.      *
  239.      * @since 2.9.0
  240.      */
  241.     public static final Path[] EMPTY_PATH_ARRAY = {};

  242.     /**
  243.      * Accumulates file tree information in a {@link AccumulatorPathVisitor}.
  244.      *
  245.      * @param directory        The directory to accumulate information.
  246.      * @param maxDepth         See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
  247.      * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
  248.      * @throws IOException if an I/O error is thrown by a visitor method.
  249.      * @return file tree information.
  250.      */
  251.     private static AccumulatorPathVisitor accumulate(final Path directory, final int maxDepth, final FileVisitOption[] fileVisitOptions) throws IOException {
  252.         return visitFileTree(AccumulatorPathVisitor.builder().setDirectoryPostTransformer(PathUtils::stripTrailingSeparator).get(), directory,
  253.                 toFileVisitOptionSet(fileVisitOptions), maxDepth);
  254.     }

  255.     /**
  256.      * Cleans a directory by deleting only files, including in subdirectories, but without deleting the directories.
  257.      *
  258.      * @param directory directory to clean.
  259.      * @return The visitation path counters.
  260.      * @throws IOException if an I/O error is thrown by a visitor method.
  261.      */
  262.     public static PathCounters cleanDirectory(final Path directory) throws IOException {
  263.         return cleanDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
  264.     }

  265.     /**
  266.      * Cleans a directory by deleting only files, including in subdirectories, but without deleting the directories.
  267.      *
  268.      * @param directory     directory to clean.
  269.      * @param deleteOptions How to handle deletion.
  270.      * @return The visitation path counters.
  271.      * @throws IOException if an I/O error is thrown by a visitor method.
  272.      * @since 2.8.0
  273.      */
  274.     public static PathCounters cleanDirectory(final Path directory, final DeleteOption... deleteOptions) throws IOException {
  275.         return visitFileTree(new CleaningPathVisitor(Counters.longPathCounters(), deleteOptions), directory).getPathCounters();
  276.     }

  277.     /**
  278.      * Compares the given {@link Path}'s last modified time to the given file time.
  279.      *
  280.      * @param file     the {@link Path} to test.
  281.      * @param fileTime the time reference.
  282.      * @param options  options indicating how to handle symbolic links.
  283.      * @return See {@link FileTime#compareTo(FileTime)}
  284.      * @throws IOException          if an I/O error occurs.
  285.      * @throws NullPointerException if the file is {@code null}.
  286.      */
  287.     private static int compareLastModifiedTimeTo(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
  288.         return getLastModifiedTime(file, options).compareTo(fileTime);
  289.     }

  290.     /**
  291.      * Compares the files of two FileSystems to determine if they are equal or not while considering file contents. The comparison includes all files in all
  292.      * subdirectories.
  293.      * <p>
  294.      * For example, to compare two ZIP files:
  295.      * </p>
  296.      *
  297.      * <pre>
  298.      * final Path zipPath1 = Paths.get("file1.zip");
  299.      * final Path zipPath2 = Paths.get("file2.zip");
  300.      * try (FileSystem fileSystem1 = FileSystems.newFileSystem(zipPath1, null); FileSystem fileSystem2 = FileSystems.newFileSystem(zipPath2, null)) {
  301.      *     assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir2));
  302.      * }
  303.      * </pre>
  304.      *
  305.      * @param fileSystem1 The first FileSystem.
  306.      * @param fileSystem2 The second FileSystem.
  307.      * @return Whether the two FileSystem contain the same files while considering file contents.
  308.      * @throws IOException if an I/O error is thrown by a visitor method.
  309.      * @since 2.19.0
  310.      */
  311.     public static boolean contentEquals(final FileSystem fileSystem1, final FileSystem fileSystem2) throws IOException {
  312.         if (Objects.equals(fileSystem1, fileSystem2)) {
  313.             return true;
  314.         }
  315.         final List<Path> sortedList1 = toSortedList(fileSystem1.getRootDirectories());
  316.         final List<Path> sortedList2 = toSortedList(fileSystem2.getRootDirectories());
  317.         if (sortedList1.size() != sortedList2.size()) {
  318.             return false;
  319.         }
  320.         for (int i = 0; i < sortedList1.size(); i++) {
  321.             if (!directoryAndFileContentEquals(sortedList1.get(i), sortedList2.get(i))) {
  322.                 return false;
  323.             }
  324.         }
  325.         return true;
  326.     }

  327.     /**
  328.      * Copies the InputStream from the supplier with {@link Files#copy(InputStream, Path, CopyOption...)}.
  329.      *
  330.      * @param in          Supplies the InputStream.
  331.      * @param target      See {@link Files#copy(InputStream, Path, CopyOption...)}.
  332.      * @param copyOptions See {@link Files#copy(InputStream, Path, CopyOption...)}.
  333.      * @return See {@link Files#copy(InputStream, Path, CopyOption...)}
  334.      * @throws IOException See {@link Files#copy(InputStream, Path, CopyOption...)}
  335.      * @since 2.12.0
  336.      */
  337.     public static long copy(final IOSupplier<InputStream> in, final Path target, final CopyOption... copyOptions) throws IOException {
  338.         try (InputStream inputStream = in.get()) {
  339.             return Files.copy(inputStream, target, copyOptions);
  340.         }
  341.     }

  342.     /**
  343.      * Copies a directory to another directory.
  344.      *
  345.      * @param sourceDirectory The source directory.
  346.      * @param targetDirectory The target directory.
  347.      * @param copyOptions     Specifies how the copying should be done.
  348.      * @return The visitation path counters.
  349.      * @throws IOException if an I/O error is thrown by a visitor method.
  350.      */
  351.     public static PathCounters copyDirectory(final Path sourceDirectory, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
  352.         final Path absoluteSource = sourceDirectory.toAbsolutePath();
  353.         return visitFileTree(new CopyDirectoryVisitor(Counters.longPathCounters(), absoluteSource, targetDirectory, copyOptions), absoluteSource)
  354.                 .getPathCounters();
  355.     }

  356.     /**
  357.      * Copies a URL to a directory.
  358.      *
  359.      * @param sourceFile  The source URL.
  360.      * @param targetFile  The target file.
  361.      * @param copyOptions Specifies how the copying should be done.
  362.      * @return The target file
  363.      * @throws IOException if an I/O error occurs.
  364.      * @see Files#copy(InputStream, Path, CopyOption...)
  365.      */
  366.     public static Path copyFile(final URL sourceFile, final Path targetFile, final CopyOption... copyOptions) throws IOException {
  367.         copy(sourceFile::openStream, targetFile, copyOptions);
  368.         return targetFile;
  369.     }

  370.     /**
  371.      * Copies a file to a directory.
  372.      *
  373.      * @param sourceFile      The source file.
  374.      * @param targetDirectory The target directory.
  375.      * @param copyOptions     Specifies how the copying should be done.
  376.      * @return The target file
  377.      * @throws IOException if an I/O error occurs.
  378.      * @see Files#copy(Path, Path, CopyOption...)
  379.      */
  380.     public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
  381.         // Path.resolve() naturally won't work across FileSystem unless we convert to a String
  382.         final Path sourceFileName = Objects.requireNonNull(sourceFile.getFileName(), "source file name");
  383.         final Path targetFile = resolve(targetDirectory, sourceFileName);
  384.         return Files.copy(sourceFile, targetFile, copyOptions);
  385.     }

  386.     /**
  387.      * Copies a URL to a directory.
  388.      *
  389.      * @param sourceFile      The source URL.
  390.      * @param targetDirectory The target directory.
  391.      * @param copyOptions     Specifies how the copying should be done.
  392.      * @return The target file
  393.      * @throws IOException if an I/O error occurs.
  394.      * @see Files#copy(InputStream, Path, CopyOption...)
  395.      */
  396.     public static Path copyFileToDirectory(final URL sourceFile, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
  397.         final Path resolve = targetDirectory.resolve(FilenameUtils.getName(sourceFile.getFile()));
  398.         copy(sourceFile::openStream, resolve, copyOptions);
  399.         return resolve;
  400.     }

  401.     /**
  402.      * Counts aspects of a directory including subdirectories.
  403.      *
  404.      * @param directory directory to delete.
  405.      * @return The visitor used to count the given directory.
  406.      * @throws IOException if an I/O error is thrown by a visitor method.
  407.      */
  408.     public static PathCounters countDirectory(final Path directory) throws IOException {
  409.         return visitFileTree(CountingPathVisitor.withLongCounters(), directory).getPathCounters();
  410.     }

  411.     /**
  412.      * Counts aspects of a directory including subdirectories.
  413.      *
  414.      * @param directory directory to count.
  415.      * @return The visitor used to count the given directory.
  416.      * @throws IOException if an I/O error occurs.
  417.      * @since 2.12.0
  418.      */
  419.     public static PathCounters countDirectoryAsBigInteger(final Path directory) throws IOException {
  420.         return visitFileTree(CountingPathVisitor.withBigIntegerCounters(), directory).getPathCounters();
  421.     }

  422.     /**
  423.      * Creates the parent directories for the given {@code path}.
  424.      * <p>
  425.      * If the parent directory already exists, then return it.
  426.      * </p>
  427.      *
  428.      * @param path  The path to a file (or directory).
  429.      * @param attrs An optional list of file attributes to set atomically when creating the directories.
  430.      * @return The Path for the {@code path}'s parent directory or null if the given path has no parent.
  431.      * @throws IOException if an I/O error occurs.
  432.      * @since 2.9.0
  433.      */
  434.     public static Path createParentDirectories(final Path path, final FileAttribute<?>... attrs) throws IOException {
  435.         return createParentDirectories(path, LinkOption.NOFOLLOW_LINKS, attrs);
  436.     }

  437.     /**
  438.      * Creates the parent directories for the given {@code path}.
  439.      * <p>
  440.      * If the parent directory already exists, then return it.
  441.      * </p>
  442.      *
  443.      * @param path       The path to a file (or directory).
  444.      * @param linkOption A {@link LinkOption} or null.
  445.      * @param attrs      An optional list of file attributes to set atomically when creating the directories.
  446.      * @return The Path for the {@code path}'s parent directory or null if the given path has no parent.
  447.      * @throws IOException if an I/O error occurs.
  448.      * @since 2.12.0
  449.      */
  450.     public static Path createParentDirectories(final Path path, final LinkOption linkOption, final FileAttribute<?>... attrs) throws IOException {
  451.         Path parent = getParent(path);
  452.         parent = linkOption == LinkOption.NOFOLLOW_LINKS ? parent : readIfSymbolicLink(parent);
  453.         if (parent == null) {
  454.             return null;
  455.         }
  456.         final boolean exists = linkOption == null ? Files.exists(parent) : Files.exists(parent, linkOption);
  457.         return exists ? parent : Files.createDirectories(parent, attrs);
  458.     }

  459.     /**
  460.      * Gets the current directory.
  461.      *
  462.      * @return the current directory.
  463.      * @since 2.9.0
  464.      */
  465.     public static Path current() {
  466.         return Paths.get(".");
  467.     }

  468.     /**
  469.      * Deletes a file or directory. If the path is a directory, delete it and all subdirectories.
  470.      * <p>
  471.      * The difference between {@link File#delete()} and this method are:
  472.      * </p>
  473.      * <ul>
  474.      * <li>A directory to delete does not have to be empty.</li>
  475.      * <li>You get exceptions when a file or directory cannot be deleted; {@link File#delete()} returns a boolean.
  476.      * </ul>
  477.      *
  478.      * @param path file or directory to delete, must not be {@code null}
  479.      * @return The visitor used to delete the given directory.
  480.      * @throws NullPointerException if the directory is {@code null}
  481.      * @throws IOException          if an I/O error is thrown by a visitor method or if an I/O error occurs.
  482.      */
  483.     public static PathCounters delete(final Path path) throws IOException {
  484.         return delete(path, EMPTY_DELETE_OPTION_ARRAY);
  485.     }

  486.     /**
  487.      * Deletes a file or directory. If the path is a directory, delete it and all subdirectories.
  488.      * <p>
  489.      * The difference between File.delete() and this method are:
  490.      * </p>
  491.      * <ul>
  492.      * <li>A directory to delete does not have to be empty.</li>
  493.      * <li>You get exceptions when a file or directory cannot be deleted; {@link File#delete()} returns a boolean.
  494.      * </ul>
  495.      *
  496.      * @param path          file or directory to delete, must not be {@code null}
  497.      * @param deleteOptions How to handle deletion.
  498.      * @return The visitor used to delete the given directory.
  499.      * @throws NullPointerException if the directory is {@code null}
  500.      * @throws IOException          if an I/O error is thrown by a visitor method or if an I/O error occurs.
  501.      * @since 2.8.0
  502.      */
  503.     public static PathCounters delete(final Path path, final DeleteOption... deleteOptions) throws IOException {
  504.         // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
  505.         return Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) ? deleteDirectory(path, deleteOptions) : deleteFile(path, deleteOptions);
  506.     }

  507.     /**
  508.      * Deletes a file or directory. If the path is a directory, delete it and all subdirectories.
  509.      * <p>
  510.      * The difference between File.delete() and this method are:
  511.      * </p>
  512.      * <ul>
  513.      * <li>A directory to delete does not have to be empty.</li>
  514.      * <li>You get exceptions when a file or directory cannot be deleted; {@link File#delete()} returns a boolean.
  515.      * </ul>
  516.      *
  517.      * @param path          file or directory to delete, must not be {@code null}
  518.      * @param linkOptions   How to handle symbolic links.
  519.      * @param deleteOptions How to handle deletion.
  520.      * @return The visitor used to delete the given directory.
  521.      * @throws NullPointerException if the directory is {@code null}
  522.      * @throws IOException          if an I/O error is thrown by a visitor method or if an I/O error occurs.
  523.      * @since 2.9.0
  524.      */
  525.     public static PathCounters delete(final Path path, final LinkOption[] linkOptions, final DeleteOption... deleteOptions) throws IOException {
  526.         // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
  527.         return Files.isDirectory(path, linkOptions) ? deleteDirectory(path, linkOptions, deleteOptions) : deleteFile(path, linkOptions, deleteOptions);
  528.     }

  529.     /**
  530.      * Deletes a directory including subdirectories.
  531.      *
  532.      * @param directory directory to delete.
  533.      * @return The visitor used to delete the given directory.
  534.      * @throws IOException if an I/O error is thrown by a visitor method.
  535.      */
  536.     public static PathCounters deleteDirectory(final Path directory) throws IOException {
  537.         return deleteDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
  538.     }

  539.     /**
  540.      * Deletes a directory including subdirectories.
  541.      *
  542.      * @param directory     directory to delete.
  543.      * @param deleteOptions How to handle deletion.
  544.      * @return The visitor used to delete the given directory.
  545.      * @throws IOException if an I/O error is thrown by a visitor method.
  546.      * @since 2.8.0
  547.      */
  548.     public static PathCounters deleteDirectory(final Path directory, final DeleteOption... deleteOptions) throws IOException {
  549.         final LinkOption[] linkOptions = noFollowLinkOptionArray();
  550.         // POSIX ops will noop on non-POSIX.
  551.         return withPosixFileAttributes(getParent(directory), linkOptions, overrideReadOnly(deleteOptions),
  552.                 pfa -> visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), directory).getPathCounters());
  553.     }

  554.     /**
  555.      * Deletes a directory including subdirectories.
  556.      *
  557.      * @param directory     directory to delete.
  558.      * @param linkOptions   How to handle symbolic links.
  559.      * @param deleteOptions How to handle deletion.
  560.      * @return The visitor used to delete the given directory.
  561.      * @throws IOException if an I/O error is thrown by a visitor method.
  562.      * @since 2.9.0
  563.      */
  564.     public static PathCounters deleteDirectory(final Path directory, final LinkOption[] linkOptions, final DeleteOption... deleteOptions) throws IOException {
  565.         return visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), directory).getPathCounters();
  566.     }

  567.     /**
  568.      * Deletes the given file.
  569.      *
  570.      * @param file The file to delete.
  571.      * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
  572.      * @throws IOException         if an I/O error occurs.
  573.      * @throws NoSuchFileException if the file is a directory
  574.      */
  575.     public static PathCounters deleteFile(final Path file) throws IOException {
  576.         return deleteFile(file, EMPTY_DELETE_OPTION_ARRAY);
  577.     }

  578.     /**
  579.      * Deletes the given file.
  580.      *
  581.      * @param file          The file to delete.
  582.      * @param deleteOptions How to handle deletion.
  583.      * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
  584.      * @throws IOException         if an I/O error occurs.
  585.      * @throws NoSuchFileException if the file is a directory.
  586.      * @since 2.8.0
  587.      */
  588.     public static PathCounters deleteFile(final Path file, final DeleteOption... deleteOptions) throws IOException {
  589.         // Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files.
  590.         return deleteFile(file, noFollowLinkOptionArray(), deleteOptions);
  591.     }

  592.     /**
  593.      * Deletes the given file.
  594.      *
  595.      * @param file          The file to delete.
  596.      * @param linkOptions   How to handle symbolic links.
  597.      * @param deleteOptions How to handle deletion.
  598.      * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
  599.      * @throws IOException         if an I/O error occurs.
  600.      * @throws NoSuchFileException if the file is a directory.
  601.      * @since 2.9.0
  602.      */
  603.     public static PathCounters deleteFile(final Path file, final LinkOption[] linkOptions, final DeleteOption... deleteOptions)
  604.             throws NoSuchFileException, IOException {
  605.         //
  606.         // TODO Needs clean up?
  607.         //
  608.         if (Files.isDirectory(file, linkOptions)) {
  609.             throw new NoSuchFileException(file.toString());
  610.         }
  611.         final PathCounters pathCounts = Counters.longPathCounters();
  612.         boolean exists = exists(file, linkOptions);
  613.         long size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
  614.         try {
  615.             if (Files.deleteIfExists(file)) {
  616.                 pathCounts.getFileCounter().increment();
  617.                 pathCounts.getByteCounter().add(size);
  618.                 return pathCounts;
  619.             }
  620.         } catch (final AccessDeniedException ignored) {
  621.             // Ignore and try again below.
  622.         }
  623.         final Path parent = getParent(file);
  624.         PosixFileAttributes posixFileAttributes = null;
  625.         try {
  626.             if (overrideReadOnly(deleteOptions)) {
  627.                 posixFileAttributes = readPosixFileAttributes(parent, linkOptions);
  628.                 setReadOnly(file, false, linkOptions);
  629.             }
  630.             // Read size _after_ having read/execute access on POSIX.
  631.             exists = exists(file, linkOptions);
  632.             size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
  633.             if (Files.deleteIfExists(file)) {
  634.                 pathCounts.getFileCounter().increment();
  635.                 pathCounts.getByteCounter().add(size);
  636.             }
  637.         } finally {
  638.             if (posixFileAttributes != null) {
  639.                 Files.setPosixFilePermissions(parent, posixFileAttributes.permissions());
  640.             }
  641.         }
  642.         return pathCounts;
  643.     }

  644.     /**
  645.      * Delegates to {@link File#deleteOnExit()}.
  646.      *
  647.      * @param path the path to delete.
  648.      * @since 3.13.0
  649.      */
  650.     public static void deleteOnExit(final Path path) {
  651.         Objects.requireNonNull(path).toFile().deleteOnExit();
  652.     }

  653.     /**
  654.      * Compares the files of two Paths to determine if they are equal or not while considering file contents. The comparison includes all files in all
  655.      * subdirectories.
  656.      *
  657.      * @param path1 The first directory.
  658.      * @param path2 The second directory.
  659.      * @return Whether the two directories contain the same files while considering file contents.
  660.      * @throws IOException if an I/O error is thrown by a visitor method.
  661.      */
  662.     public static boolean directoryAndFileContentEquals(final Path path1, final Path path2) throws IOException {
  663.         return directoryAndFileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY, EMPTY_FILE_VISIT_OPTION_ARRAY);
  664.     }

  665.     /**
  666.      * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The comparison includes all files in all
  667.      * subdirectories.
  668.      *
  669.      * @param path1           The first directory.
  670.      * @param path2           The second directory.
  671.      * @param linkOptions     options to follow links.
  672.      * @param openOptions     options to open files.
  673.      * @param fileVisitOption options to configure traversal.
  674.      * @return Whether the two directories contain the same files while considering file contents.
  675.      * @throws IOException if an I/O error is thrown by a visitor method.
  676.      */
  677.     public static boolean directoryAndFileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, final OpenOption[] openOptions,
  678.             final FileVisitOption[] fileVisitOption) throws IOException {
  679.         // First walk both file trees and gather normalized paths.
  680.         if (path1 == null && path2 == null) {
  681.             return true;
  682.         }
  683.         if (path1 == null || path2 == null) {
  684.             return false;
  685.         }
  686.         if (notExists(path1) && notExists(path2)) {
  687.             return true;
  688.         }
  689.         final RelativeSortedPaths relativeSortedPaths = new RelativeSortedPaths(path1, path2, Integer.MAX_VALUE, linkOptions, fileVisitOption);
  690.         // If the normalized path names and counts are not the same, no need to compare contents.
  691.         if (!relativeSortedPaths.equals) {
  692.             return false;
  693.         }
  694.         // Both visitors contain the same normalized paths, we can compare file contents.
  695.         final List<Path> fileList1 = relativeSortedPaths.relativeFileList1;
  696.         final List<Path> fileList2 = relativeSortedPaths.relativeFileList2;
  697.         final boolean sameFileSystem = isSameFileSystem(path1, path2);
  698.         for (final Path path : fileList1) {
  699.             final int binarySearch = sameFileSystem ? Collections.binarySearch(fileList2, path)
  700.                     : Collections.binarySearch(fileList2, path,
  701.                             Comparator.comparing(p -> RelativeSortedPaths.extractKey(p.getFileSystem().getSeparator(), p.toString())));
  702.             if (binarySearch < 0) {
  703.                 throw new IllegalStateException("Unexpected mismatch.");
  704.             }
  705.             if (sameFileSystem && !fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) {
  706.                 return false;
  707.             }
  708.             if (!fileContentEquals(path1.resolve(path.toString()), path2.resolve(path.toString()), linkOptions, openOptions)) {
  709.                 return false;
  710.             }
  711.         }
  712.         return true;
  713.     }

  714.     /**
  715.      * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The comparison includes all files in all
  716.      * subdirectories.
  717.      *
  718.      * @param path1 The first directory.
  719.      * @param path2 The second directory.
  720.      * @return Whether the two directories contain the same files without considering file contents.
  721.      * @throws IOException if an I/O error is thrown by a visitor method.
  722.      */
  723.     public static boolean directoryContentEquals(final Path path1, final Path path2) throws IOException {
  724.         return directoryContentEquals(path1, path2, Integer.MAX_VALUE, EMPTY_LINK_OPTION_ARRAY, EMPTY_FILE_VISIT_OPTION_ARRAY);
  725.     }

  726.     /**
  727.      * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The comparison includes all files in all
  728.      * subdirectories.
  729.      *
  730.      * @param path1            The first directory.
  731.      * @param path2            The second directory.
  732.      * @param maxDepth         See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
  733.      * @param linkOptions      options to follow links.
  734.      * @param fileVisitOptions options to configure the traversal
  735.      * @return Whether the two directories contain the same files without considering file contents.
  736.      * @throws IOException if an I/O error is thrown by a visitor method.
  737.      */
  738.     public static boolean directoryContentEquals(final Path path1, final Path path2, final int maxDepth, final LinkOption[] linkOptions,
  739.             final FileVisitOption[] fileVisitOptions) throws IOException {
  740.         return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, fileVisitOptions).equals;
  741.     }

  742.     private static boolean exists(final Path path, final LinkOption... options) {
  743.         return path != null && (options != null ? Files.exists(path, options) : Files.exists(path));
  744.     }

  745.     /**
  746.      * Compares the file contents of two Paths to determine if they are equal or not.
  747.      * <p>
  748.      * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
  749.      * </p>
  750.      *
  751.      * @param path1 the first file path.
  752.      * @param path2 the second file path.
  753.      * @return true if the content of the streams are equal or they both don't exist, false otherwise.
  754.      * @throws NullPointerException if either input is null.
  755.      * @throws IOException          if an I/O error occurs.
  756.      * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
  757.      */
  758.     public static boolean fileContentEquals(final Path path1, final Path path2) throws IOException {
  759.         return fileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY);
  760.     }

  761.     /**
  762.      * Compares the file contents of two Paths to determine if they are equal or not.
  763.      * <p>
  764.      * File content is accessed through {@link RandomAccessFileMode#create(Path)}.
  765.      * </p>
  766.      *
  767.      * @param path1       the first file path.
  768.      * @param path2       the second file path.
  769.      * @param linkOptions options specifying how files are followed.
  770.      * @param openOptions ignored.
  771.      * @return true if the content of the streams are equal or they both don't exist, false otherwise.
  772.      * @throws NullPointerException if openOptions is null.
  773.      * @throws IOException          if an I/O error occurs.
  774.      * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
  775.      */
  776.     public static boolean fileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, final OpenOption[] openOptions)
  777.             throws IOException {
  778.         if (path1 == null && path2 == null) {
  779.             return true;
  780.         }
  781.         if (path1 == null || path2 == null) {
  782.             return false;
  783.         }
  784.         final Path nPath1 = path1.normalize();
  785.         final Path nPath2 = path2.normalize();
  786.         final boolean path1Exists = exists(nPath1, linkOptions);
  787.         if (path1Exists != exists(nPath2, linkOptions)) {
  788.             return false;
  789.         }
  790.         if (!path1Exists) {
  791.             // Two not existing files are equal?
  792.             // Same as FileUtils
  793.             return true;
  794.         }
  795.         if (Files.isDirectory(nPath1, linkOptions)) {
  796.             // don't compare directory contents.
  797.             throw new IOException("Can't compare directories, only files: " + nPath1);
  798.         }
  799.         if (Files.isDirectory(nPath2, linkOptions)) {
  800.             // don't compare directory contents.
  801.             throw new IOException("Can't compare directories, only files: " + nPath2);
  802.         }
  803.         if (Files.size(nPath1) != Files.size(nPath2)) {
  804.             // lengths differ, cannot be equal
  805.             return false;
  806.         }
  807.         if (isSameFileSystem(path1, path2) && path1.equals(path2)) {
  808.             // same file
  809.             return true;
  810.         }
  811.         // Faster:
  812.         try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(path1.toRealPath(linkOptions));
  813.                 RandomAccessFile raf2 = RandomAccessFileMode.READ_ONLY.create(path2.toRealPath(linkOptions))) {
  814.             return RandomAccessFiles.contentEquals(raf1, raf2);
  815.         } catch (final UnsupportedOperationException e) {
  816.             // Slower:
  817.             // Handle
  818.             // java.lang.UnsupportedOperationException
  819.             // at com.sun.nio.zipfs.ZipPath.toFile(ZipPath.java:656)
  820.             try (InputStream inputStream1 = Files.newInputStream(nPath1, openOptions);
  821.                     InputStream inputStream2 = Files.newInputStream(nPath2, openOptions)) {
  822.                 return IOUtils.contentEquals(inputStream1, inputStream2);
  823.             }
  824.         }
  825.     }

  826.     /**
  827.      * <p>
  828.      * Applies an {@link IOFileFilter} to the provided {@link File} objects. The resulting array is a subset of the original file list that matches the provided
  829.      * filter.
  830.      * </p>
  831.      *
  832.      * <p>
  833.      * The {@link Set} returned by this method is not guaranteed to be thread safe.
  834.      * </p>
  835.      *
  836.      * <pre>
  837.      * Set&lt;File&gt; allFiles = ...
  838.      * Set&lt;File&gt; javaFiles = FileFilterUtils.filterSet(allFiles,
  839.      *     FileFilterUtils.suffixFileFilter(".java"));
  840.      * </pre>
  841.      *
  842.      * @param filter the filter to apply to the set of files.
  843.      * @param paths  the array of files to apply the filter to.
  844.      * @return a subset of {@code files} that is accepted by the file filter.
  845.      * @throws NullPointerException     if the filter is {@code null}
  846.      * @throws IllegalArgumentException if {@code files} contains a {@code null} value.
  847.      * @since 2.9.0
  848.      */
  849.     public static Path[] filter(final PathFilter filter, final Path... paths) {
  850.         Objects.requireNonNull(filter, "filter");
  851.         if (paths == null) {
  852.             return EMPTY_PATH_ARRAY;
  853.         }
  854.         return filterPaths(filter, Stream.of(paths), Collectors.toList()).toArray(EMPTY_PATH_ARRAY);
  855.     }

  856.     private static <R, A> R filterPaths(final PathFilter filter, final Stream<Path> stream, final Collector<? super Path, A, R> collector) {
  857.         Objects.requireNonNull(filter, "filter");
  858.         Objects.requireNonNull(collector, "collector");
  859.         if (stream == null) {
  860.             return Stream.<Path>empty().collect(collector);
  861.         }
  862.         return stream.filter(p -> {
  863.             try {
  864.                 return p != null && filter.accept(p, readBasicFileAttributes(p)) == FileVisitResult.CONTINUE;
  865.             } catch (final IOException e) {
  866.                 return false;
  867.             }
  868.         }).collect(collector);
  869.     }

  870.     /**
  871.      * Reads the access control list from a file attribute view.
  872.      *
  873.      * @param sourcePath the path to the file.
  874.      * @return a file attribute view of the given type, or null if the attribute view type is not available.
  875.      * @throws IOException if an I/O error occurs.
  876.      * @since 2.8.0
  877.      */
  878.     public static List<AclEntry> getAclEntryList(final Path sourcePath) throws IOException {
  879.         final AclFileAttributeView fileAttributeView = getAclFileAttributeView(sourcePath);
  880.         return fileAttributeView == null ? null : fileAttributeView.getAcl();
  881.     }

  882.     /**
  883.      * Shorthand for {@code Files.getFileAttributeView(path, AclFileAttributeView.class)}.
  884.      *
  885.      * @param path    the path to the file.
  886.      * @param options how to handle symbolic links.
  887.      * @return a AclFileAttributeView, or {@code null} if the attribute view type is not available.
  888.      * @since 2.12.0
  889.      */
  890.     public static AclFileAttributeView getAclFileAttributeView(final Path path, final LinkOption... options) {
  891.         return Files.getFileAttributeView(path, AclFileAttributeView.class, options);
  892.     }

  893.     /**
  894.      * Gets the base name (the part up to and not including the last ".") of the last path segment of a file name.
  895.      * <p>
  896.      * Will return the file name itself if it doesn't contain any periods. All leading directories of the {@code file name} parameter are skipped.
  897.      * </p>
  898.      *
  899.      * @return the base name of file name
  900.      * @param path the path of the file to obtain the base name of.
  901.      * @since 2.16.0
  902.      */
  903.     public static String getBaseName(final Path path) {
  904.         if (path == null) {
  905.             return null;
  906.         }
  907.         final Path fileName = path.getFileName();
  908.         return fileName != null ? FilenameUtils.removeExtension(fileName.toString()) : null;
  909.     }

  910.     /**
  911.      * Shorthand for {@code Files.getFileAttributeView(path, DosFileAttributeView.class, options)}.
  912.      *
  913.      * @param path    the path to the file.
  914.      * @param options how to handle symbolic links.
  915.      * @return a DosFileAttributeView, or {@code null} if the attribute view type is not available.
  916.      * @since 2.12.0
  917.      */
  918.     public static DosFileAttributeView getDosFileAttributeView(final Path path, final LinkOption... options) {
  919.         return Files.getFileAttributeView(path, DosFileAttributeView.class, options);
  920.     }

  921.     /**
  922.      * Gets the extension of a Path.
  923.      * <p>
  924.      * This method returns the textual part of the Path after the last period.
  925.      * </p>
  926.      *
  927.      * <pre>
  928.      * foo.txt      --&gt; "txt"
  929.      * a/b/c.jpg    --&gt; "jpg"
  930.      * a/b.txt/c    --&gt; ""
  931.      * a/b/c        --&gt; ""
  932.      * </pre>
  933.      * <p>
  934.      * The output will be the same irrespective of the machine that the code is running on.
  935.      * </p>
  936.      *
  937.      * @param path the path to query.
  938.      * @return the extension of the file or an empty string if none exists or {@code null} if the fileName is {@code null}.
  939.      * @since 2.16.0
  940.      */
  941.     public static String getExtension(final Path path) {
  942.         final String fileName = getFileNameString(path);
  943.         return fileName != null ? FilenameUtils.getExtension(fileName) : null;
  944.     }

  945.     /**
  946.      * Gets the Path's file name and apply the given function if the file name is non-null.
  947.      *
  948.      * @param <R>      The function's result type.
  949.      * @param path     the path to query.
  950.      * @param function function to apply to the file name.
  951.      * @return the Path's file name as a string or null.
  952.      * @see Path#getFileName()
  953.      * @since 2.16.0
  954.      */
  955.     public static <R> R getFileName(final Path path, final Function<Path, R> function) {
  956.         final Path fileName = path != null ? path.getFileName() : null;
  957.         return fileName != null ? function.apply(fileName) : null;
  958.     }

  959.     /**
  960.      * Gets the Path's file name as a string.
  961.      *
  962.      * @param path the path to query.
  963.      * @return the Path's file name as a string or null.
  964.      * @see Path#getFileName()
  965.      * @since 2.16.0
  966.      */
  967.     public static String getFileNameString(final Path path) {
  968.         return getFileName(path, Path::toString);
  969.     }

  970.     /**
  971.      * Gets the file's last modified time or null if the file does not exist.
  972.      * <p>
  973.      * The method provides a workaround for bug <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a> where {@link File#lastModified()}
  974.      * looses milliseconds and always ends in 000. This bug is in OpenJDK 8 and 9, and fixed in 11.
  975.      * </p>
  976.      *
  977.      * @param file the file to query.
  978.      * @return the file's last modified time.
  979.      * @throws IOException Thrown if an I/O error occurs.
  980.      * @since 2.12.0
  981.      */
  982.     public static FileTime getLastModifiedFileTime(final File file) throws IOException {
  983.         return getLastModifiedFileTime(file.toPath(), null, EMPTY_LINK_OPTION_ARRAY);
  984.     }

  985.     /**
  986.      * Gets the file's last modified time or null if the file does not exist.
  987.      *
  988.      * @param path            the file to query.
  989.      * @param defaultIfAbsent Returns this file time of the file does not exist, may be null.
  990.      * @param options         options indicating how symbolic links are handled.
  991.      * @return the file's last modified time.
  992.      * @throws IOException Thrown if an I/O error occurs.
  993.      * @since 2.12.0
  994.      */
  995.     public static FileTime getLastModifiedFileTime(final Path path, final FileTime defaultIfAbsent, final LinkOption... options) throws IOException {
  996.         return Files.exists(path) ? getLastModifiedTime(path, options) : defaultIfAbsent;
  997.     }

  998.     /**
  999.      * Gets the file's last modified time or null if the file does not exist.
  1000.      *
  1001.      * @param path    the file to query.
  1002.      * @param options options indicating how symbolic links are handled.
  1003.      * @return the file's last modified time.
  1004.      * @throws IOException Thrown if an I/O error occurs.
  1005.      * @since 2.12.0
  1006.      */
  1007.     public static FileTime getLastModifiedFileTime(final Path path, final LinkOption... options) throws IOException {
  1008.         return getLastModifiedFileTime(path, null, options);
  1009.     }

  1010.     /**
  1011.      * Gets the file's last modified time or null if the file does not exist.
  1012.      *
  1013.      * @param uri the file to query.
  1014.      * @return the file's last modified time.
  1015.      * @throws IOException Thrown if an I/O error occurs.
  1016.      * @since 2.12.0
  1017.      */
  1018.     public static FileTime getLastModifiedFileTime(final URI uri) throws IOException {
  1019.         return getLastModifiedFileTime(Paths.get(uri), null, EMPTY_LINK_OPTION_ARRAY);
  1020.     }

  1021.     /**
  1022.      * Gets the file's last modified time or null if the file does not exist.
  1023.      *
  1024.      * @param url the file to query.
  1025.      * @return the file's last modified time.
  1026.      * @throws IOException        Thrown if an I/O error occurs.
  1027.      * @throws URISyntaxException if the URL is not formatted strictly according to RFC2396 and cannot be converted to a URI.
  1028.      * @since 2.12.0
  1029.      */
  1030.     public static FileTime getLastModifiedFileTime(final URL url) throws IOException, URISyntaxException {
  1031.         return getLastModifiedFileTime(url.toURI());
  1032.     }

  1033.     private static FileTime getLastModifiedTime(final Path path, final LinkOption... options) throws IOException {
  1034.         return Files.getLastModifiedTime(Objects.requireNonNull(path, "path"), options);
  1035.     }

  1036.     private static Path getParent(final Path path) {
  1037.         return path == null ? null : path.getParent();
  1038.     }

  1039.     /**
  1040.      * Shorthand for {@code Files.getFileAttributeView(path, PosixFileAttributeView.class)}.
  1041.      *
  1042.      * @param path    the path to the file.
  1043.      * @param options how to handle symbolic links.
  1044.      * @return a PosixFileAttributeView, or {@code null} if the attribute view type is not available.
  1045.      * @since 2.12.0
  1046.      */
  1047.     public static PosixFileAttributeView getPosixFileAttributeView(final Path path, final LinkOption... options) {
  1048.         return Files.getFileAttributeView(path, PosixFileAttributeView.class, options);
  1049.     }

  1050.     /**
  1051.      * Gets a {@link Path} representing the system temporary directory.
  1052.      *
  1053.      * @return the system temporary directory.
  1054.      * @since 2.12.0
  1055.      */
  1056.     public static Path getTempDirectory() {
  1057.         return Paths.get(FileUtils.getTempDirectoryPath());
  1058.     }

  1059.     /**
  1060.      * Tests whether the given {@link Path} is a directory or not. Implemented as a null-safe delegate to
  1061.      * {@code Files.isDirectory(Path path, LinkOption... options)}.
  1062.      *
  1063.      * @param path    the path to the file.
  1064.      * @param options options indicating how to handle symbolic links
  1065.      * @return {@code true} if the file is a directory; {@code false} if the path is null, the file does not exist, is not a directory, or it cannot be
  1066.      *         determined if the file is a directory or not.
  1067.      * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
  1068.      *                           checkRead} method is invoked to check read access to the directory.
  1069.      * @since 2.9.0
  1070.      */
  1071.     public static boolean isDirectory(final Path path, final LinkOption... options) {
  1072.         return path != null && Files.isDirectory(path, options);
  1073.     }

  1074.     /**
  1075.      * Tests whether the given file or directory is empty.
  1076.      *
  1077.      * @param path the file or directory to query.
  1078.      * @return whether the file or directory is empty.
  1079.      * @throws IOException if an I/O error occurs.
  1080.      */
  1081.     public static boolean isEmpty(final Path path) throws IOException {
  1082.         return Files.isDirectory(path) ? isEmptyDirectory(path) : isEmptyFile(path);
  1083.     }

  1084.     /**
  1085.      * Tests whether the directory is empty.
  1086.      *
  1087.      * @param directory the directory to query.
  1088.      * @return whether the directory is empty.
  1089.      * @throws NotDirectoryException if the file could not otherwise be opened because it is not a directory <em>(optional specific exception)</em>.
  1090.      * @throws IOException           if an I/O error occurs.
  1091.      * @throws SecurityException     In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
  1092.      *                               checkRead} method is invoked to check read access to the directory.
  1093.      */
  1094.     public static boolean isEmptyDirectory(final Path directory) throws IOException {
  1095.         try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
  1096.             return !directoryStream.iterator().hasNext();
  1097.         }
  1098.     }

  1099.     /**
  1100.      * Tests whether the given file is empty.
  1101.      *
  1102.      * @param file the file to query.
  1103.      * @return whether the file is empty.
  1104.      * @throws IOException       if an I/O error occurs.
  1105.      * @throws SecurityException In the case of the default provider, and a security manager is installed, its {@link SecurityManager#checkRead(String)
  1106.      *                           checkRead} method denies read access to the file.
  1107.      */
  1108.     public static boolean isEmptyFile(final Path file) throws IOException {
  1109.         return Files.size(file) <= 0;
  1110.     }

  1111.     /**
  1112.      * Tests if the given {@link Path} is newer than the given time reference.
  1113.      *
  1114.      * @param file    the {@link Path} to test.
  1115.      * @param czdt    the time reference.
  1116.      * @param options options indicating how to handle symbolic links.
  1117.      * @return true if the {@link Path} exists and has been modified after the given time reference.
  1118.      * @throws IOException          if an I/O error occurs.
  1119.      * @throws NullPointerException if the file is {@code null}.
  1120.      * @since 2.12.0
  1121.      */
  1122.     public static boolean isNewer(final Path file, final ChronoZonedDateTime<?> czdt, final LinkOption... options) throws IOException {
  1123.         Objects.requireNonNull(czdt, "czdt");
  1124.         return isNewer(file, czdt.toInstant(), options);
  1125.     }

  1126.     /**
  1127.      * Tests if the given {@link Path} is newer than the given time reference.
  1128.      *
  1129.      * @param file     the {@link Path} to test.
  1130.      * @param fileTime the time reference.
  1131.      * @param options  options indicating how to handle symbolic links.
  1132.      * @return true if the {@link Path} exists and has been modified after the given time reference.
  1133.      * @throws IOException          if an I/O error occurs.
  1134.      * @throws NullPointerException if the file is {@code null}.
  1135.      * @since 2.12.0
  1136.      */
  1137.     public static boolean isNewer(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
  1138.         if (notExists(file)) {
  1139.             return false;
  1140.         }
  1141.         return compareLastModifiedTimeTo(file, fileTime, options) > 0;
  1142.     }

  1143.     /**
  1144.      * Tests if the given {@link Path} is newer than the given time reference.
  1145.      *
  1146.      * @param file    the {@link Path} to test.
  1147.      * @param instant the time reference.
  1148.      * @param options options indicating how to handle symbolic links.
  1149.      * @return true if the {@link Path} exists and has been modified after the given time reference.
  1150.      * @throws IOException          if an I/O error occurs.
  1151.      * @throws NullPointerException if the file is {@code null}.
  1152.      * @since 2.12.0
  1153.      */
  1154.     public static boolean isNewer(final Path file, final Instant instant, final LinkOption... options) throws IOException {
  1155.         return isNewer(file, FileTime.from(instant), options);
  1156.     }

  1157.     /**
  1158.      * Tests if the given {@link Path} is newer than the given time reference.
  1159.      *
  1160.      * @param file       the {@link Path} to test.
  1161.      * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
  1162.      * @param options    options indicating how to handle symbolic links.
  1163.      * @return true if the {@link Path} exists and has been modified after the given time reference.
  1164.      * @throws IOException          if an I/O error occurs.
  1165.      * @throws NullPointerException if the file is {@code null}.
  1166.      * @since 2.9.0
  1167.      */
  1168.     public static boolean isNewer(final Path file, final long timeMillis, final LinkOption... options) throws IOException {
  1169.         return isNewer(file, FileTime.fromMillis(timeMillis), options);
  1170.     }

  1171.     /**
  1172.      * Tests if the given {@link Path} is newer than the reference {@link Path}.
  1173.      *
  1174.      * @param file      the {@link File} to test.
  1175.      * @param reference the {@link File} of which the modification date is used.
  1176.      * @return true if the {@link File} exists and has been modified more recently than the reference {@link File}.
  1177.      * @throws IOException if an I/O error occurs.
  1178.      * @since 2.12.0
  1179.      */
  1180.     public static boolean isNewer(final Path file, final Path reference) throws IOException {
  1181.         return isNewer(file, getLastModifiedTime(reference));
  1182.     }

  1183.     /**
  1184.      * Tests if the given {@link Path} is older than the given time reference.
  1185.      *
  1186.      * @param file     the {@link Path} to test.
  1187.      * @param fileTime the time reference.
  1188.      * @param options  options indicating how to handle symbolic links.
  1189.      * @return true if the {@link Path} exists and has been modified before the given time reference.
  1190.      * @throws IOException          if an I/O error occurs.
  1191.      * @throws NullPointerException if the file is {@code null}.
  1192.      * @since 2.12.0
  1193.      */
  1194.     public static boolean isOlder(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
  1195.         if (notExists(file)) {
  1196.             return false;
  1197.         }
  1198.         return compareLastModifiedTimeTo(file, fileTime, options) < 0;
  1199.     }

  1200.     /**
  1201.      * Tests if the given {@link Path} is older than the given time reference.
  1202.      *
  1203.      * @param file    the {@link Path} to test.
  1204.      * @param instant the time reference.
  1205.      * @param options options indicating how to handle symbolic links.
  1206.      * @return true if the {@link Path} exists and has been modified before the given time reference.
  1207.      * @throws IOException          if an I/O error occurs.
  1208.      * @throws NullPointerException if the file is {@code null}.
  1209.      * @since 2.12.0
  1210.      */
  1211.     public static boolean isOlder(final Path file, final Instant instant, final LinkOption... options) throws IOException {
  1212.         return isOlder(file, FileTime.from(instant), options);
  1213.     }

  1214.     /**
  1215.      * Tests if the given {@link Path} is older than the given time reference.
  1216.      *
  1217.      * @param file       the {@link Path} to test.
  1218.      * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
  1219.      * @param options    options indicating how to handle symbolic links.
  1220.      * @return true if the {@link Path} exists and has been modified before the given time reference.
  1221.      * @throws IOException          if an I/O error occurs.
  1222.      * @throws NullPointerException if the file is {@code null}.
  1223.      * @since 2.12.0
  1224.      */
  1225.     public static boolean isOlder(final Path file, final long timeMillis, final LinkOption... options) throws IOException {
  1226.         return isOlder(file, FileTime.fromMillis(timeMillis), options);
  1227.     }

  1228.     /**
  1229.      * Tests if the given {@link Path} is older than the reference {@link Path}.
  1230.      *
  1231.      * @param file      the {@link File} to test.
  1232.      * @param reference the {@link File} of which the modification date is used.
  1233.      * @return true if the {@link File} exists and has been modified before than the reference {@link File}.
  1234.      * @throws IOException if an I/O error occurs.
  1235.      * @since 2.12.0
  1236.      */
  1237.     public static boolean isOlder(final Path file, final Path reference) throws IOException {
  1238.         return isOlder(file, getLastModifiedTime(reference));
  1239.     }

  1240.     /**
  1241.      * Tests whether the given path is on a POSIX file system.
  1242.      *
  1243.      * @param test    The Path to test.
  1244.      * @param options options indicating how to handle symbolic links.
  1245.      * @return true if test is on a POSIX file system.
  1246.      * @since 2.12.0
  1247.      */
  1248.     public static boolean isPosix(final Path test, final LinkOption... options) {
  1249.         return exists(test, options) && readPosixFileAttributes(test, options) != null;
  1250.     }

  1251.     /**
  1252.      * Tests whether the given {@link Path} is a regular file or not. Implemented as a null-safe delegate to
  1253.      * {@code Files.isRegularFile(Path path, LinkOption... options)}.
  1254.      *
  1255.      * @param path    the path to the file.
  1256.      * @param options options indicating how to handle symbolic links.
  1257.      * @return {@code true} if the file is a regular file; {@code false} if the path is null, the file does not exist, is not a directory, or it cannot be
  1258.      *         determined if the file is a regular file or not.
  1259.      * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
  1260.      *                           checkRead} method is invoked to check read access to the directory.
  1261.      * @since 2.9.0
  1262.      */
  1263.     public static boolean isRegularFile(final Path path, final LinkOption... options) {
  1264.         return path != null && Files.isRegularFile(path, options);
  1265.     }

  1266.     static boolean isSameFileSystem(final Path path1, final Path path2) {
  1267.         return path1.getFileSystem() == path2.getFileSystem();
  1268.     }

  1269.     /**
  1270.      * Creates a new DirectoryStream for Paths rooted at the given directory.
  1271.      * <p>
  1272.      * If you don't use the try-with-resources construct, then you must call the stream's {@link Stream#close()} method after iteration is complete to free any
  1273.      * resources held for the open directory.
  1274.      * </p>
  1275.      *
  1276.      * @param dir        the path to the directory to stream.
  1277.      * @param pathFilter the directory stream filter.
  1278.      * @return a new instance.
  1279.      * @throws IOException if an I/O error occurs.
  1280.      */
  1281.     public static DirectoryStream<Path> newDirectoryStream(final Path dir, final PathFilter pathFilter) throws IOException {
  1282.         return Files.newDirectoryStream(dir, new DirectoryStreamFilter(pathFilter));
  1283.     }

  1284.     /**
  1285.      * Creates a new OutputStream by opening or creating a file, returning an output stream that may be used to write bytes to the file.
  1286.      *
  1287.      * @param path   the Path.
  1288.      * @param append Whether or not to append.
  1289.      * @return a new OutputStream.
  1290.      * @throws IOException if an I/O error occurs.
  1291.      * @see Files#newOutputStream(Path, OpenOption...)
  1292.      * @since 2.12.0
  1293.      */
  1294.     public static OutputStream newOutputStream(final Path path, final boolean append) throws IOException {
  1295.         return newOutputStream(path, EMPTY_LINK_OPTION_ARRAY, append ? OPEN_OPTIONS_APPEND : OPEN_OPTIONS_TRUNCATE);
  1296.     }

  1297.     static OutputStream newOutputStream(final Path path, final LinkOption[] linkOptions, final OpenOption... openOptions) throws IOException {
  1298.         if (!exists(path, linkOptions)) {
  1299.             createParentDirectories(path, linkOptions != null && linkOptions.length > 0 ? linkOptions[0] : NULL_LINK_OPTION);
  1300.         }
  1301.         final List<OpenOption> list = new ArrayList<>(Arrays.asList(openOptions != null ? openOptions : EMPTY_OPEN_OPTION_ARRAY));
  1302.         list.addAll(Arrays.asList(linkOptions != null ? linkOptions : EMPTY_LINK_OPTION_ARRAY));
  1303.         return Files.newOutputStream(path, list.toArray(EMPTY_OPEN_OPTION_ARRAY));
  1304.     }

  1305.     /**
  1306.      * Copy of the {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
  1307.      *
  1308.      * @return Copy of the {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
  1309.      */
  1310.     public static LinkOption[] noFollowLinkOptionArray() {
  1311.         return NOFOLLOW_LINK_OPTION_ARRAY.clone();
  1312.     }

  1313.     private static boolean notExists(final Path path, final LinkOption... options) {
  1314.         return Files.notExists(Objects.requireNonNull(path, "path"), options);
  1315.     }

  1316.     /**
  1317.      * Returns true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
  1318.      *
  1319.      * @param deleteOptions the array to test
  1320.      * @return true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
  1321.      */
  1322.     private static boolean overrideReadOnly(final DeleteOption... deleteOptions) {
  1323.         if (deleteOptions == null) {
  1324.             return false;
  1325.         }
  1326.         return Stream.of(deleteOptions).anyMatch(e -> e == StandardDeleteOption.OVERRIDE_READ_ONLY);
  1327.     }

  1328.     /**
  1329.      * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read.
  1330.      *
  1331.      * @param <A>     The {@link BasicFileAttributes} type
  1332.      * @param path    The Path to test.
  1333.      * @param type    the {@link Class} of the file attributes required to read.
  1334.      * @param options options indicating how to handle symbolic links.
  1335.      * @return the file attributes or null if the attributes can't be read.
  1336.      * @see Files#readAttributes(Path, Class, LinkOption...)
  1337.      * @since 2.12.0
  1338.      */
  1339.     public static <A extends BasicFileAttributes> A readAttributes(final Path path, final Class<A> type, final LinkOption... options) {
  1340.         try {
  1341.             return path == null ? null : Files.readAttributes(path, type, options);
  1342.         } catch (final UnsupportedOperationException | IOException e) {
  1343.             // For example, on Windows.
  1344.             return null;
  1345.         }
  1346.     }

  1347.     /**
  1348.      * Reads the BasicFileAttributes from the given path.
  1349.      *
  1350.      * @param path the path to read.
  1351.      * @return the path attributes.
  1352.      * @throws IOException if an I/O error occurs.
  1353.      * @since 2.9.0
  1354.      */
  1355.     public static BasicFileAttributes readBasicFileAttributes(final Path path) throws IOException {
  1356.         return Files.readAttributes(path, BasicFileAttributes.class);
  1357.     }

  1358.     /**
  1359.      * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read.
  1360.      *
  1361.      * @param path    the path to read.
  1362.      * @param options options indicating how to handle symbolic links.
  1363.      * @return the path attributes.
  1364.      * @since 2.12.0
  1365.      */
  1366.     public static BasicFileAttributes readBasicFileAttributes(final Path path, final LinkOption... options) {
  1367.         return readAttributes(path, BasicFileAttributes.class, options);
  1368.     }

  1369.     /**
  1370.      * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read.
  1371.      *
  1372.      * @param path the path to read.
  1373.      * @return the path attributes.
  1374.      * @since 2.9.0
  1375.      * @deprecated Use {@link #readBasicFileAttributes(Path, LinkOption...)}.
  1376.      */
  1377.     @Deprecated
  1378.     public static BasicFileAttributes readBasicFileAttributesUnchecked(final Path path) {
  1379.         return readBasicFileAttributes(path, EMPTY_LINK_OPTION_ARRAY);
  1380.     }

  1381.     /**
  1382.      * Reads the DosFileAttributes from the given path. Returns null if the attributes can't be read.
  1383.      *
  1384.      * @param path    the path to read.
  1385.      * @param options options indicating how to handle symbolic links.
  1386.      * @return the path attributes.
  1387.      * @since 2.12.0
  1388.      */
  1389.     public static DosFileAttributes readDosFileAttributes(final Path path, final LinkOption... options) {
  1390.         return readAttributes(path, DosFileAttributes.class, options);
  1391.     }

  1392.     private static Path readIfSymbolicLink(final Path path) throws IOException {
  1393.         return path != null ? Files.isSymbolicLink(path) ? Files.readSymbolicLink(path) : path : null;
  1394.     }

  1395.     /**
  1396.      * Reads the PosixFileAttributes or DosFileAttributes from the given path. Returns null if the attributes can't be read.
  1397.      *
  1398.      * @param path    The Path to read.
  1399.      * @param options options indicating how to handle symbolic links.
  1400.      * @return the file attributes.
  1401.      * @since 2.12.0
  1402.      */
  1403.     public static BasicFileAttributes readOsFileAttributes(final Path path, final LinkOption... options) {
  1404.         final PosixFileAttributes fileAttributes = readPosixFileAttributes(path, options);
  1405.         return fileAttributes != null ? fileAttributes : readDosFileAttributes(path, options);
  1406.     }

  1407.     /**
  1408.      * Reads the PosixFileAttributes from the given path. Returns null instead of throwing {@link UnsupportedOperationException}.
  1409.      *
  1410.      * @param path    The Path to read.
  1411.      * @param options options indicating how to handle symbolic links.
  1412.      * @return the file attributes.
  1413.      * @since 2.12.0
  1414.      */
  1415.     public static PosixFileAttributes readPosixFileAttributes(final Path path, final LinkOption... options) {
  1416.         return readAttributes(path, PosixFileAttributes.class, options);
  1417.     }

  1418.     /**
  1419.      * Reads the file contents at the given path as a String using the Charset.
  1420.      *
  1421.      * @param path    The source path.
  1422.      * @param charset How to convert bytes to a String, null uses the default Charset.
  1423.      * @return the file contents as a new String.
  1424.      * @throws IOException if an I/O error occurs reading from the stream.
  1425.      * @see Files#readAllBytes(Path)
  1426.      * @see Charsets#toCharset(Charset)
  1427.      * @since 2.12.0
  1428.      */
  1429.     public static String readString(final Path path, final Charset charset) throws IOException {
  1430.         return new String(Files.readAllBytes(path), Charsets.toCharset(charset));
  1431.     }

  1432.     /**
  1433.      * Relativizes all files in the given {@code collection} against a {@code parent}.
  1434.      *
  1435.      * @param collection The collection of paths to relativize.
  1436.      * @param parent     relativizes against this parent path.
  1437.      * @param sort       Whether to sort the result.
  1438.      * @param comparator How to sort.
  1439.      * @return A collection of relativized paths, optionally sorted.
  1440.      */
  1441.     static List<Path> relativize(final Collection<Path> collection, final Path parent, final boolean sort, final Comparator<? super Path> comparator) {
  1442.         Stream<Path> stream = collection.stream().map(parent::relativize);
  1443.         if (sort) {
  1444.             stream = comparator == null ? stream.sorted() : stream.sorted(comparator);
  1445.         }
  1446.         return stream.collect(Collectors.toList());
  1447.     }

  1448.     /**
  1449.      * Requires that the given {@link File} exists and throws an {@link IllegalArgumentException} if it doesn't.
  1450.      *
  1451.      * @param file          The {@link File} to check.
  1452.      * @param fileParamName The parameter name to use in the exception message in case of {@code null} input.
  1453.      * @param options       options indicating how to handle symbolic links.
  1454.      * @return the given file.
  1455.      * @throws NullPointerException     if the given {@link File} is {@code null}.
  1456.      * @throws IllegalArgumentException if the given {@link File} does not exist.
  1457.      */
  1458.     private static Path requireExists(final Path file, final String fileParamName, final LinkOption... options) {
  1459.         Objects.requireNonNull(file, fileParamName);
  1460.         if (!exists(file, options)) {
  1461.             throw new IllegalArgumentException("File system element for parameter '" + fileParamName + "' does not exist: '" + file + "'");
  1462.         }
  1463.         return file;
  1464.     }

  1465.     static Path resolve(final Path targetDirectory, final Path otherPath) {
  1466.         final FileSystem fileSystemTarget = targetDirectory.getFileSystem();
  1467.         final FileSystem fileSystemSource = otherPath.getFileSystem();
  1468.         if (fileSystemTarget == fileSystemSource) {
  1469.             return targetDirectory.resolve(otherPath);
  1470.         }
  1471.         final String separatorSource = fileSystemSource.getSeparator();
  1472.         final String separatorTarget = fileSystemTarget.getSeparator();
  1473.         final String otherString = otherPath.toString();
  1474.         return targetDirectory.resolve(Objects.equals(separatorSource, separatorTarget) ? otherString : otherString.replace(separatorSource, separatorTarget));
  1475.     }

  1476.     private static boolean setDosReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
  1477.         final DosFileAttributeView dosFileAttributeView = getDosFileAttributeView(path, linkOptions);
  1478.         if (dosFileAttributeView != null) {
  1479.             dosFileAttributeView.setReadOnly(readOnly);
  1480.             return true;
  1481.         }
  1482.         return false;
  1483.     }

  1484.     /**
  1485.      * Sets the given {@code targetFile}'s last modified time to the value from {@code sourceFile}.
  1486.      *
  1487.      * @param sourceFile The source path to query.
  1488.      * @param targetFile The target path to set.
  1489.      * @throws NullPointerException if sourceFile is {@code null}.
  1490.      * @throws NullPointerException if targetFile is {@code null}.
  1491.      * @throws IOException          if setting the last-modified time failed.
  1492.      * @since 2.12.0
  1493.      */
  1494.     public static void setLastModifiedTime(final Path sourceFile, final Path targetFile) throws IOException {
  1495.         Objects.requireNonNull(sourceFile, "sourceFile");
  1496.         Files.setLastModifiedTime(targetFile, getLastModifiedTime(sourceFile));
  1497.     }

  1498.     /**
  1499.      * To delete a file in POSIX, you need Write and Execute permissions on its parent directory.
  1500.      *
  1501.      * @param parent               The parent path for a file element to delete which needs RW permissions.
  1502.      * @param enableDeleteChildren true to set permissions to delete.
  1503.      * @param linkOptions          options indicating how handle symbolic links.
  1504.      * @return true if the operation was attempted and succeeded, false if parent is null.
  1505.      * @throws IOException if an I/O error occurs.
  1506.      */
  1507.     private static boolean setPosixDeletePermissions(final Path parent, final boolean enableDeleteChildren, final LinkOption... linkOptions)
  1508.             throws IOException {
  1509.         // To delete a file in POSIX, you need write and execute permissions on its parent directory.
  1510.         // @formatter:off
  1511.         return setPosixPermissions(parent, enableDeleteChildren, Arrays.asList(
  1512.             PosixFilePermission.OWNER_WRITE,
  1513.             //PosixFilePermission.GROUP_WRITE,
  1514.             //PosixFilePermission.OTHERS_WRITE,
  1515.             PosixFilePermission.OWNER_EXECUTE
  1516.             //PosixFilePermission.GROUP_EXECUTE,
  1517.             //PosixFilePermission.OTHERS_EXECUTE
  1518.             ), linkOptions);
  1519.         // @formatter:on
  1520.     }

  1521.     /**
  1522.      * Low-level POSIX permission operation to set permissions.
  1523.      * <p>
  1524.      * If the permissions to update are already set, then make no additional calls.
  1525.      * </p>
  1526.      *
  1527.      * @param path              Set this path's permissions.
  1528.      * @param addPermissions    true to add, false to remove.
  1529.      * @param updatePermissions the List of PosixFilePermission to add or remove.
  1530.      * @param linkOptions       options indicating how handle symbolic links.
  1531.      * @return true if the operation was attempted and succeeded, false if parent is null.
  1532.      * @throws IOException if an I/O error occurs.
  1533.      */
  1534.     private static boolean setPosixPermissions(final Path path, final boolean addPermissions, final List<PosixFilePermission> updatePermissions,
  1535.             final LinkOption... linkOptions) throws IOException {
  1536.         if (path != null) {
  1537.             final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
  1538.             final Set<PosixFilePermission> newPermissions = new HashSet<>(permissions);
  1539.             if (addPermissions) {
  1540.                 newPermissions.addAll(updatePermissions);
  1541.             } else {
  1542.                 newPermissions.removeAll(updatePermissions);
  1543.             }
  1544.             if (!newPermissions.equals(permissions)) {
  1545.                 Files.setPosixFilePermissions(path, newPermissions);
  1546.             }
  1547.             return true;
  1548.         }
  1549.         return false;
  1550.     }

  1551.     private static void setPosixReadOnlyFile(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
  1552.         // Not Windows 10
  1553.         final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
  1554.         // @formatter:off
  1555.         final List<PosixFilePermission> readPermissions = Arrays.asList(
  1556.                 PosixFilePermission.OWNER_READ
  1557.                 //PosixFilePermission.GROUP_READ,
  1558.                 //PosixFilePermission.OTHERS_READ
  1559.             );
  1560.         final List<PosixFilePermission> writePermissions = Arrays.asList(
  1561.                 PosixFilePermission.OWNER_WRITE
  1562.                 //PosixFilePermission.GROUP_WRITE,
  1563.                 //PosixFilePermission.OTHERS_WRITE
  1564.             );
  1565.         // @formatter:on
  1566.         if (readOnly) {
  1567.             // RO: We can read, we cannot write.
  1568.             permissions.addAll(readPermissions);
  1569.             permissions.removeAll(writePermissions);
  1570.         } else {
  1571.             // Not RO: We can read, we can write.
  1572.             permissions.addAll(readPermissions);
  1573.             permissions.addAll(writePermissions);
  1574.         }
  1575.         Files.setPosixFilePermissions(path, permissions);
  1576.     }

  1577.     /**
  1578.      * Sets the given Path to the {@code readOnly} value.
  1579.      * <p>
  1580.      * This behavior is OS dependent.
  1581.      * </p>
  1582.      *
  1583.      * @param path        The path to set.
  1584.      * @param readOnly    true for read-only, false for not read-only.
  1585.      * @param linkOptions options indicating how to handle symbolic links.
  1586.      * @return The given path.
  1587.      * @throws IOException if an I/O error occurs.
  1588.      * @since 2.8.0
  1589.      */
  1590.     public static Path setReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
  1591.         try {
  1592.             // Windows is simplest
  1593.             if (setDosReadOnly(path, readOnly, linkOptions)) {
  1594.                 return path;
  1595.             }
  1596.         } catch (final IOException ignored) {
  1597.             // Retry with POSIX below.
  1598.         }
  1599.         final Path parent = getParent(path);
  1600.         if (!isPosix(parent, linkOptions)) { // Test parent because we may not the permissions to test the file.
  1601.             throw new IOException(String.format("DOS or POSIX file operations not available for '%s', linkOptions %s", path, Arrays.toString(linkOptions)));
  1602.         }
  1603.         // POSIX
  1604.         if (readOnly) {
  1605.             // RO
  1606.             // File, then parent dir (if any).
  1607.             setPosixReadOnlyFile(path, readOnly, linkOptions);
  1608.             setPosixDeletePermissions(parent, false, linkOptions);
  1609.         } else {
  1610.             // RE
  1611.             // Parent dir (if any), then file.
  1612.             setPosixDeletePermissions(parent, true, linkOptions);
  1613.         }
  1614.         return path;
  1615.     }

  1616.     /**
  1617.      * Returns the size of the given file or directory. If the provided {@link Path} is a regular file, then the file's size is returned. If the argument is a
  1618.      * directory, then the size of the directory is calculated recursively.
  1619.      * <p>
  1620.      * Note that overflow is not detected, and the return value may be negative if overflow occurs. See {@link #sizeOfAsBigInteger(Path)} for an alternative
  1621.      * method that does not overflow.
  1622.      * </p>
  1623.      *
  1624.      * @param path the regular file or directory to return the size of, must not be {@code null}.
  1625.      * @return the length of the file, or recursive size of the directory, in bytes.
  1626.      * @throws NullPointerException     if the file is {@code null}.
  1627.      * @throws IllegalArgumentException if the file does not exist.
  1628.      * @throws IOException              if an I/O error occurs.
  1629.      * @since 2.12.0
  1630.      */
  1631.     public static long sizeOf(final Path path) throws IOException {
  1632.         requireExists(path, "path");
  1633.         return Files.isDirectory(path) ? sizeOfDirectory(path) : Files.size(path);
  1634.     }

  1635.     /**
  1636.      * Returns the size of the given file or directory. If the provided {@link Path} is a regular file, then the file's size is returned. If the argument is a
  1637.      * directory, then the size of the directory is calculated recursively.
  1638.      *
  1639.      * @param path the regular file or directory to return the size of (must not be {@code null}).
  1640.      * @return the length of the file, or recursive size of the directory, provided (in bytes).
  1641.      * @throws NullPointerException     if the file is {@code null}.
  1642.      * @throws IllegalArgumentException if the file does not exist.
  1643.      * @throws IOException              if an I/O error occurs.
  1644.      * @since 2.12.0
  1645.      */
  1646.     public static BigInteger sizeOfAsBigInteger(final Path path) throws IOException {
  1647.         requireExists(path, "path");
  1648.         return Files.isDirectory(path) ? sizeOfDirectoryAsBigInteger(path) : BigInteger.valueOf(Files.size(path));
  1649.     }

  1650.     /**
  1651.      * Counts the size of a directory recursively (sum of the size of all files).
  1652.      * <p>
  1653.      * Note that overflow is not detected, and the return value may be negative if overflow occurs. See {@link #sizeOfDirectoryAsBigInteger(Path)} for an
  1654.      * alternative method that does not overflow.
  1655.      * </p>
  1656.      *
  1657.      * @param directory directory to inspect, must not be {@code null}.
  1658.      * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total is greater than {@link Long#MAX_VALUE}.
  1659.      * @throws NullPointerException if the directory is {@code null}.
  1660.      * @throws IOException          if an I/O error occurs.
  1661.      * @since 2.12.0
  1662.      */
  1663.     public static long sizeOfDirectory(final Path directory) throws IOException {
  1664.         return countDirectory(directory).getByteCounter().getLong();
  1665.     }

  1666.     /**
  1667.      * Counts the size of a directory recursively (sum of the size of all files).
  1668.      *
  1669.      * @param directory directory to inspect, must not be {@code null}.
  1670.      * @return size of directory in bytes, 0 if directory is security restricted.
  1671.      * @throws NullPointerException if the directory is {@code null}.
  1672.      * @throws IOException          if an I/O error occurs.
  1673.      * @since 2.12.0
  1674.      */
  1675.     public static BigInteger sizeOfDirectoryAsBigInteger(final Path directory) throws IOException {
  1676.         return countDirectoryAsBigInteger(directory).getByteCounter().getBigInteger();
  1677.     }

  1678.     private static Path stripTrailingSeparator(final Path dir) {
  1679.         final String separator = dir.getFileSystem().getSeparator();
  1680.         final String fileName = getFileNameString(dir);
  1681.         return fileName != null && fileName.endsWith(separator) ? dir.resolveSibling(fileName.substring(0, fileName.length() - 1)) : dir;
  1682.     }

  1683.     /**
  1684.      * Converts an array of {@link FileVisitOption} to a {@link Set}.
  1685.      *
  1686.      * @param fileVisitOptions input array.
  1687.      * @return a new Set.
  1688.      */
  1689.     static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... fileVisitOptions) {
  1690.         return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class) : Stream.of(fileVisitOptions).collect(Collectors.toSet());
  1691.     }

  1692.     private static <T> List<T> toList(final Iterable<T> iterable) {
  1693.         return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
  1694.     }

  1695.     private static List<Path> toSortedList(final Iterable<Path> rootDirectories) {
  1696.         final List<Path> list = toList(rootDirectories);
  1697.         Collections.sort(list);
  1698.         return list;
  1699.     }

  1700.     /**
  1701.      * Implements behavior similar to the Unix "touch" utility. Creates a new file with size 0, or, if the file exists, just updates the file's modified time.
  1702.      * this method creates parent directories if they do not exist.
  1703.      *
  1704.      * @param file the file to touch.
  1705.      * @return The given file.
  1706.      * @throws NullPointerException if the parameter is {@code null}.
  1707.      * @throws IOException          if setting the last-modified time failed or an I/O problem occurs.\
  1708.      * @since 2.12.0
  1709.      */
  1710.     public static Path touch(final Path file) throws IOException {
  1711.         Objects.requireNonNull(file, "file");
  1712.         if (!Files.exists(file)) {
  1713.             createParentDirectories(file);
  1714.             Files.createFile(file);
  1715.         } else {
  1716.             FileTimes.setLastModifiedTime(file);
  1717.         }
  1718.         return file;
  1719.     }

  1720.     /**
  1721.      * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
  1722.      *
  1723.      * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
  1724.      *
  1725.      * @param visitor   See {@link Files#walkFileTree(Path,FileVisitor)}.
  1726.      * @param directory See {@link Files#walkFileTree(Path,FileVisitor)}.
  1727.      * @param <T>       See {@link Files#walkFileTree(Path,FileVisitor)}.
  1728.      * @return the given visitor.
  1729.      * @throws NoSuchFileException  if the directory does not exist.
  1730.      * @throws IOException          if an I/O error is thrown by a visitor method.
  1731.      * @throws NullPointerException if the directory is {@code null}.
  1732.      */
  1733.     public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path directory) throws IOException {
  1734.         Files.walkFileTree(directory, visitor);
  1735.         return visitor;
  1736.     }

  1737.     /**
  1738.      * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
  1739.      *
  1740.      * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
  1741.      *
  1742.      * @param start    See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
  1743.      * @param options  See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
  1744.      * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
  1745.      * @param visitor  See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
  1746.      * @param <T>      See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
  1747.      * @return the given visitor.
  1748.      * @throws IOException if an I/O error is thrown by a visitor method.
  1749.      */
  1750.     public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path start, final Set<FileVisitOption> options,
  1751.             final int maxDepth) throws IOException {
  1752.         Files.walkFileTree(start, options, maxDepth, visitor);
  1753.         return visitor;
  1754.     }

  1755.     /**
  1756.      * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
  1757.      *
  1758.      * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
  1759.      *
  1760.      * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
  1761.      * @param first   See {@link Paths#get(String,String[])}.
  1762.      * @param more    See {@link Paths#get(String,String[])}.
  1763.      * @param <T>     See {@link Files#walkFileTree(Path,FileVisitor)}.
  1764.      * @return the given visitor.
  1765.      * @throws IOException if an I/O error is thrown by a visitor method.
  1766.      */
  1767.     public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final String first, final String... more) throws IOException {
  1768.         return visitFileTree(visitor, Paths.get(first, more));
  1769.     }

  1770.     /**
  1771.      * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
  1772.      *
  1773.      * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
  1774.      *
  1775.      * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
  1776.      * @param uri     See {@link Paths#get(URI)}.
  1777.      * @param <T>     See {@link Files#walkFileTree(Path,FileVisitor)}.
  1778.      * @return the given visitor.
  1779.      * @throws IOException if an I/O error is thrown by a visitor method.
  1780.      */
  1781.     public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final URI uri) throws IOException {
  1782.         return visitFileTree(visitor, Paths.get(uri));
  1783.     }

  1784.     /**
  1785.      * Waits for the file system to detect a file's presence, with a timeout.
  1786.      * <p>
  1787.      * This method repeatedly tests {@link Files#exists(Path,LinkOption...)} until it returns true up to the maximum time given.
  1788.      * </p>
  1789.      *
  1790.      * @param file    the file to check, must not be {@code null}.
  1791.      * @param timeout the maximum time to wait.
  1792.      * @param options options indicating how to handle symbolic links.
  1793.      * @return true if file exists.
  1794.      * @throws NullPointerException if the file is {@code null}.
  1795.      * @since 2.12.0
  1796.      */
  1797.     public static boolean waitFor(final Path file, final Duration timeout, final LinkOption... options) {
  1798.         Objects.requireNonNull(file, "file");
  1799.         final Instant finishInstant = Instant.now().plus(timeout);
  1800.         boolean interrupted = false;
  1801.         final long minSleepMillis = 100;
  1802.         try {
  1803.             while (!exists(file, options)) {
  1804.                 final Instant now = Instant.now();
  1805.                 if (now.isAfter(finishInstant)) {
  1806.                     return false;
  1807.                 }
  1808.                 try {
  1809.                     ThreadUtils.sleep(Duration.ofMillis(Math.min(minSleepMillis, finishInstant.minusMillis(now.toEpochMilli()).toEpochMilli())));
  1810.                 } catch (final InterruptedException ignore) {
  1811.                     interrupted = true;
  1812.                 } catch (final Exception ex) {
  1813.                     break;
  1814.                 }
  1815.             }
  1816.         } finally {
  1817.             if (interrupted) {
  1818.                 Thread.currentThread().interrupt();
  1819.             }
  1820.         }
  1821.         return exists(file, options);
  1822.     }

  1823.     /**
  1824.      * Returns a stream of filtered paths.
  1825.      * <p>
  1826.      * The returned {@link Stream} may wrap one or more {@link DirectoryStream}s. When you require timely disposal of file system resources, use a
  1827.      * {@code try}-with-resources block to ensure invocation of the stream's {@link Stream#close()} method after the stream operations are completed. Calling a
  1828.      * closed stream causes a {@link IllegalStateException}.
  1829.      * </p>
  1830.      *
  1831.      * @param start          the start path
  1832.      * @param pathFilter     the path filter
  1833.      * @param maxDepth       the maximum depth of directories to walk.
  1834.      * @param readAttributes whether to call the filters with file attributes (false passes null).
  1835.      * @param options        the options to configure the walk.
  1836.      * @return a filtered stream of paths.
  1837.      * @throws IOException if an I/O error is thrown when accessing the starting file.
  1838.      * @since 2.9.0
  1839.      */
  1840.     @SuppressWarnings("resource") // Caller closes
  1841.     public static Stream<Path> walk(final Path start, final PathFilter pathFilter, final int maxDepth, final boolean readAttributes,
  1842.             final FileVisitOption... options) throws IOException {
  1843.         return Files.walk(start, maxDepth, options)
  1844.                 .filter(path -> pathFilter.accept(path, readAttributes ? readBasicFileAttributesUnchecked(path) : null) == FileVisitResult.CONTINUE);
  1845.     }

  1846.     private static <R> R withPosixFileAttributes(final Path path, final LinkOption[] linkOptions, final boolean overrideReadOnly,
  1847.             final IOFunction<PosixFileAttributes, R> function) throws IOException {
  1848.         final PosixFileAttributes posixFileAttributes = overrideReadOnly ? readPosixFileAttributes(path, linkOptions) : null;
  1849.         try {
  1850.             return function.apply(posixFileAttributes);
  1851.         } finally {
  1852.             if (posixFileAttributes != null && path != null && Files.exists(path, linkOptions)) {
  1853.                 Files.setPosixFilePermissions(path, posixFileAttributes.permissions());
  1854.             }
  1855.         }
  1856.     }

  1857.     /**
  1858.      * Writes the given character sequence to a file at the given path.
  1859.      *
  1860.      * @param path         The target file.
  1861.      * @param charSequence The character sequence text.
  1862.      * @param charset      The Charset to encode the text.
  1863.      * @param openOptions  options How to open the file.
  1864.      * @return The given path.
  1865.      * @throws IOException          if an I/O error occurs writing to or creating the file.
  1866.      * @throws NullPointerException if either {@code path} or {@code charSequence} is {@code null}.
  1867.      * @since 2.12.0
  1868.      */
  1869.     public static Path writeString(final Path path, final CharSequence charSequence, final Charset charset, final OpenOption... openOptions)
  1870.             throws IOException {
  1871.         // Check the text is not null before opening file.
  1872.         Objects.requireNonNull(path, "path");
  1873.         Objects.requireNonNull(charSequence, "charSequence");
  1874.         Files.write(path, String.valueOf(charSequence).getBytes(Charsets.toCharset(charset)), openOptions);
  1875.         return path;
  1876.     }

  1877.     /**
  1878.      * Prevents instantiation.
  1879.      */
  1880.     private PathUtils() {
  1881.         // do not instantiate.
  1882.     }
  1883. }