This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git


The following commit(s) were added to refs/heads/master by this push:
     new d6c14418 2.15.0 breaks backwards compatibility in 
PathUtils.fileContentEquals for the Zip file system
d6c14418 is described below

commit d6c14418c44fea1b783ec32ea683a78cacd6deec
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Fri Nov 10 10:08:41 2023 -0500

    2.15.0 breaks backwards compatibility in PathUtils.fileContentEquals for
    the Zip file system
---
 src/changes/changes.xml                            |   3 +
 .../java/org/apache/commons/io/file/PathUtils.java | 418 ++++++++++-----------
 .../io/file/PathUtilsContentEqualsTest.java        |  18 +
 .../commons/io/test-same-size-diff-contents.zip    | Bin 0 -> 554 bytes
 4 files changed, 229 insertions(+), 210 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 97d6583d..aaf21c71 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -117,6 +117,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="fix" due-to="Gary Gregory">
         Pick up Maven Moditect plugin version from parent POM.
       </action>
+      <action issue="IO-821" dev="ggregory" type="fix" due-to="Frédéric 
Hannes, Gary Gregory">
+        2.15.0 breaks backwards compatibility in PathUtils.fileContentEquals 
for the Zip file system.
+      </action>
       <!-- ADD -->
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Add org.apache.commons.io.channels.FileChannels.
diff --git a/src/main/java/org/apache/commons/io/file/PathUtils.java 
b/src/main/java/org/apache/commons/io/file/PathUtils.java
index d85fdb67..6e3e4b1a 100644
--- a/src/main/java/org/apache/commons/io/file/PathUtils.java
+++ b/src/main/java/org/apache/commons/io/file/PathUtils.java
@@ -70,6 +70,7 @@ import java.util.stream.Stream;
 import org.apache.commons.io.Charsets;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.RandomAccessFileMode;
 import org.apache.commons.io.RandomAccessFiles;
 import org.apache.commons.io.ThreadUtils;
@@ -87,8 +88,7 @@ import org.apache.commons.io.function.IOSupplier;
 public final class PathUtils {
 
     /**
-     * Private worker/holder that computes and tracks relative path names and 
their equality. We reuse the sorted relative
-     * lists when comparing directories.
+     * Private worker/holder that computes and tracks relative path names and 
their equality. We reuse the sorted relative lists when comparing directories.
      */
     private static final class RelativeSortedPaths {
 
@@ -101,15 +101,15 @@ public final class PathUtils {
         /**
          * Constructs and initializes a new instance by accumulating directory 
and file info.
          *
-         * @param dir1 First directory to compare.
-         * @param dir2 Seconds directory to compare.
-         * @param maxDepth See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
-         * @param linkOptions Options indicating how symbolic links are 
handled.
+         * @param dir1             First directory to compare.
+         * @param dir2             Seconds directory to compare.
+         * @param maxDepth         See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+         * @param linkOptions      Options indicating how symbolic links are 
handled.
          * @param fileVisitOptions See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
          * @throws IOException if an I/O error is thrown by a visitor method.
          */
         private RelativeSortedPaths(final Path dir1, final Path dir2, final 
int maxDepth, final LinkOption[] linkOptions,
-            final FileVisitOption[] fileVisitOptions) throws IOException {
+                final FileVisitOption[] fileVisitOptions) throws IOException {
             final List<Path> tmpRelativeDirList1;
             final List<Path> tmpRelativeDirList2;
             List<Path> tmpRelativeFileList1 = null;
@@ -148,9 +148,9 @@ public final class PathUtils {
         }
     }
 
-    private static final OpenOption[] OPEN_OPTIONS_TRUNCATE = 
{StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+    private static final OpenOption[] OPEN_OPTIONS_TRUNCATE = { 
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING };
 
-    private static final OpenOption[] OPEN_OPTIONS_APPEND = 
{StandardOpenOption.CREATE, StandardOpenOption.APPEND};
+    private static final OpenOption[] OPEN_OPTIONS_APPEND = { 
StandardOpenOption.CREATE, StandardOpenOption.APPEND };
 
     /**
      * Empty {@link CopyOption} array.
@@ -190,7 +190,7 @@ public final class PathUtils {
      * @deprecated Use {@link #noFollowLinkOptionArray()}.
      */
     @Deprecated
-    public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = 
{LinkOption.NOFOLLOW_LINKS};
+    public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = { 
LinkOption.NOFOLLOW_LINKS };
 
     /**
      * A LinkOption used to follow link in this class, the inverse of {@link 
LinkOption#NOFOLLOW_LINKS}.
@@ -214,8 +214,8 @@ public final class PathUtils {
     /**
      * Accumulates file tree information in a {@link AccumulatorPathVisitor}.
      *
-     * @param directory The directory to accumulate information.
-     * @param maxDepth See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+     * @param directory        The directory to accumulate information.
+     * @param maxDepth         See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
      * @param fileVisitOptions See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
      * @throws IOException if an I/O error is thrown by a visitor method.
      * @return file tree information.
@@ -238,7 +238,7 @@ public final class PathUtils {
     /**
      * Cleans a directory including subdirectories without deleting 
directories.
      *
-     * @param directory directory to clean.
+     * @param directory     directory to clean.
      * @param deleteOptions How to handle deletion.
      * @return The visitation path counters.
      * @throws IOException if an I/O error is thrown by a visitor method.
@@ -251,11 +251,11 @@ public final class PathUtils {
     /**
      * Compares the given {@link Path}'s last modified time to the given file 
time.
      *
-     * @param file the {@link Path} to test.
+     * @param file     the {@link Path} to test.
      * @param fileTime the time reference.
-     * @param options options indicating how to handle symbolic links.
+     * @param options  options indicating how to handle symbolic links.
      * @return See {@link FileTime#compareTo(FileTime)}
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException          if an I/O error occurs.
      * @throws NullPointerException if the file is {@code null}.
      */
     private static int compareLastModifiedTimeTo(final Path file, final 
FileTime fileTime, final LinkOption... options) throws IOException {
@@ -265,8 +265,8 @@ public final class PathUtils {
     /**
      * Copies the InputStream from the supplier with {@link 
Files#copy(InputStream, Path, CopyOption...)}.
      *
-     * @param in Supplies the InputStream.
-     * @param target See {@link Files#copy(InputStream, Path, CopyOption...)}.
+     * @param in          Supplies the InputStream.
+     * @param target      See {@link Files#copy(InputStream, Path, 
CopyOption...)}.
      * @param copyOptions See {@link Files#copy(InputStream, Path, 
CopyOption...)}.
      * @return See {@link Files#copy(InputStream, Path, CopyOption...)}
      * @throws IOException See {@link Files#copy(InputStream, Path, 
CopyOption...)}
@@ -283,21 +283,21 @@ public final class PathUtils {
      *
      * @param sourceDirectory The source directory.
      * @param targetDirectory The target directory.
-     * @param copyOptions Specifies how the copying should be done.
+     * @param copyOptions     Specifies how the copying should be done.
      * @return The visitation path counters.
      * @throws IOException if an I/O error is thrown by a visitor method.
      */
     public static PathCounters copyDirectory(final Path sourceDirectory, final 
Path targetDirectory, final CopyOption... copyOptions) throws IOException {
         final Path absoluteSource = sourceDirectory.toAbsolutePath();
         return visitFileTree(new 
CopyDirectoryVisitor(Counters.longPathCounters(), absoluteSource, 
targetDirectory, copyOptions), absoluteSource)
-            .getPathCounters();
+                .getPathCounters();
     }
 
     /**
      * Copies a URL to a directory.
      *
-     * @param sourceFile The source URL.
-     * @param targetFile The target file.
+     * @param sourceFile  The source URL.
+     * @param targetFile  The target file.
      * @param copyOptions Specifies how the copying should be done.
      * @return The target file
      * @throws IOException if an I/O error occurs.
@@ -311,9 +311,9 @@ public final class PathUtils {
     /**
      * Copies a file to a directory.
      *
-     * @param sourceFile The source file.
+     * @param sourceFile      The source file.
      * @param targetDirectory The target directory.
-     * @param copyOptions Specifies how the copying should be done.
+     * @param copyOptions     Specifies how the copying should be done.
      * @return The target file
      * @throws IOException if an I/O error occurs.
      * @see Files#copy(Path, Path, CopyOption...)
@@ -325,9 +325,9 @@ public final class PathUtils {
     /**
      * Copies a URL to a directory.
      *
-     * @param sourceFile The source URL.
+     * @param sourceFile      The source URL.
      * @param targetDirectory The target directory.
-     * @param copyOptions Specifies how the copying should be done.
+     * @param copyOptions     Specifies how the copying should be done.
      * @return The target file
      * @throws IOException if an I/O error occurs.
      * @see Files#copy(InputStream, Path, CopyOption...)
@@ -367,7 +367,7 @@ public final class PathUtils {
      * If the parent directory already exists, then return it.
      * </p>
      *
-     * @param path The path to a file (or directory).
+     * @param path  The path to a file (or directory).
      * @param attrs An optional list of file attributes to set atomically when 
creating the directories.
      * @return The Path for the {@code path}'s parent directory or null if the 
given path has no parent.
      * @throws IOException if an I/O error occurs.
@@ -383,15 +383,14 @@ public final class PathUtils {
      * If the parent directory already exists, then return it.
      * </p>
      *
-     * @param path The path to a file (or directory).
+     * @param path       The path to a file (or directory).
      * @param linkOption A {@link LinkOption} or null.
-     * @param attrs An optional list of file attributes to set atomically when 
creating the directories.
+     * @param attrs      An optional list of file attributes to set atomically 
when creating the directories.
      * @return The Path for the {@code path}'s parent directory or null if the 
given path has no parent.
      * @throws IOException if an I/O error occurs.
      * @since 2.12.0
      */
-    public static Path createParentDirectories(final Path path, final 
LinkOption linkOption,
-            final FileAttribute<?>... attrs) throws IOException {
+    public static Path createParentDirectories(final Path path, final 
LinkOption linkOption, final FileAttribute<?>... attrs) throws IOException {
         Path parent = getParent(path);
         parent = linkOption == LinkOption.NOFOLLOW_LINKS ? parent : 
readIfSymbolicLink(parent);
         if (parent == null) {
@@ -425,7 +424,7 @@ public final class PathUtils {
      * @param path file or directory to delete, must not be {@code null}
      * @return The visitor used to delete the given directory.
      * @throws NullPointerException if the directory is {@code null}
-     * @throws IOException if an I/O error is thrown by a visitor method or if 
an I/O error occurs.
+     * @throws IOException          if an I/O error is thrown by a visitor 
method or if an I/O error occurs.
      */
     public static PathCounters delete(final Path path) throws IOException {
         return delete(path, EMPTY_DELETE_OPTION_ARRAY);
@@ -441,11 +440,11 @@ public final class PathUtils {
      * <li>You get exceptions when a file or directory cannot be deleted; 
{@link java.io.File#delete()} returns a boolean.
      * </ul>
      *
-     * @param path file or directory to delete, must not be {@code null}
+     * @param path          file or directory to delete, must not be {@code 
null}
      * @param deleteOptions How to handle deletion.
      * @return The visitor used to delete the given directory.
      * @throws NullPointerException if the directory is {@code null}
-     * @throws IOException if an I/O error is thrown by a visitor method or if 
an I/O error occurs.
+     * @throws IOException          if an I/O error is thrown by a visitor 
method or if an I/O error occurs.
      * @since 2.8.0
      */
     public static PathCounters delete(final Path path, final DeleteOption... 
deleteOptions) throws IOException {
@@ -463,12 +462,12 @@ public final class PathUtils {
      * <li>You get exceptions when a file or directory cannot be deleted; 
{@link java.io.File#delete()} returns a boolean.
      * </ul>
      *
-     * @param path file or directory to delete, must not be {@code null}
-     * @param linkOptions How to handle symbolic links.
+     * @param path          file or directory to delete, must not be {@code 
null}
+     * @param linkOptions   How to handle symbolic links.
      * @param deleteOptions How to handle deletion.
      * @return The visitor used to delete the given directory.
      * @throws NullPointerException if the directory is {@code null}
-     * @throws IOException if an I/O error is thrown by a visitor method or if 
an I/O error occurs.
+     * @throws IOException          if an I/O error is thrown by a visitor 
method or if an I/O error occurs.
      * @since 2.9.0
      */
     public static PathCounters delete(final Path path, final LinkOption[] 
linkOptions, final DeleteOption... deleteOptions) throws IOException {
@@ -490,7 +489,7 @@ public final class PathUtils {
     /**
      * Deletes a directory including subdirectories.
      *
-     * @param directory directory to delete.
+     * @param directory     directory to delete.
      * @param deleteOptions How to handle deletion.
      * @return The visitor used to delete the given directory.
      * @throws IOException if an I/O error is thrown by a visitor method.
@@ -500,14 +499,14 @@ public final class PathUtils {
         final LinkOption[] linkOptions = PathUtils.noFollowLinkOptionArray();
         // POSIX ops will noop on non-POSIX.
         return withPosixFileAttributes(getParent(directory), linkOptions, 
overrideReadOnly(deleteOptions),
-            pfa -> visitFileTree(new 
DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), 
directory).getPathCounters());
+                pfa -> visitFileTree(new 
DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), 
directory).getPathCounters());
     }
 
     /**
      * Deletes a directory including subdirectories.
      *
-     * @param directory directory to delete.
-     * @param linkOptions How to handle symbolic links.
+     * @param directory     directory to delete.
+     * @param linkOptions   How to handle symbolic links.
      * @param deleteOptions How to handle deletion.
      * @return The visitor used to delete the given directory.
      * @throws IOException if an I/O error is thrown by a visitor method.
@@ -522,7 +521,7 @@ public final class PathUtils {
      *
      * @param file The file to delete.
      * @return A visitor with path counts set to 1 file, 0 directories, and 
the size of the deleted file.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException         if an I/O error occurs.
      * @throws NoSuchFileException if the file is a directory.
      */
     public static PathCounters deleteFile(final Path file) throws IOException {
@@ -532,10 +531,10 @@ public final class PathUtils {
     /**
      * Deletes the given file.
      *
-     * @param file The file to delete.
+     * @param file          The file to delete.
      * @param deleteOptions How to handle deletion.
      * @return A visitor with path counts set to 1 file, 0 directories, and 
the size of the deleted file.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException         if an I/O error occurs.
      * @throws NoSuchFileException if the file is a directory.
      * @since 2.8.0
      */
@@ -547,16 +546,16 @@ public final class PathUtils {
     /**
      * Deletes the given file.
      *
-     * @param file The file to delete.
-     * @param linkOptions How to handle symbolic links.
+     * @param file          The file to delete.
+     * @param linkOptions   How to handle symbolic links.
      * @param deleteOptions How to handle deletion.
      * @return A visitor with path counts set to 1 file, 0 directories, and 
the size of the deleted file.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException         if an I/O error occurs.
      * @throws NoSuchFileException if the file is a directory.
      * @since 2.9.0
      */
     public static PathCounters deleteFile(final Path file, final LinkOption[] 
linkOptions, final DeleteOption... deleteOptions)
-        throws NoSuchFileException, IOException {
+            throws NoSuchFileException, IOException {
         //
         // TODO Needs clean up
         //
@@ -608,8 +607,8 @@ public final class PathUtils {
     }
 
     /**
-     * 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 subdirectories.
+     * 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
+     * subdirectories.
      *
      * @param path1 The first directory.
      * @param path2 The second directory.
@@ -621,19 +620,19 @@ public final class PathUtils {
     }
 
     /**
-     * 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 subdirectories.
+     * 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
+     * subdirectories.
      *
-     * @param path1 The first directory.
-     * @param path2 The second directory.
-     * @param linkOptions options to follow links.
-     * @param openOptions options to open files.
+     * @param path1           The first directory.
+     * @param path2           The second directory.
+     * @param linkOptions     options to follow links.
+     * @param openOptions     options to open files.
      * @param fileVisitOption options to configure traversal.
      * @return Whether the two directories contain the same files while 
considering file contents.
      * @throws IOException if an I/O error is thrown by a visitor method.
      */
     public static boolean directoryAndFileContentEquals(final Path path1, 
final Path path2, final LinkOption[] linkOptions, final OpenOption[] 
openOptions,
-        final FileVisitOption[] fileVisitOption) throws IOException {
+            final FileVisitOption[] fileVisitOption) throws IOException {
         // First walk both file trees and gather normalized paths.
         if (path1 == null && path2 == null) {
             return true;
@@ -665,8 +664,8 @@ public final class PathUtils {
     }
 
     /**
-     * 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 subdirectories.
+     * 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
+     * subdirectories.
      *
      * @param path1 The first directory.
      * @param path2 The second directory.
@@ -678,19 +677,19 @@ public final class PathUtils {
     }
 
     /**
-     * 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 subdirectories.
+     * 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
+     * subdirectories.
      *
-     * @param path1 The first directory.
-     * @param path2 The second directory.
-     * @param maxDepth See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
-     * @param linkOptions options to follow links.
+     * @param path1            The first directory.
+     * @param path2            The second directory.
+     * @param maxDepth         See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+     * @param linkOptions      options to follow links.
      * @param fileVisitOptions options to configure the traversal
      * @return Whether the two directories contain the same files without 
considering file contents.
      * @throws IOException if an I/O error is thrown by a visitor method.
      */
     public static boolean directoryContentEquals(final Path path1, final Path 
path2, final int maxDepth, final LinkOption[] linkOptions,
-        final FileVisitOption[] fileVisitOptions) throws IOException {
+            final FileVisitOption[] fileVisitOptions) throws IOException {
         return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, 
fileVisitOptions).equals;
     }
 
@@ -709,7 +708,7 @@ public final class PathUtils {
      * @param path2 the second stream.
      * @return true if the content of the streams are equal or they both don't 
exist, false otherwise.
      * @throws NullPointerException if either input is null.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException          if an I/O error occurs.
      * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, 
java.io.File)
      */
     public static boolean fileContentEquals(final Path path1, final Path 
path2) throws IOException {
@@ -722,13 +721,13 @@ public final class PathUtils {
      * File content is accessed through {@link 
RandomAccessFileMode#create(Path)}.
      * </p>
      *
-     * @param path1 the first stream.
-     * @param path2 the second stream.
+     * @param path1       the first stream.
+     * @param path2       the second stream.
      * @param linkOptions options specifying how files are followed.
      * @param openOptions ignored.
      * @return true if the content of the streams are equal or they both don't 
exist, false otherwise.
      * @throws NullPointerException if openOptions is null.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException          if an I/O error occurs.
      * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, 
java.io.File)
      */
     public static boolean fileContentEquals(final Path path1, final Path 
path2, final LinkOption[] linkOptions, final OpenOption[] openOptions)
@@ -766,16 +765,26 @@ public final class PathUtils {
             // same file
             return true;
         }
+        // Faster:
         try (RandomAccessFile raf1 = 
RandomAccessFileMode.READ_ONLY.create(path1.toRealPath(linkOptions));
                 RandomAccessFile raf2 = 
RandomAccessFileMode.READ_ONLY.create(path2.toRealPath(linkOptions))) {
             return RandomAccessFiles.contentEquals(raf1, raf2);
+        } catch (UnsupportedOperationException e) {
+            // Slower:
+            // Handle
+            // java.lang.UnsupportedOperationException
+            // at com.sun.nio.zipfs.ZipPath.toFile(ZipPath.java:656)
+            try (InputStream inputStream1 = Files.newInputStream(nPath1, 
openOptions);
+                    InputStream inputStream2 = Files.newInputStream(nPath2, 
openOptions)) {
+                return IOUtils.contentEquals(inputStream1, inputStream2);
+            }
         }
     }
 
     /**
      * <p>
-     * 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 filter.
+     * 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
+     * filter.
      * </p>
      *
      * <p>
@@ -789,10 +798,10 @@ public final class PathUtils {
      * </pre>
      *
      * @param filter the filter to apply to the set of files.
-     * @param paths the array of files to apply the filter to.
+     * @param paths  the array of files to apply the filter to.
      *
      * @return a subset of {@code files} that is accepted by the file filter.
-     * @throws NullPointerException if the filter is {@code null}
+     * @throws NullPointerException     if the filter is {@code null}
      * @throws IllegalArgumentException if {@code files} contains a {@code 
null} value.
      *
      * @since 2.9.0
@@ -836,7 +845,7 @@ public final class PathUtils {
     /**
      * Shorthand for {@code Files.getFileAttributeView(path, 
AclFileAttributeView.class)}.
      *
-     * @param path the path to the file.
+     * @param path    the path to the file.
      * @param options how to handle symbolic links.
      * @return a AclFileAttributeView, or {@code null} if the attribute view 
type is not available.
      * @since 2.12.0
@@ -848,7 +857,7 @@ public final class PathUtils {
     /**
      * Shorthand for {@code Files.getFileAttributeView(path, 
DosFileAttributeView.class)}.
      *
-     * @param path the path to the file.
+     * @param path    the path to the file.
      * @param options how to handle symbolic links.
      * @return a DosFileAttributeView, or {@code null} if the attribute view 
type is not available.
      * @since 2.12.0
@@ -860,9 +869,8 @@ public final class PathUtils {
     /**
      * Gets the file's last modified time or null if the file does not exist.
      * <p>
-     * The method provides a workaround for bug <a 
href="https://bugs.openjdk.java.net/browse/JDK-8177809";>JDK-8177809</a>
-     * where {@link File#lastModified()} looses milliseconds and always ends 
in 000. This bug is in OpenJDK 8 and 9, and
-     * fixed in 11.
+     * The method provides a workaround for bug <a 
href="https://bugs.openjdk.java.net/browse/JDK-8177809";>JDK-8177809</a> where 
{@link File#lastModified()}
+     * looses milliseconds and always ends in 000. This bug is in OpenJDK 8 
and 9, and fixed in 11.
      * </p>
      *
      * @param file the file to query.
@@ -877,9 +885,9 @@ public final class PathUtils {
     /**
      * Gets the file's last modified time or null if the file does not exist.
      *
-     * @param path the file to query.
+     * @param path            the file to query.
      * @param defaultIfAbsent Returns this file time of the file does not 
exist, may be null.
-     * @param options options indicating how symbolic links are handled.
+     * @param options         options indicating how symbolic links are 
handled.
      * @return the file's last modified time.
      * @throws IOException Thrown if an I/O error occurs.
      * @since 2.12.0
@@ -891,7 +899,7 @@ public final class PathUtils {
     /**
      * Gets the file's last modified time or null if the file does not exist.
      *
-     * @param path the file to query.
+     * @param path    the file to query.
      * @param options options indicating how symbolic links are handled.
      * @return the file's last modified time.
      * @throws IOException Thrown if an I/O error occurs.
@@ -918,9 +926,8 @@ public final class PathUtils {
      *
      * @param url the file to query.
      * @return the file's last modified time.
-     * @throws IOException Thrown if an I/O error occurs.
-     * @throws URISyntaxException if the URL is not formatted strictly 
according to RFC2396 and cannot be converted to a
-     *         URI.
+     * @throws IOException        Thrown if an I/O error occurs.
+     * @throws URISyntaxException if the URL is not formatted strictly 
according to RFC2396 and cannot be converted to a URI.
      * @since 2.12.0
      */
     public static FileTime getLastModifiedFileTime(final URL url) throws 
IOException, URISyntaxException {
@@ -938,7 +945,7 @@ public final class PathUtils {
     /**
      * Shorthand for {@code Files.getFileAttributeView(path, 
PosixFileAttributeView.class)}.
      *
-     * @param path the path to the file.
+     * @param path    the path to the file.
      * @param options how to handle symbolic links.
      * @return a PosixFileAttributeView, or {@code null} if the attribute view 
type is not available.
      * @since 2.12.0
@@ -961,12 +968,12 @@ public final class PathUtils {
      * Tests whether the given {@link Path} is a directory or not. Implemented 
as a null-safe delegate to
      * {@code Files.isDirectory(Path path, LinkOption... options)}.
      *
-     * @param path the path to the file.
+     * @param path    the path to the file.
      * @param options options indicating how to handle symbolic links
-     * @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 determined if the file is a 
directory or not.
-     * @throws SecurityException In the case of the default provider, and a 
security manager is installed, the
-     *         {@link SecurityManager#checkRead(String) checkRead} method is 
invoked to check read access to the directory.
+     * @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
+     *         determined if the file is a directory or not.
+     * @throws SecurityException In the case of the default provider, and a 
security manager is installed, the {@link SecurityManager#checkRead(String)
+     *                           checkRead} method is invoked to check read 
access to the directory.
      * @since 2.9.0
      */
     public static boolean isDirectory(final Path path, final LinkOption... 
options) {
@@ -989,11 +996,10 @@ public final class PathUtils {
      *
      * @param directory the directory to query.
      * @return whether the directory is empty.
-     * @throws NotDirectoryException if the file could not otherwise be opened 
because it is not a directory <i>(optional
-     *         specific exception)</i>.
-     * @throws IOException if an I/O error occurs.
-     * @throws SecurityException In the case of the default provider, and a 
security manager is installed, the
-     *         {@link SecurityManager#checkRead(String) checkRead} method is 
invoked to check read access to the directory.
+     * @throws NotDirectoryException if the file could not otherwise be opened 
because it is not a directory <i>(optional specific exception)</i>.
+     * @throws IOException           if an I/O error occurs.
+     * @throws SecurityException     In the case of the default provider, and 
a security manager is installed, the {@link SecurityManager#checkRead(String)
+     *                               checkRead} method is invoked to check 
read access to the directory.
      */
     public static boolean isEmptyDirectory(final Path directory) throws 
IOException {
         try (DirectoryStream<Path> directoryStream = 
Files.newDirectoryStream(directory)) {
@@ -1006,9 +1012,9 @@ public final class PathUtils {
      *
      * @param file the file to query.
      * @return whether the file is empty.
-     * @throws IOException if an I/O error occurs.
-     * @throws SecurityException In the case of the default provider, and a 
security manager is installed, its
-     *         {@link SecurityManager#checkRead(String) checkRead} method 
denies read access to the file.
+     * @throws IOException       if an I/O error occurs.
+     * @throws SecurityException In the case of the default provider, and a 
security manager is installed, its {@link SecurityManager#checkRead(String)
+     *                           checkRead} method denies read access to the 
file.
      */
     public static boolean isEmptyFile(final Path file) throws IOException {
         return Files.size(file) <= 0;
@@ -1017,11 +1023,11 @@ public final class PathUtils {
     /**
      * Tests if the given {@link Path} is newer than the given time reference.
      *
-     * @param file the {@link Path} to test.
-     * @param czdt the time reference.
+     * @param file    the {@link Path} to test.
+     * @param czdt    the time reference.
      * @param options options indicating how to handle symbolic links.
      * @return true if the {@link Path} exists and has been modified after the 
given time reference.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException          if an I/O error occurs.
      * @throws NullPointerException if the file is {@code null}.
      * @since 2.12.0
      */
@@ -1033,11 +1039,11 @@ public final class PathUtils {
     /**
      * Tests if the given {@link Path} is newer than the given time reference.
      *
-     * @param file the {@link Path} to test.
+     * @param file     the {@link Path} to test.
      * @param fileTime the time reference.
-     * @param options options indicating how to handle symbolic links.
+     * @param options  options indicating how to handle symbolic links.
      * @return true if the {@link Path} exists and has been modified after the 
given time reference.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException          if an I/O error occurs.
      * @throws NullPointerException if the file is {@code null}.
      * @since 2.12.0
      */
@@ -1051,11 +1057,11 @@ public final class PathUtils {
     /**
      * Tests if the given {@link Path} is newer than the given time reference.
      *
-     * @param file the {@link Path} to test.
+     * @param file    the {@link Path} to test.
      * @param instant the time reference.
      * @param options options indicating how to handle symbolic links.
      * @return true if the {@link Path} exists and has been modified after the 
given time reference.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException          if an I/O error occurs.
      * @throws NullPointerException if the file is {@code null}.
      * @since 2.12.0
      */
@@ -1066,11 +1072,11 @@ public final class PathUtils {
     /**
      * Tests if the given {@link Path} is newer than the given time reference.
      *
-     * @param file the {@link Path} to test.
+     * @param file       the {@link Path} to test.
      * @param timeMillis the time reference measured in milliseconds since the 
epoch (00:00:00 GMT, January 1, 1970)
-     * @param options options indicating how to handle symbolic links.
+     * @param options    options indicating how to handle symbolic links.
      * @return true if the {@link Path} exists and has been modified after the 
given time reference.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException          if an I/O error occurs.
      * @throws NullPointerException if the file is {@code null}.
      * @since 2.9.0
      */
@@ -1081,7 +1087,7 @@ public final class PathUtils {
     /**
      * Tests if the given {@link Path} is newer than the reference {@link 
Path}.
      *
-     * @param file the {@link File} to test.
+     * @param file      the {@link File} to test.
      * @param reference the {@link File} of which the modification date is 
used.
      * @return true if the {@link File} exists and has been modified more 
recently than the reference {@link File}.
      * @throws IOException if an I/O error occurs.
@@ -1094,11 +1100,11 @@ public final class PathUtils {
     /**
      * Tests if the given {@link Path} is older than the given time reference.
      *
-     * @param file the {@link Path} to test.
+     * @param file     the {@link Path} to test.
      * @param fileTime the time reference.
-     * @param options options indicating how to handle symbolic links.
+     * @param options  options indicating how to handle symbolic links.
      * @return true if the {@link Path} exists and has been modified before 
the given time reference.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException          if an I/O error occurs.
      * @throws NullPointerException if the file is {@code null}.
      * @since 2.12.0
      */
@@ -1112,11 +1118,11 @@ public final class PathUtils {
     /**
      * Tests if the given {@link Path} is older than the given time reference.
      *
-     * @param file the {@link Path} to test.
+     * @param file    the {@link Path} to test.
      * @param instant the time reference.
      * @param options options indicating how to handle symbolic links.
      * @return true if the {@link Path} exists and has been modified before 
the given time reference.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException          if an I/O error occurs.
      * @throws NullPointerException if the file is {@code null}.
      * @since 2.12.0
      */
@@ -1127,11 +1133,11 @@ public final class PathUtils {
     /**
      * Tests if the given {@link Path} is older than the given time reference.
      *
-     * @param file the {@link Path} to test.
+     * @param file       the {@link Path} to test.
      * @param timeMillis the time reference measured in milliseconds since the 
epoch (00:00:00 GMT, January 1, 1970)
-     * @param options options indicating how to handle symbolic links.
+     * @param options    options indicating how to handle symbolic links.
      * @return true if the {@link Path} exists and has been modified before 
the given time reference.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException          if an I/O error occurs.
      * @throws NullPointerException if the file is {@code null}.
      * @since 2.12.0
      */
@@ -1142,7 +1148,7 @@ public final class PathUtils {
     /**
      * Tests if the given {@link Path} is older than the reference {@link 
Path}.
      *
-     * @param file the {@link File} to test.
+     * @param file      the {@link File} to test.
      * @param reference the {@link File} of which the modification date is 
used.
      * @return true if the {@link File} exists and has been modified before 
than the reference {@link File}.
      * @throws IOException if an I/O error occurs.
@@ -1155,7 +1161,7 @@ public final class PathUtils {
     /**
      * Tests whether the given path is on a POSIX file system.
      *
-     * @param test The Path to test.
+     * @param test    The Path to test.
      * @param options options indicating how to handle symbolic links.
      * @return true if test is on a POSIX file system.
      * @since 2.12.0
@@ -1168,12 +1174,12 @@ public final class PathUtils {
      * Tests whether the given {@link Path} is a regular file or not. 
Implemented as a null-safe delegate to
      * {@code Files.isRegularFile(Path path, LinkOption... options)}.
      *
-     * @param path the path to the file.
+     * @param path    the path to the file.
      * @param options options indicating how to handle symbolic links.
-     * @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 determined if the file is a 
regular file or not.
-     * @throws SecurityException In the case of the default provider, and a 
security manager is installed, the
-     *         {@link SecurityManager#checkRead(String) checkRead} method is 
invoked to check read access to the directory.
+     * @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
+     *         determined if the file is a regular file or not.
+     * @throws SecurityException In the case of the default provider, and a 
security manager is installed, the {@link SecurityManager#checkRead(String)
+     *                           checkRead} method is invoked to check read 
access to the directory.
      * @since 2.9.0
      */
     public static boolean isRegularFile(final Path path, final LinkOption... 
options) {
@@ -1197,10 +1203,9 @@ public final class PathUtils {
     }
 
     /**
-     * Creates a new OutputStream by opening or creating a file, returning an 
output stream that may be used to write bytes
-     * to the file.
+     * Creates a new OutputStream by opening or creating a file, returning an 
output stream that may be used to write bytes to the file.
      *
-     * @param path the Path.
+     * @param path   the Path.
      * @param append Whether or not to append.
      * @return a new OutputStream.
      * @throws IOException if an I/O error occurs.
@@ -1249,9 +1254,9 @@ public final class PathUtils {
     /**
      * Reads the BasicFileAttributes from the given path. Returns null if the 
attributes can't be read.
      *
-     * @param <A> The {@link BasicFileAttributes} type
-     * @param path The Path to test.
-     * @param type the {@link Class} of the file attributes required to read.
+     * @param <A>     The {@link BasicFileAttributes} type
+     * @param path    The Path to test.
+     * @param type    the {@link Class} of the file attributes required to 
read.
      * @param options options indicating how to handle symbolic links.
      * @return the file attributes or null if the attributes can't be read.
      * @see Files#readAttributes(Path, Class, LinkOption...)
@@ -1279,10 +1284,9 @@ public final class PathUtils {
     }
 
     /**
-     * Reads the BasicFileAttributes from the given path. Returns null if the 
attributes
-     * can't be read.
+     * Reads the BasicFileAttributes from the given path. Returns null if the 
attributes can't be read.
      *
-     * @param path the path to read.
+     * @param path    the path to read.
      * @param options options indicating how to handle symbolic links.
      * @return the path attributes.
      * @since 2.12.0
@@ -1292,8 +1296,7 @@ public final class PathUtils {
     }
 
     /**
-     * Reads the BasicFileAttributes from the given path. Returns null if the 
attributes
-     * can't be read.
+     * Reads the BasicFileAttributes from the given path. Returns null if the 
attributes can't be read.
      *
      * @param path the path to read.
      * @return the path attributes.
@@ -1306,10 +1309,9 @@ public final class PathUtils {
     }
 
     /**
-     * Reads the DosFileAttributes from the given path. Returns null if the 
attributes
-     * can't be read.
+     * Reads the DosFileAttributes from the given path. Returns null if the 
attributes can't be read.
      *
-     * @param path the path to read.
+     * @param path    the path to read.
      * @param options options indicating how to handle symbolic links.
      * @return the path attributes.
      * @since 2.12.0
@@ -1323,10 +1325,9 @@ public final class PathUtils {
     }
 
     /**
-     * Reads the PosixFileAttributes or DosFileAttributes from the given path. 
Returns null if the attributes
-     * can't be read.
+     * Reads the PosixFileAttributes or DosFileAttributes from the given path. 
Returns null if the attributes can't be read.
      *
-     * @param path The Path to read.
+     * @param path    The Path to read.
      * @param options options indicating how to handle symbolic links.
      * @return the file attributes.
      * @since 2.12.0
@@ -1337,10 +1338,9 @@ public final class PathUtils {
     }
 
     /**
-     * Reads the PosixFileAttributes from the given path. Returns null instead 
of throwing
-     * {@link UnsupportedOperationException}.
+     * Reads the PosixFileAttributes from the given path. Returns null instead 
of throwing {@link UnsupportedOperationException}.
      *
-     * @param path The Path to read.
+     * @param path    The Path to read.
      * @param options options indicating how to handle symbolic links.
      * @return the file attributes.
      * @since 2.12.0
@@ -1352,7 +1352,7 @@ public final class PathUtils {
     /**
      * Reads the given path as a String.
      *
-     * @param path The source path.
+     * @param path    The source path.
      * @param charset How to convert bytes to a String, null uses the default 
Charset.
      * @return a new String.
      * @throws IOException if an I/O error occurs reading from the stream.
@@ -1367,8 +1367,8 @@ public final class PathUtils {
      * Relativizes all files in the given {@code collection} against a {@code 
parent}.
      *
      * @param collection The collection of paths to relativize.
-     * @param parent relativizes against this parent path.
-     * @param sort Whether to sort the result.
+     * @param parent     relativizes against this parent path.
+     * @param sort       Whether to sort the result.
      * @param comparator How to sort.
      * @return A collection of relativized paths, optionally sorted.
      */
@@ -1383,11 +1383,11 @@ public final class PathUtils {
     /**
      * Requires that the given {@link File} exists and throws an {@link 
IllegalArgumentException} if it doesn't.
      *
-     * @param file The {@link File} to check.
+     * @param file          The {@link File} to check.
      * @param fileParamName The parameter name to use in the exception message 
in case of {@code null} input.
-     * @param options options indicating how to handle symbolic links.
+     * @param options       options indicating how to handle symbolic links.
      * @return the given file.
-     * @throws NullPointerException if the given {@link File} is {@code null}.
+     * @throws NullPointerException     if the given {@link File} is {@code 
null}.
      * @throws IllegalArgumentException if the given {@link File} does not 
exist.
      */
     private static Path requireExists(final Path file, final String 
fileParamName, final LinkOption... options) {
@@ -1414,7 +1414,7 @@ public final class PathUtils {
      * @param targetFile The target path to set.
      * @throws NullPointerException if sourceFile is {@code null}.
      * @throws NullPointerException if targetFile is {@code null}.
-     * @throws IOException if setting the last-modified time failed.
+     * @throws IOException          if setting the last-modified time failed.
      * @since 2.12.0
      */
     public static void setLastModifiedTime(final Path sourceFile, final Path 
targetFile) throws IOException {
@@ -1425,14 +1425,14 @@ public final class PathUtils {
     /**
      * To delete a file in POSIX, you need Write and Execute permissions on 
its parent directory.
      *
-     * @param parent The parent path for a file element to delete which needs 
RW permissions.
+     * @param parent               The parent path for a file element to 
delete which needs RW permissions.
      * @param enableDeleteChildren true to set permissions to delete.
-     * @param linkOptions options indicating how handle symbolic links.
+     * @param linkOptions          options indicating how handle symbolic 
links.
      * @return true if the operation was attempted and succeeded, false if 
parent is null.
      * @throws IOException if an I/O error occurs.
      */
     private static boolean setPosixDeletePermissions(final Path parent, final 
boolean enableDeleteChildren, final LinkOption... linkOptions)
-        throws IOException {
+            throws IOException {
         // To delete a file in POSIX, you need write and execute permissions 
on its parent directory.
         // @formatter:off
         return setPosixPermissions(parent, enableDeleteChildren, Arrays.asList(
@@ -1449,15 +1449,15 @@ public final class PathUtils {
     /**
      * Low-level POSIX permission operation to set permissions.
      *
-     * @param path Set this path's permissions.
-     * @param addPermissions true to add, false to remove.
+     * @param path              Set this path's permissions.
+     * @param addPermissions    true to add, false to remove.
      * @param updatePermissions the List of PosixFilePermission to add or 
remove.
-     * @param linkOptions options indicating how handle symbolic links.
+     * @param linkOptions       options indicating how handle symbolic links.
      * @return true if the operation was attempted and succeeded, false if 
parent is null.
      * @throws IOException if an I/O error occurs.
      */
     private static boolean setPosixPermissions(final Path path, final boolean 
addPermissions, final List<PosixFilePermission> updatePermissions,
-        final LinkOption... linkOptions) throws IOException {
+            final LinkOption... linkOptions) throws IOException {
         if (path != null) {
             final Set<PosixFilePermission> permissions = 
Files.getPosixFilePermissions(path, linkOptions);
             if (addPermissions) {
@@ -1504,8 +1504,8 @@ public final class PathUtils {
      * This behavior is OS dependent.
      * </p>
      *
-     * @param path The path to set.
-     * @param readOnly true for read-only, false for not read-only.
+     * @param path        The path to set.
+     * @param readOnly    true for read-only, false for not read-only.
      * @param linkOptions options indicating how to handle symbolic links.
      * @return The given path.
      * @throws IOException if an I/O error occurs.
@@ -1539,18 +1539,18 @@ public final class PathUtils {
     }
 
     /**
-     * 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 directory, then the size of the 
directory is calculated recursively.
+     * 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
+     * directory, then the size of the directory is calculated recursively.
      * <p>
-     * Note that overflow is not detected, and the return value may be 
negative if overflow occurs. See
-     * {@link #sizeOfAsBigInteger(Path)} for an alternative method that does 
not overflow.
+     * Note that overflow is not detected, and the return value may be 
negative if overflow occurs. See {@link #sizeOfAsBigInteger(Path)} for an 
alternative
+     * method that does not overflow.
      * </p>
      *
      * @param path the regular file or directory to return the size of, must 
not be {@code null}.
      * @return the length of the file, or recursive size of the directory, in 
bytes.
-     * @throws NullPointerException if the file is {@code null}.
+     * @throws NullPointerException     if the file is {@code null}.
      * @throws IllegalArgumentException if the file does not exist.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException              if an I/O error occurs.
      * @since 2.12.0
      */
     public static long sizeOf(final Path path) throws IOException {
@@ -1559,14 +1559,14 @@ public final class PathUtils {
     }
 
     /**
-     * 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 directory, then the size of the 
directory is calculated recursively.
+     * 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
+     * directory, then the size of the directory is calculated recursively.
      *
      * @param path the regular file or directory to return the size of (must 
not be {@code null}).
      * @return the length of the file, or recursive size of the directory, 
provided (in bytes).
-     * @throws NullPointerException if the file is {@code null}.
+     * @throws NullPointerException     if the file is {@code null}.
      * @throws IllegalArgumentException if the file does not exist.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException              if an I/O error occurs.
      * @since 2.12.0
      */
     public static BigInteger sizeOfAsBigInteger(final Path path) throws 
IOException {
@@ -1577,15 +1577,14 @@ public final class PathUtils {
     /**
      * Counts the size of a directory recursively (sum of the size of all 
files).
      * <p>
-     * Note that overflow is not detected, and the return value may be 
negative if overflow occurs. See
-     * {@link #sizeOfDirectoryAsBigInteger(Path)} for an alternative method 
that does not overflow.
+     * Note that overflow is not detected, and the return value may be 
negative if overflow occurs. See {@link #sizeOfDirectoryAsBigInteger(Path)} for 
an
+     * alternative method that does not overflow.
      * </p>
      *
      * @param directory directory to inspect, must not be {@code null}.
-     * @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}.
+     * @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}.
      * @throws NullPointerException if the directory is {@code null}.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException          if an I/O error occurs.
      * @since 2.12.0
      */
     public static long sizeOfDirectory(final Path directory) throws 
IOException {
@@ -1598,7 +1597,7 @@ public final class PathUtils {
      * @param directory directory to inspect, must not be {@code null}.
      * @return size of directory in bytes, 0 if directory is security 
restricted.
      * @throws NullPointerException if the directory is {@code null}.
-     * @throws IOException if an I/O error occurs.
+     * @throws IOException          if an I/O error occurs.
      * @since 2.12.0
      */
     public static BigInteger sizeOfDirectoryAsBigInteger(final Path directory) 
throws IOException {
@@ -1616,13 +1615,13 @@ public final class PathUtils {
     }
 
     /**
-     * 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. this method creates parent 
directories if they do not exist.
+     * 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.
+     * this method creates parent directories if they do not exist.
      *
      * @param file the file to touch.
      * @return The given file.
      * @throws NullPointerException if the parameter is {@code null}.
-     * @throws IOException if setting the last-modified time failed or an I/O 
problem occurs.\
+     * @throws IOException          if setting the last-modified time failed 
or an I/O problem occurs.\
      * @since 2.12.0
      */
     public static Path touch(final Path file) throws IOException {
@@ -1641,13 +1640,13 @@ public final class PathUtils {
      *
      * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the 
given path.
      *
-     * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
+     * @param visitor   See {@link Files#walkFileTree(Path,FileVisitor)}.
      * @param directory See {@link Files#walkFileTree(Path,FileVisitor)}.
-     * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
+     * @param <T>       See {@link Files#walkFileTree(Path,FileVisitor)}.
      * @return the given visitor.
      *
-     * @throws NoSuchFileException if the directory does not exist.
-     * @throws IOException if an I/O error is thrown by a visitor method.
+     * @throws NoSuchFileException  if the directory does not exist.
+     * @throws IOException          if an I/O error is thrown by a visitor 
method.
      * @throws NullPointerException if the directory is {@code null}.
      */
     public static <T extends FileVisitor<? super Path>> T visitFileTree(final 
T visitor, final Path directory) throws IOException {
@@ -1660,17 +1659,17 @@ public final class PathUtils {
      *
      * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the 
given path.
      *
-     * @param start See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
-     * @param options See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
+     * @param start    See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+     * @param options  See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
      * @param maxDepth See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
-     * @param visitor See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
-     * @param <T> See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
+     * @param visitor  See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+     * @param <T>      See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
      * @return the given visitor.
      *
      * @throws IOException if an I/O error is thrown by a visitor method.
      */
     public static <T extends FileVisitor<? super Path>> T visitFileTree(final 
T visitor, final Path start, final Set<FileVisitOption> options,
-        final int maxDepth) throws IOException {
+            final int maxDepth) throws IOException {
         Files.walkFileTree(start, options, maxDepth, visitor);
         return visitor;
     }
@@ -1681,9 +1680,9 @@ public final class PathUtils {
      * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the 
given path.
      *
      * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
-     * @param first See {@link Paths#get(String,String[])}.
-     * @param more See {@link Paths#get(String,String[])}.
-     * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
+     * @param first   See {@link Paths#get(String,String[])}.
+     * @param more    See {@link Paths#get(String,String[])}.
+     * @param <T>     See {@link Files#walkFileTree(Path,FileVisitor)}.
      * @return the given visitor.
      *
      * @throws IOException if an I/O error is thrown by a visitor method.
@@ -1698,8 +1697,8 @@ public final class PathUtils {
      * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the 
given path.
      *
      * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
-     * @param uri See {@link Paths#get(URI)}.
-     * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
+     * @param uri     See {@link Paths#get(URI)}.
+     * @param <T>     See {@link Files#walkFileTree(Path,FileVisitor)}.
      * @return the given visitor.
      *
      * @throws IOException if an I/O error is thrown by a visitor method.
@@ -1711,11 +1710,10 @@ public final class PathUtils {
     /**
      * Waits for the file system to propagate a file creation, with a timeout.
      * <p>
-     * This method repeatedly tests {@link Files#exists(Path,LinkOption...)} 
until it returns true up to the maximum time
-     * given.
+     * This method repeatedly tests {@link Files#exists(Path,LinkOption...)} 
until it returns true up to the maximum time given.
      * </p>
      *
-     * @param file the file to check, must not be {@code null}.
+     * @param file    the file to check, must not be {@code null}.
      * @param timeout the maximum time to wait.
      * @param options options indicating how to handle symbolic links.
      * @return true if file exists.
@@ -1757,23 +1755,23 @@ public final class PathUtils {
      * closed stream causes a {@link IllegalStateException}.
      * </p>
      *
-     * @param start the start path
-     * @param pathFilter the path filter
-     * @param maxDepth the maximum depth of directories to walk.
+     * @param start          the start path
+     * @param pathFilter     the path filter
+     * @param maxDepth       the maximum depth of directories to walk.
      * @param readAttributes whether to call the filters with file attributes 
(false passes null).
-     * @param options the options to configure the walk.
+     * @param options        the options to configure the walk.
      * @return a filtered stream of paths.
      * @throws IOException if an I/O error is thrown when accessing the 
starting file.
      * @since 2.9.0
      */
     public static Stream<Path> walk(final Path start, final PathFilter 
pathFilter, final int maxDepth, final boolean readAttributes,
-        final FileVisitOption... options) throws IOException {
+            final FileVisitOption... options) throws IOException {
         return Files.walk(start, maxDepth, options)
-            .filter(path -> pathFilter.accept(path, readAttributes ? 
readBasicFileAttributesUnchecked(path) : null) == FileVisitResult.CONTINUE);
+                .filter(path -> pathFilter.accept(path, readAttributes ? 
readBasicFileAttributesUnchecked(path) : null) == FileVisitResult.CONTINUE);
     }
 
     private static <R> R withPosixFileAttributes(final Path path, final 
LinkOption[] linkOptions, final boolean overrideReadOnly,
-        final IOFunction<PosixFileAttributes, R> function) throws IOException {
+            final IOFunction<PosixFileAttributes, R> function) throws 
IOException {
         final PosixFileAttributes posixFileAttributes = overrideReadOnly ? 
readPosixFileAttributes(path, linkOptions) : null;
         try {
             return function.apply(posixFileAttributes);
@@ -1787,17 +1785,17 @@ public final class PathUtils {
     /**
      * Writes the given character sequence to a file at the given path.
      *
-     * @param path The target file.
+     * @param path         The target file.
      * @param charSequence The character sequence text.
-     * @param charset The Charset to encode the text.
-     * @param openOptions options How to open the file.
+     * @param charset      The Charset to encode the text.
+     * @param openOptions  options How to open the file.
      * @return The given path.
-     * @throws IOException if an I/O error occurs writing to or creating the 
file.
+     * @throws IOException          if an I/O error occurs writing to or 
creating the file.
      * @throws NullPointerException if either {@code path} or {@code 
charSequence} is {@code null}.
      * @since 2.12.0
      */
     public static Path writeString(final Path path, final CharSequence 
charSequence, final Charset charset, final OpenOption... openOptions)
-        throws IOException {
+            throws IOException {
         // Check the text is not null before opening file.
         Objects.requireNonNull(path, "path");
         Objects.requireNonNull(charSequence, "charSequence");
diff --git 
a/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java 
b/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java
index 696f37f9..84939502 100644
--- a/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java
+++ b/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java
@@ -23,6 +23,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -205,4 +207,20 @@ public class PathUtilsContentEqualsTest {
         assertFalse(PathUtils.fileContentEquals(path1, path3));
     }
 
+    @Test
+    public void testFileContentEqualsZipFileSystem() throws Exception {
+        try (FileSystem fileSystem = 
FileSystems.newFileSystem(Paths.get("src/test/resources/org/apache/commons/io/test-same-size-diff-contents.zip"),
+                ClassLoader.getSystemClassLoader())) {
+            // Contains one char: A
+            final Path path1 = 
fileSystem.getPath("/test-same-size-diff-contents/A.txt");
+            // Contains one char: B
+            final Path path2 = 
fileSystem.getPath("/test-same-size-diff-contents/B.txt");
+            assertTrue(Files.exists(path1));
+            assertTrue(Files.exists(path2));
+            assertTrue(PathUtils.fileContentEquals(path1, path1));
+            assertTrue(PathUtils.fileContentEquals(path2, path2));
+            assertFalse(PathUtils.fileContentEquals(path1, path2));
+        }
+    }
+
 }
diff --git 
a/src/test/resources/org/apache/commons/io/test-same-size-diff-contents.zip 
b/src/test/resources/org/apache/commons/io/test-same-size-diff-contents.zip
new file mode 100644
index 00000000..4e49eb3d
Binary files /dev/null and 
b/src/test/resources/org/apache/commons/io/test-same-size-diff-contents.zip 
differ

Reply via email to