[Git][java-team/commons-io][upstream] New upstream version 2.16.1

Tony Mancill (@tmancill) gitlab at salsa.debian.org
Fri Apr 26 05:18:21 BST 2024



Tony Mancill pushed to branch upstream at Debian Java Maintainers / commons-io


Commits:
dccb96fe by tony mancill at 2024-04-25T21:04:59-07:00
New upstream version 2.16.1
- - - - -


24 changed files:

- README.md
- RELEASE-NOTES.txt
- pom.xml
- src/changes/changes.xml
- src/main/java/org/apache/commons/io/FileSystemUtils.java
- src/main/java/org/apache/commons/io/FileUtils.java
- src/main/java/org/apache/commons/io/IOUtils.java
- src/main/java/org/apache/commons/io/file/PathUtils.java
- src/main/java/org/apache/commons/io/filefilter/AndFileFilter.java
- src/main/java/org/apache/commons/io/input/BoundedInputStream.java
- src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java
- src/site/xdoc/download_io.xml
- src/test/java/org/apache/commons/io/FileSystemUtilsTest.java
- src/test/java/org/apache/commons/io/FileUtilsTest.java
- src/test/java/org/apache/commons/io/file/AbstractPathWrapper.java
- src/test/java/org/apache/commons/io/file/AbstractTempDirTest.java
- src/test/java/org/apache/commons/io/file/AccumulatorPathVisitorTest.java
- src/test/java/org/apache/commons/io/file/CleaningPathVisitorTest.java
- src/test/java/org/apache/commons/io/file/DeletingPathVisitorTest.java
- src/test/java/org/apache/commons/io/file/PathUtilsDeleteFileTest.java
- src/test/java/org/apache/commons/io/file/PathUtilsTest.java
- src/test/java/org/apache/commons/io/input/BoundedInputStreamTest.java
- src/test/java/org/apache/commons/io/output/DeferredFileOutputStreamTest.java
- src/test/java/org/apache/commons/io/output/ThresholdingOutputStreamTest.java


Changes:

=====================================
README.md
=====================================
@@ -46,7 +46,7 @@ Apache Commons IO
 [![Java CI](https://github.com/apache/commons-io/actions/workflows/maven.yml/badge.svg)](https://github.com/apache/commons-io/actions/workflows/maven.yml)
 [![Coverage Status](https://codecov.io/gh/apache/commons-io/branch/master/graph/badge.svg)](https://app.codecov.io/gh/apache/commons-io)
 [![Maven Central](https://maven-badges.herokuapp.com/maven-central/commons-io/commons-io/badge.svg?gav=true)](https://maven-badges.herokuapp.com/maven-central/commons-io/commons-io/?gav=true)
-[![Javadocs](https://javadoc.io/badge/commons-io/commons-io/2.16.0.svg)](https://javadoc.io/doc/commons-io/commons-io/2.16.0)
+[![Javadocs](https://javadoc.io/badge/commons-io/commons-io/2.16.1.svg)](https://javadoc.io/doc/commons-io/commons-io/2.16.1)
 [![CodeQL](https://github.com/apache/commons-io/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/apache/commons-io/actions/workflows/codeql-analysis.yml)
 [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/apache/commons-io/badge)](https://api.securityscorecards.dev/projects/github.com/apache/commons-io)
 
@@ -70,7 +70,7 @@ Alternatively, you can pull it from  the central Maven repositories:
 <dependency>
   <groupId>commons-io</groupId>
   <artifactId>commons-io</artifactId>
-  <version>2.16.0</version>
+  <version>2.16.1</version>
 </dependency>
 ```
 


=====================================
RELEASE-NOTES.txt
=====================================
@@ -1,4 +1,107 @@
 
+Apache Commons IO 2.16.1 Release Notes
+
+Introduction
+------------
+
+Commons IO is a package of Java utility classes like java.io.  
+Classes in this package are considered to be so standard and of such high 
+reuse as to justify existence in java.io.
+
+The Apache Commons IO library contains utility classes, stream implementations, file filters,
+file comparators, endian transformation classes, and much more.
+
+Java 8 is required.
+
+
+Fixed Bugs
+----------
+
+o          Reimplement FileSystemUtils using NIO. Thanks to Gary Gregory. 
+o IO-851:  FileSystemUtils no longer throws IllegalStateException. Thanks to Sebb, Gary Gregory. 
+o          Avoid possible NullPointerException in FileUtils.listAccumulate(File, IOFileFilter, IOFileFilter, FileVisitOption...). Thanks to Gary Gregory. 
+o IO-853:  BoundedInputStream.reset() not updating count. Thanks to Mike Drob, Gary Gregory. 
+o          ThresholdingOutputStream: a negative threshold should behave like a zero threshold and trigger the event on the first write #609. Thanks to rproserpio, Gary Gregory. 
+
+Changes
+-------
+
+o          Bump commons.bytebuddy.version from 1.14.12 to 1.14.13 #605. Thanks to Gary Gregory. 
+o          Bump org.apache.commons:commons-parent from 67 to 69 #608. Thanks to Gary Gregory, Dependabot. 
+
+
+Commons IO 2.7 and up requires Java 8 or above.
+Commons IO 2.6 requires Java 7 or above.
+Commons IO 2.3 through 2.5 requires Java 6 or above.
+Commons IO 2.2 requires Java 5 or above.
+Commons IO 1.4 requires Java 1.3 or above.
+
+Historical list of changes: https://commons.apache.org/proper/commons-io/changes-report.html
+
+For complete information on Apache Commons IO, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache Commons IO website:
+
+https://commons.apache.org/proper/commons-io/
+
+Download page: https://commons.apache.org/proper/commons-io/download_io.cgi
+
+Have fun!
+-Apache Commons Team
+
+------------------------------------------------------------------------------
+
+
+Apache Commons IO 2.16.1 Release Notes
+
+Introduction
+------------
+
+Commons IO is a package of Java utility classes like java.io.  
+Classes in this package are considered to be so standard and of such high 
+reuse as to justify existence in java.io.
+
+The Apache Commons IO library contains utility classes, stream implementations, file filters,
+file comparators, endian transformation classes, and much more.
+
+Java 8 is required.
+
+
+Fixed Bugs
+----------
+
+o          Reimplement FileSystemUtils using NIO. Thanks to Gary Gregory. 
+o IO-851:  FileSystemUtils no longer throws IllegalStateException. Thanks to Sebb, Gary Gregory. 
+o          Avoid possible NullPointerException in FileUtils.listAccumulate(File, IOFileFilter, IOFileFilter, FileVisitOption...). Thanks to Gary Gregory. 
+o IO-853:  BoundedInputStream.reset() not updating count. Thanks to Mike Drob, Gary Gregory. 
+
+Changes
+-------
+
+o          Bump commons.bytebuddy.version from 1.14.12 to 1.14.13 #605. Thanks to Gary Gregory. 
+o          Bump org.apache.commons:commons-parent from 67 to 69 #608. Thanks to Gary Gregory, Dependabot. 
+
+
+Commons IO 2.7 and up requires Java 8 or above.
+Commons IO 2.6 requires Java 7 or above.
+Commons IO 2.3 through 2.5 requires Java 6 or above.
+Commons IO 2.2 requires Java 5 or above.
+Commons IO 1.4 requires Java 1.3 or above.
+
+Historical list of changes: https://commons.apache.org/proper/commons-io/changes-report.html
+
+For complete information on Apache Commons IO, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache Commons IO website:
+
+https://commons.apache.org/proper/commons-io/
+
+Download page: https://commons.apache.org/proper/commons-io/download_io.cgi
+
+Have fun!
+-Apache Commons Team
+
+------------------------------------------------------------------------------
+
+
 Apache Commons IO 2.16.0 Release Notes
 
 Introduction


=====================================
pom.xml
=====================================
@@ -19,12 +19,12 @@
   <parent>
     <groupId>org.apache.commons</groupId>
     <artifactId>commons-parent</artifactId>
-    <version>67</version>
+    <version>69</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <groupId>commons-io</groupId>
   <artifactId>commons-io</artifactId>
-  <version>2.16.0</version>
+  <version>2.16.1</version>
   <name>Apache Commons IO</name>
 
   <inceptionYear>2002</inceptionYear>
@@ -124,11 +124,11 @@ file comparators, endian transformation classes, and much more.
     <maven.compiler.target>1.8</maven.compiler.target>
     <commons.componentid>io</commons.componentid>
     <commons.module.name>org.apache.commons.io</commons.module.name>
-    <commons.rc.version>RC1</commons.rc.version>
-    <commons.bc.version>2.15.1</commons.bc.version>
-    <commons.release.version>2.16.0</commons.release.version>
-    <commons.release.next>2.16.1</commons.release.next>
-    <project.build.outputTimestamp>2024-03-25T12:17:22Z</project.build.outputTimestamp>
+    <commons.rc.version>RC2</commons.rc.version>
+    <commons.bc.version>2.16.0</commons.bc.version>
+    <commons.release.version>2.16.1</commons.release.version>
+    <commons.release.next>2.16.2</commons.release.next>
+    <project.build.outputTimestamp>2024-04-04T20:10:16Z</project.build.outputTimestamp>
     <commons.release.desc>(requires Java 8)</commons.release.desc>
     <commons.jira.id>IO</commons.jira.id>
     <commons.jira.pid>12310477</commons.jira.pid>
@@ -157,7 +157,7 @@ file comparators, endian transformation classes, and much more.
     <commons.scmPubCheckoutDirectory>site-content</commons.scmPubCheckoutDirectory>
     <commons.javadoc.java.link>${commons.javadoc8.java.link}</commons.javadoc.java.link>
     <jmh.version>1.37</jmh.version>
-    <commons.bytebuddy.version>1.14.12</commons.bytebuddy.version>
+    <commons.bytebuddy.version>1.14.13</commons.bytebuddy.version>
     <japicmp.skip>false</japicmp.skip>
     <jacoco.skip>${env.JACOCO_SKIP}</jacoco.skip>
     <commons.release.isDistModule>true</commons.release.isDistModule>
@@ -267,14 +267,6 @@ file comparators, endian transformation classes, and much more.
       <plugin>
         <groupId>com.github.siom79.japicmp</groupId>
         <artifactId>japicmp-maven-plugin</artifactId>
-        <configuration>
-          <parameter>
-			 <excludes>
-               <!-- False positive: https://github.com/siom79/japicmp/issues/365 -->
-               <exclude>org.apache.commons.io.StreamIterator</exclude>
-             </excludes>
-          </parameter>
-        </configuration>
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
@@ -341,11 +333,9 @@ file comparators, endian transformation classes, and much more.
                     <exec executable="svn">
                       <arg line="checkout --depth immediates ${commons.scmPubUrl} ${commons.scmPubCheckoutDirectory}" />
                     </exec>
-
                     <exec executable="svn">
                       <arg line="update --set-depth exclude ${commons.scmPubCheckoutDirectory}/javadocs" />
                     </exec>
-
                     <pathconvert pathsep=" " property="dirs">
                       <dirset dir="${commons.scmPubCheckoutDirectory}" includes="*" />
                     </pathconvert>


=====================================
src/changes/changes.xml
=====================================
@@ -46,8 +46,19 @@ The <action> type attribute can be add,update,fix,remove.
     <title>Apache Commons IO Release Notes</title>
   </properties>
   <body>
+    <release version="2.16.1" date="2024-04-04" description="Java 8 is required.">
+      <!-- FIX -->
+      <action dev="ggregory" type="fix"                due-to="Gary Gregory">Reimplement FileSystemUtils using NIO.</action>
+      <action dev="ggregory" type="fix" issue="IO-851" due-to="Sebb, Gary Gregory">FileSystemUtils no longer throws IllegalStateException.</action>
+      <action dev="ggregory" type="fix"                due-to="Gary Gregory">Avoid possible NullPointerException in FileUtils.listAccumulate(File, IOFileFilter, IOFileFilter, FileVisitOption...).</action>
+      <action dev="ggregory" type="fix" issue="IO-853" due-to="Mike Drob, Gary Gregory">BoundedInputStream.reset() not updating count.</action>
+      <action dev="ggregory" type="fix" issue="IO-854" due-to="rproserpio, Bill Orpet, Gary Gregory">ThresholdingOutputStream: a negative threshold should behave like a zero threshold and trigger the event on the first write #609.</action>
+      <!-- UPDATE -->
+      <action dev="ggregory" type="update"             due-to="Gary Gregory">Bump commons.bytebuddy.version from 1.14.12 to 1.14.13 #605.</action>
+      <action dev="ggregory" type="update"             due-to="Gary Gregory, Dependabot">Bump org.apache.commons:commons-parent from 67 to 69 #608.</action>
+    </release>
     <release version="2.16.0" date="2024-03-25" description="Java 8 is required.">
-      <!-- Fix -->
+      <!-- FIX -->
       <action dev="ggregory" type="fix" due-to="Elliotte Rusty Harold">Fix and re-enable testSkip_RequiredCharsets #518.</action>
       <action dev="ggregory" type="fix" issue="IO-824" due-to="Miguel Munoz, Gary Gregory">SymbolicLineFileFilter documentation fixes.</action>
       <action dev="ggregory" type="fix" issue="IO-795" due-to="Miguel Munoz, Gary Gregory">CharSequenceInputStream.reset() only works once #520.</action>


=====================================
src/main/java/org/apache/commons/io/FileSystemUtils.java
=====================================
@@ -16,148 +16,67 @@
  */
 package org.apache.commons.io;
 
-import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-import java.nio.file.InvalidPathException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.time.Duration;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
 import java.util.Objects;
-import java.util.StringTokenizer;
-import java.util.stream.Collectors;
 
 /**
  * General File System utilities.
  * <p>
- * This class provides static utility methods for general file system functions not provided via the JDK {@link java.io.File File} class.
+ * This class provides static utility methods for general file system functions not provided before Java 6's {@link java.io.File File} class.
+ * </p>
  * <p>
  * The current functions provided are:
+ * </p>
  * <ul>
- * <li>Get the free space on a drive
+ * <li>Get the free space on a drive</li>
  * </ul>
  *
  * @since 1.1
- * @deprecated As of 2.6 deprecated without replacement. Use equivalent methods in {@link java.nio.file.FileStore} instead, e.g.
+ * @deprecated As of 2.6 deprecated without replacement. Use equivalent methods in {@link java.nio.file.FileStore} instead,
  *             {@code Files.getFileStore(Paths.get("/home")).getUsableSpace()} or iterate over {@code FileSystems.getDefault().getFileStores()}
  */
 @Deprecated
 public class FileSystemUtils {
 
     /**
-     * Singleton instance, used mainly for testing.
-     */
-    private static final FileSystemUtils INSTANCE = new FileSystemUtils();
-
-    /**
-     * Operating system state flag for error.
-     */
-    private static final int INIT_PROBLEM = -1;
-
-    /**
-     * Operating system state flag for neither UNIX nor Windows.
-     */
-    private static final int OTHER = 0;
-
-    /**
-     * Operating system state flag for Windows.
-     */
-    private static final int WINDOWS = 1;
-
-    /**
-     * Operating system state flag for Unix.
-     */
-    private static final int UNIX = 2;
-
-    /**
-     * Operating system state flag for POSIX flavor Unix.
-     */
-    private static final int POSIX_UNIX = 3;
-
-    /**
-     * The operating system flag.
-     */
-    private static final int OS;
-
-    /**
-     * The path to {@code df}.
-     */
-    private static final String DF;
-
-    static {
-        int os = OTHER;
-        String dfPath = "df";
-        try {
-            String osName = System.getProperty("os.name");
-            if (osName == null) {
-                throw new IOException("os.name not found");
-            }
-            osName = osName.toLowerCase(Locale.ENGLISH);
-            // match
-            if (osName.contains("windows")) {
-                os = WINDOWS;
-            } else if (osName.contains("linux") || osName.contains("mpe/ix") || osName.contains("freebsd") || osName.contains("openbsd")
-                    || osName.contains("irix") || osName.contains("digital unix") || osName.contains("unix") || osName.contains("mac os x")) {
-                os = UNIX;
-            } else if (osName.contains("sun os") || osName.contains("sunos") || osName.contains("solaris")) {
-                os = POSIX_UNIX;
-                dfPath = "/usr/xpg4/bin/df";
-            } else if (osName.contains("hp-ux") || osName.contains("aix")) {
-                os = POSIX_UNIX;
-            }
-
-        } catch (final Exception ex) {
-            os = INIT_PROBLEM;
-        }
-        OS = os;
-        DF = dfPath;
-    }
-
-    /**
-     * Returns the free space on a drive or volume by invoking the command line. This method does not normalize the result, and typically returns bytes on
-     * Windows, 512 byte units on OS X and kilobytes on Unix. As this is not very useful, this method is deprecated in favor of {@link #freeSpaceKb(String)}
-     * which returns a result in kilobytes.
+     * Gets the number of kibibytes (1024 bytes) available to this Java virtual machine on the given file store.
      * <p>
      * Note that some OS's are NOT currently supported, including OS/390, OpenVMS.
+     * </p>
      *
      * <pre>
      * FileSystemUtils.freeSpace("C:"); // Windows
      * FileSystemUtils.freeSpace("/volume"); // *nix
      * </pre>
      *
-     * The free space is calculated via the command line. It uses 'dir /-c' on Windows and 'df' on *nix.
-     *
      * @param path the path to get free space for, not null, not empty on UNIX
      * @return the amount of free drive space on the drive or volume
-     * @throws IllegalArgumentException if the path is invalid
-     * @throws IllegalStateException    if an error occurred in initialization
-     * @throws IOException              if an error occurs when finding the free space
+     * @throws IOException              if an I/O error occurs.
+     * @throws IllegalArgumentException if the path is invalid.
      * @since 1.1, enhanced OS support in 1.2 and 1.3
      * @deprecated Use freeSpaceKb(String) Deprecated from 1.3, may be removed in 2.0
      */
     @Deprecated
     public static long freeSpace(final String path) throws IOException {
-        return INSTANCE.freeSpaceOS(path, OS, false, Duration.ofMillis(-1));
+        return getFreeSpace(path);
     }
 
     /**
-     * Returns the free space for the working directory in kibibytes (1024 bytes) by invoking the command line.
+     * Gets the number of kibibytes (1024 bytes) available to this Java virtual machine on the current file store.
      * <p>
      * Identical to:
+     * </p>
      *
      * <pre>
      * freeSpaceKb(FileUtils.current().getAbsolutePath())
      * </pre>
      *
      * @return the amount of free drive space on the drive or volume in kilobytes
-     * @throws IllegalStateException if an error occurred in initialization
-     * @throws IOException           if an error occurs when finding the free space
+     * @throws IOException              if an I/O error occurs.
+     * @throws IllegalArgumentException if the path is invalid.
      * @since 2.0
      * @deprecated As of 2.6 deprecated without replacement. Please use {@link java.nio.file.FileStore#getUsableSpace()}.
      */
@@ -167,18 +86,19 @@ public class FileSystemUtils {
     }
 
     /**
-     * Returns the free space for the working directory in kibibytes (1024 bytes) by invoking the command line.
+     * Gets the number of kibibytes (1024 bytes) available to this Java virtual machine on the current file store.
      * <p>
      * Identical to:
+     * </p>
      *
      * <pre>
      * freeSpaceKb(FileUtils.current().getAbsolutePath())
      * </pre>
      *
-     * @param timeout The timeout amount in milliseconds or no timeout if the value is zero or less
+     * @param timeout ignored.
      * @return the amount of free drive space on the drive or volume in kilobytes
-     * @throws IllegalStateException if an error occurred in initialization
-     * @throws IOException           if an error occurs when finding the free space
+     * @throws IOException              if an I/O error occurs.
+     * @throws IllegalArgumentException if the path is invalid.
      * @since 2.0
      * @deprecated As of 2.6 deprecated without replacement. Please use {@link java.nio.file.FileStore#getUsableSpace()}.
      */
@@ -188,24 +108,17 @@ public class FileSystemUtils {
     }
 
     /**
-     * Returns the free space on a drive or volume in kibibytes (1024 bytes) by invoking the command line.
+     * Gets the number of kibibytes (1024 bytes) available to this Java virtual machine on the given file store.
      *
      * <pre>
      * FileSystemUtils.freeSpaceKb("C:"); // Windows
      * FileSystemUtils.freeSpaceKb("/volume"); // *nix
      * </pre>
      *
-     * The free space is calculated via the command line. It uses 'dir /-c' on Windows, 'df -kP' on AIX/HP-UX and 'df -k' on other Unix.
-     * <p>
-     * In order to work, you must be running Windows, or have an implementation of UNIX df that supports GNU format when passed -k (or -kP). If you are going to
-     * rely on this code, please check that it works on your OS by running some simple tests to compare the command line with the output from this class. If
-     * your operating system isn't supported, please raise a JIRA call detailing the exact result from df -k and as much other detail as possible, thanks.
-     *
      * @param path the path to get free space for, not null, not empty on UNIX
      * @return the amount of free drive space on the drive or volume in kilobytes
-     * @throws IllegalArgumentException if the path is invalid
-     * @throws IllegalStateException    if an error occurred in initialization
-     * @throws IOException              if an error occurs when finding the free space
+     * @throws IOException              if an I/O error occurs.
+     * @throws IllegalArgumentException if the path is invalid.
      * @since 1.2, enhanced OS support in 1.3
      * @deprecated As of 2.6 deprecated without replacement. Please use {@link java.nio.file.FileStore#getUsableSpace()}.
      */
@@ -215,312 +128,57 @@ public class FileSystemUtils {
     }
 
     /**
-     * Returns the free space on a drive or volume in kibibytes (1024 bytes) by invoking the command line.
+     * Gets the number of kibibytes (1024 bytes) available to this Java virtual machine on the given file store.
      *
      * <pre>
      * FileSystemUtils.freeSpaceKb("C:"); // Windows
      * FileSystemUtils.freeSpaceKb("/volume"); // *nix
      * </pre>
      *
-     * The free space is calculated via the command line. It uses 'dir /-c' on Windows, 'df -kP' on AIX/HP-UX and 'df -k' on other Unix.
-     * <p>
-     * In order to work, you must be running Windows, or have an implementation of UNIX df that supports GNU format when passed -k (or -kP). If you are going to
-     * rely on this code, please check that it works on your OS by running some simple tests to compare the command line with the output from this class. If
-     * your operating system isn't supported, please raise a JIRA call detailing the exact result from df -k and as much other detail as possible, thanks.
-     *
      * @param path    the path to get free space for, not null, not empty on UNIX
-     * @param timeout The timeout amount in milliseconds or no timeout if the value is zero or less
+     * @param timeout ignored.
      * @return the amount of free drive space on the drive or volume in kilobytes
-     * @throws IllegalArgumentException if the path is invalid
-     * @throws IllegalStateException    if an error occurred in initialization
-     * @throws IOException              if an error occurs when finding the free space
+     * @throws IOException              if an I/O error occurs.
+     * @throws IllegalArgumentException if the path is invalid.
      * @since 2.0
      * @deprecated As of 2.6 deprecated without replacement. Please use {@link java.nio.file.FileStore#getUsableSpace()}.
      */
     @Deprecated
     public static long freeSpaceKb(final String path, final long timeout) throws IOException {
-        return INSTANCE.freeSpaceOS(path, OS, true, Duration.ofMillis(timeout));
-    }
-
-    /**
-     * Instances should NOT be constructed in standard programming.
-     *
-     * @deprecated TODO Make private in 3.0.
-     */
-    @Deprecated
-    public FileSystemUtils() {
-        // empty
-    }
-
-    /**
-     * Checks that a path string is valid through NIO's {@link Paths#get(String, String...)}.
-     *
-     * @param pathStr string.
-     * @param allowEmpty allows empty paths.
-     * @return A checked normalized Path.
-     * @throws InvalidPathException if the path string cannot be converted to a {@code Path}
-     */
-    private Path checkPath(final String pathStr, final boolean allowEmpty) {
-        Objects.requireNonNull(pathStr, "pathStr");
-        if (!allowEmpty && pathStr.isEmpty()) {
-            throw new IllegalArgumentException("Path must not be empty");
-        }
-        final Path normPath;
-        final String trimPathStr = pathStr.trim();
-        if (trimPathStr.isEmpty() || trimPathStr.charAt(0) != '"') {
-            // Paths.get throws InvalidPathException if the path is bad before we pass it to a shell.
-            normPath = Paths.get(trimPathStr).normalize();
-        } else {
-            // Paths.get throws InvalidPathException if the path is bad before we pass it to a shell.
-            normPath = Paths.get(trimPathStr.substring(1, trimPathStr.length() - 1)).normalize();
-        }
-        return normPath;
+        return getFreeSpace(path) / FileUtils.ONE_KB;
     }
 
     /**
-     * Returns the free space on a drive or volume in a cross-platform manner. Note that some OS's are NOT currently supported, including OS/390.
+     * Gets the number of bytes available to this Java virtual machine on the given file store.
      *
      * <pre>
      * FileSystemUtils.freeSpace("C:"); // Windows
      * FileSystemUtils.freeSpace("/volume"); // *nix
      * </pre>
      *
-     * The free space is calculated via the command line. It uses 'dir /-c' on Windows and 'df' on *nix.
-     *
      * @param pathStr the path to get free space for, not null, not empty on UNIX
-     * @param os      the operating system code
-     * @param kb      whether to normalize to kilobytes
-     * @param timeout The timeout amount in milliseconds or no timeout if the value is zero or less
      * @return the amount of free drive space on the drive or volume
-     * @throws IllegalArgumentException if the path is invalid
-     * @throws IllegalStateException    if an error occurred in initialization
-     * @throws IOException              if an error occurs when finding the free space
-     */
-    long freeSpaceOS(final String pathStr, final int os, final boolean kb, final Duration timeout) throws IOException {
-        Objects.requireNonNull(pathStr, "path");
-        switch (os) {
-        case WINDOWS:
-            return kb ? freeSpaceWindows(pathStr, timeout) / FileUtils.ONE_KB : freeSpaceWindows(pathStr, timeout);
-        case UNIX:
-            return freeSpaceUnix(pathStr, kb, false, timeout);
-        case POSIX_UNIX:
-            return freeSpaceUnix(pathStr, kb, true, timeout);
-        case OTHER:
-            throw new IllegalStateException("Unsupported operating system");
-        default:
-            throw new IllegalStateException("Exception caught when determining operating system");
-        }
-    }
-
-    /**
-     * Finds free space on the *nix platform using the 'df' command.
-     *
-     * @param path    the path to get free space for
-     * @param kb      whether to normalize to kilobytes
-     * @param posix   whether to use the POSIX standard format flag
-     * @param timeout The timeout amount in milliseconds or no timeout if the value is zero or less
-     * @return the amount of free drive space on the volume
-     * @throws IOException If an I/O error occurs
-     */
-    long freeSpaceUnix(final String path, final boolean kb, final boolean posix, final Duration timeout) throws IOException {
-        final String pathStr = checkPath(path, false).toString();
-        // build and run the 'dir' command
-        String flags = "-";
-        if (kb) {
-            flags += "k";
-        }
-        if (posix) {
-            flags += "P";
-        }
-        final String[] cmdAttribs = flags.length() > 1 ? new String[] { DF, flags, pathStr } : new String[] { DF, pathStr };
-
-        // perform the command, asking for up to 3 lines (header, interesting, overflow)
-        final List<String> lines = performCommand(cmdAttribs, 3, timeout);
-        if (lines.size() < 2) {
-            // unknown problem, throw exception
-            throw new IOException("Command line '" + DF + "' did not return info as expected for path '" + pathStr + "'- response was " + lines);
+     * @throws IOException              if an I/O error occurs.
+     * @throws IllegalArgumentException if the path is invalid.
+     */
+    static long getFreeSpace(final String pathStr) throws IOException {
+        final Path path = Paths.get(Objects.requireNonNull(pathStr, "pathStr"));
+        if (Files.exists(path)) {
+            // Need an absolute path for input like "" to work
+            return Files.getFileStore(path.toAbsolutePath()).getUsableSpace();
+            // return path.toAbsolutePath().toFile().getUsableSpace();
         }
-        final String line2 = lines.get(1); // the line we're interested in
-
-        // Now, we tokenize the string. The fourth element is what we want.
-        StringTokenizer tok = new StringTokenizer(line2, " ");
-        if (tok.countTokens() < 4) {
-            // could be long Filesystem, thus data on third line
-            if (tok.countTokens() != 1 || lines.size() < 3) {
-                throw new IOException("Command line '" + DF + "' did not return data as expected for path '" + pathStr + "'- check path is valid");
-            }
-            final String line3 = lines.get(2); // the line may be interested in
-            tok = new StringTokenizer(line3, " ");
-        } else {
-            tok.nextToken(); // Ignore Filesystem
-        }
-        tok.nextToken(); // Ignore 1K-blocks
-        tok.nextToken(); // Ignore Used
-        final String freeSpace = tok.nextToken();
-        return parseBytes(freeSpace, path);
+        throw new IllegalArgumentException(path.toString());
     }
 
     /**
-     * Finds free space on the Windows platform using the 'dir' command.
-     *
-     * @param pathStr    the path to get free space for, including the colon
-     * @param timeout The timeout amount in milliseconds or no timeout if the value is zero or less
-     * @return the amount of free drive space on the drive
-     * @throws IOException If an I/O error occurs
-     */
-    long freeSpaceWindows(final String pathStr, final Duration timeout) throws IOException {
-        final Path path = checkPath(pathStr, true);
-        // build and run the 'dir' command
-        // read in the output of the command to an ArrayList
-        final List<String> lines = performCommand(new String[] { "cmd.exe", "/C", "dir /a /-c \"" + path + "\"" }, Integer.MAX_VALUE, timeout);
-
-        // now iterate over the lines we just read and find the LAST
-        // non-empty line (the free space bytes should be in the last element
-        // of the ArrayList anyway, but this will ensure it works even if it's
-        // not, still assuming it is on the last non-blank line)
-        for (int i = lines.size() - 1; i >= 0; i--) {
-            final String line = lines.get(i);
-            if (!line.isEmpty()) {
-                return parseDir(line, pathStr);
-            }
-        }
-        // all lines are blank
-        throw new IOException("Command 'dir' did not return any info for path '" + path + "'");
-    }
-
-    /**
-     * Opens the process to the operating system.
-     * <p>
-     * Package-private for tests.
-     * </p>
-     * @param cmdArray the command line parameters
-     * @return the process
-     * @throws IOException If an I/O error occurs
-     */
-    Process openProcess(final String[] cmdArray) throws IOException {
-        return Runtime.getRuntime().exec(cmdArray);
-    }
-
-    /**
-     * Parses the bytes from a string.
-     *
-     * @param freeSpace the free space string
-     * @param path      the path
-     * @return the number of bytes
-     * @throws IOException If an I/O error occurs
-     */
-    private long parseBytes(final String freeSpace, final String path) throws IOException {
-        try {
-            final long bytes = Long.parseLong(freeSpace);
-            if (bytes < 0) {
-                throw new IOException("Command line '" + DF + "' did not find free space in response for path '" + path + "'- check path is valid");
-            }
-            return bytes;
-
-        } catch (final NumberFormatException ex) {
-            throw new IOException("Command line '" + DF + "' did not return numeric data as expected for path '" + path + "'- check path is valid", ex);
-        }
-    }
-
-    /**
-     * Parses the Windows dir response last line.
-     *
-     * @param line the line to parse
-     * @param path the path that was sent
-     * @return the number of bytes
-     * @throws IOException If an I/O error occurs
-     */
-    private long parseDir(final String line, final String path) throws IOException {
-        // read from the end of the line to find the last numeric
-        // character on the line, then continue until we find the first
-        // non-numeric character, and everything between that and the last
-        // numeric character inclusive is our free space bytes count
-        int bytesStart = 0;
-        int bytesEnd = 0;
-        int j = line.length() - 1;
-        innerLoop1: while (j >= 0) {
-            final char c = line.charAt(j);
-            if (Character.isDigit(c)) {
-                // found the last numeric character, this is the end of
-                // the free space bytes count
-                bytesEnd = j + 1;
-                break innerLoop1;
-            }
-            j--;
-        }
-        innerLoop2: while (j >= 0) {
-            final char c = line.charAt(j);
-            if (!Character.isDigit(c) && c != ',' && c != '.') {
-                // found the next non-numeric character, this is the
-                // beginning of the free space bytes count
-                bytesStart = j + 1;
-                break innerLoop2;
-            }
-            j--;
-        }
-        if (j < 0) {
-            throw new IOException("Command line 'dir /-c' did not return valid info for path '" + path + "'");
-        }
-
-        // remove commas and dots in the bytes count
-        final StringBuilder buf = new StringBuilder(line.substring(bytesStart, bytesEnd));
-        for (int k = 0; k < buf.length(); k++) {
-            if (buf.charAt(k) == ',' || buf.charAt(k) == '.') {
-                buf.deleteCharAt(k--);
-            }
-        }
-        return parseBytes(buf.toString(), path);
-    }
-
-    /**
-     * Performs an OS command.
+     * Instances should NOT be constructed in standard programming.
      *
-     * @param cmdArray the command line parameters
-     * @param max        The maximum limit for the lines returned
-     * @param timeout    The timeout amount in milliseconds or no timeout if the value is zero or less
-     * @return the lines returned by the command, converted to lower-case
-     * @throws IOException if an error occurs
+     * @deprecated TODO Make private in 3.0.
      */
-    private List<String> performCommand(final String[] cmdArray, final int max, final Duration timeout) throws IOException {
-        //
-        // This method does what it can to avoid the 'Too many open files' error
-        // based on trial and error and these links:
-        // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4784692
-        // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4801027
-        // However, it's still not perfect as the JDK support is so poor.
-        // (See commons-exec or Ant for a better multithreaded multi-OS solution.)
-        //
-        final Process proc = openProcess(cmdArray);
-        final Thread monitor = ThreadMonitor.start(timeout);
-        try (InputStream in = proc.getInputStream();
-                OutputStream out = proc.getOutputStream();
-                // default Charset is most likely appropriate here
-                InputStream err = proc.getErrorStream();
-                // If in is null here, InputStreamReader throws NullPointerException
-                BufferedReader inr = new BufferedReader(new InputStreamReader(in, Charset.defaultCharset()))) {
-
-            final List<String> lines = inr.lines().limit(max).map(line -> line.toLowerCase(Locale.getDefault()).trim()).collect(Collectors.toList());
-            proc.waitFor();
-            ThreadMonitor.stop(monitor);
-
-            if (proc.exitValue() != 0) {
-                // Command problem, throw exception
-                throw new IOException("Command line returned OS error code '" + proc.exitValue() + "' for command " + Arrays.asList(cmdArray));
-            }
-            if (lines.isEmpty()) {
-                // Unknown problem, throw exception
-                throw new IOException("Command line did not return any info for command " + Arrays.asList(cmdArray));
-            }
-
-            return lines;
-
-        } catch (final InterruptedException ex) {
-            throw new IOException("Command line threw an InterruptedException for command " + Arrays.asList(cmdArray) + " timeout=" + timeout, ex);
-        } finally {
-            if (proc != null) {
-                proc.destroy();
-            }
-        }
+    @Deprecated
+    public FileSystemUtils() {
+        // empty
     }
 
 }


=====================================
src/main/java/org/apache/commons/io/FileUtils.java
=====================================
@@ -2230,14 +2230,16 @@ public class FileUtils {
     }
 
     private static AccumulatorPathVisitor listAccumulate(final File directory, final IOFileFilter fileFilter, final IOFileFilter dirFilter,
-        final FileVisitOption... options) throws IOException {
+            final FileVisitOption... options) throws IOException {
         final boolean isDirFilterSet = dirFilter != null;
         final FileEqualsFileFilter rootDirFilter = new FileEqualsFileFilter(directory);
         final PathFilter dirPathFilter = isDirFilterSet ? rootDirFilter.or(dirFilter) : rootDirFilter;
         final AccumulatorPathVisitor visitor = new AccumulatorPathVisitor(Counters.noopPathCounters(), fileFilter, dirPathFilter,
-            (p, e) -> FileVisitResult.CONTINUE);
+                (p, e) -> FileVisitResult.CONTINUE);
         final Set<FileVisitOption> optionSet = new HashSet<>();
-        Collections.addAll(optionSet, options);
+        if (options != null) {
+            Collections.addAll(optionSet, options);
+        }
         Files.walkFileTree(directory.toPath(), optionSet, toMaxDepth(isDirFilterSet), visitor);
         return visitor;
     }


=====================================
src/main/java/org/apache/commons/io/IOUtils.java
=====================================
@@ -1686,7 +1686,7 @@ public class IOUtils {
     /**
      * Fills the given array with 0s.
      *
-     * @param arr The array to fill.
+     * @param arr The non-null array to fill.
      * @return The given array.
      */
     private static byte[] fill0(final byte[] arr) {
@@ -1697,7 +1697,7 @@ public class IOUtils {
     /**
      * Fills the given array with 0s.
      *
-     * @param arr The array to fill.
+     * @param arr The non-null array to fill.
      * @return The given array.
      */
     private static char[] fill0(final char[] arr) {


=====================================
src/main/java/org/apache/commons/io/file/PathUtils.java
=====================================
@@ -1841,6 +1841,7 @@ public final class PathUtils {
      * @throws IOException if an I/O error is thrown when accessing the starting file.
      * @since 2.9.0
      */
+    @SuppressWarnings("resource") // Caller closes
     public static Stream<Path> walk(final Path start, final PathFilter pathFilter, final int maxDepth, final boolean readAttributes,
             final FileVisitOption... options) throws IOException {
         return Files.walk(start, maxDepth, options)


=====================================
src/main/java/org/apache/commons/io/filefilter/AndFileFilter.java
=====================================
@@ -41,9 +41,7 @@ import java.util.stream.Stream;
  * @since 1.0
  * @see FileFilterUtils#and(IOFileFilter...)
  */
-public class AndFileFilter
-        extends AbstractFileFilter
-        implements ConditionalFileFilter, Serializable {
+public class AndFileFilter extends AbstractFileFilter implements ConditionalFileFilter, Serializable {
 
     private static final long serialVersionUID = 7215974688563965257L;
 
@@ -79,8 +77,8 @@ public class AndFileFilter
 
     /**
      * Constructs a new instance for the give filters.
-     * @param fileFilters filters to OR.
      *
+     * @param fileFilters filters to OR.
      * @since 2.9.0
      */
     public AndFileFilter(final IOFileFilter... fileFilters) {
@@ -143,7 +141,7 @@ public class AndFileFilter
      */
     @Override
     public void addFileFilter(final IOFileFilter fileFilter) {
-        this.fileFilters.add(Objects.requireNonNull(fileFilter, "fileFilter"));
+        fileFilters.add(Objects.requireNonNull(fileFilter, "fileFilter"));
     }
 
     /**
@@ -161,11 +159,11 @@ public class AndFileFilter
      */
     @Override
     public List<IOFileFilter> getFileFilters() {
-        return Collections.unmodifiableList(this.fileFilters);
+        return Collections.unmodifiableList(fileFilters);
     }
 
     private boolean isEmpty() {
-        return this.fileFilters.isEmpty();
+        return fileFilters.isEmpty();
     }
 
     /**
@@ -173,7 +171,7 @@ public class AndFileFilter
      */
     @Override
     public boolean removeFileFilter(final IOFileFilter ioFileFilter) {
-        return this.fileFilters.remove(ioFileFilter);
+        return fileFilters.remove(ioFileFilter);
     }
 
     /**
@@ -186,7 +184,7 @@ public class AndFileFilter
     }
 
     /**
-     * Provide a String representation of this file filter.
+     * Builds a String representation of this file filter.
      *
      * @return a String representation
      */


=====================================
src/main/java/org/apache/commons/io/input/BoundedInputStream.java
=====================================
@@ -249,6 +249,9 @@ public class BoundedInputStream extends ProxyInputStream {
     /** The current count of bytes counted. */
     private long count;
 
+    /** The current mark. */
+    private long mark;
+
     /** The max count of bytes to read. */
     private final long maxCount;
 
@@ -347,7 +350,7 @@ public class BoundedInputStream extends ProxyInputStream {
      * @return The count of bytes read.
      * @since 2.12.0
      */
-    public long getCount() {
+    public synchronized long getCount() {
         return count;
     }
 
@@ -404,6 +407,7 @@ public class BoundedInputStream extends ProxyInputStream {
     @Override
     public synchronized void mark(final int readLimit) {
         in.mark(readLimit);
+        mark = count;
     }
 
     /**
@@ -482,6 +486,7 @@ public class BoundedInputStream extends ProxyInputStream {
     @Override
     public synchronized void reset() throws IOException {
         in.reset();
+        count = mark;
     }
 
     /**
@@ -504,7 +509,7 @@ public class BoundedInputStream extends ProxyInputStream {
      * @throws IOException if an I/O error occurs.
      */
     @Override
-    public long skip(final long n) throws IOException {
+    public synchronized long skip(final long n) throws IOException {
         final long skip = super.skip(toReadLen(n));
         count += skip;
         return skip;


=====================================
src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java
=====================================
@@ -81,6 +81,7 @@ public class ThresholdingOutputStream extends OutputStream {
 
     /**
      * Constructs an instance of this class which will trigger an event at the specified threshold.
+     * A negative threshold has no meaning and will be treated as 0
      *
      * @param threshold The number of bytes at which to trigger an event.
      * @param thresholdConsumer Accepts reaching the threshold.
@@ -89,10 +90,9 @@ public class ThresholdingOutputStream extends OutputStream {
      */
     public ThresholdingOutputStream(final int threshold, final IOConsumer<ThresholdingOutputStream> thresholdConsumer,
         final IOFunction<ThresholdingOutputStream, OutputStream> outputStreamGetter) {
-        this.threshold = threshold;
+        this.threshold = threshold < 0 ? 0 : threshold;
         this.thresholdConsumer = thresholdConsumer == null ? IOConsumer.noop() : thresholdConsumer;
         this.outputStreamGetter = outputStreamGetter == null ? NOOP_OS_GETTER : outputStreamGetter;
-        this.thresholdExceeded = threshold < 0;
     }
 
     /**


=====================================
src/site/xdoc/download_io.xml
=====================================
@@ -113,32 +113,32 @@ limitations under the License.
       </p>
     </subsection>
     </section>
-    <section name="Apache Commons IO 2.16.0 (requires Java 8)">
+    <section name="Apache Commons IO 2.16.1 (requires Java 8)">
       <subsection name="Binaries">
         <table>
           <tr>
-              <td><a href="[preferred]/commons/io/binaries/commons-io-2.16.0-bin.tar.gz">commons-io-2.16.0-bin.tar.gz</a></td>
-              <td><a href="https://downloads.apache.org/commons/io/binaries/commons-io-2.16.0-bin.tar.gz.sha512">sha512</a></td>
-              <td><a href="https://downloads.apache.org/commons/io/binaries/commons-io-2.16.0-bin.tar.gz.asc">pgp</a></td>
+              <td><a href="[preferred]/commons/io/binaries/commons-io-2.16.1-bin.tar.gz">commons-io-2.16.1-bin.tar.gz</a></td>
+              <td><a href="https://downloads.apache.org/commons/io/binaries/commons-io-2.16.1-bin.tar.gz.sha512">sha512</a></td>
+              <td><a href="https://downloads.apache.org/commons/io/binaries/commons-io-2.16.1-bin.tar.gz.asc">pgp</a></td>
           </tr>
           <tr>
-              <td><a href="[preferred]/commons/io/binaries/commons-io-2.16.0-bin.zip">commons-io-2.16.0-bin.zip</a></td>
-              <td><a href="https://downloads.apache.org/commons/io/binaries/commons-io-2.16.0-bin.zip.sha512">sha512</a></td>
-              <td><a href="https://downloads.apache.org/commons/io/binaries/commons-io-2.16.0-bin.zip.asc">pgp</a></td>
+              <td><a href="[preferred]/commons/io/binaries/commons-io-2.16.1-bin.zip">commons-io-2.16.1-bin.zip</a></td>
+              <td><a href="https://downloads.apache.org/commons/io/binaries/commons-io-2.16.1-bin.zip.sha512">sha512</a></td>
+              <td><a href="https://downloads.apache.org/commons/io/binaries/commons-io-2.16.1-bin.zip.asc">pgp</a></td>
           </tr>
         </table>
       </subsection>
       <subsection name="Source">
         <table>
           <tr>
-              <td><a href="[preferred]/commons/io/source/commons-io-2.16.0-src.tar.gz">commons-io-2.16.0-src.tar.gz</a></td>
-              <td><a href="https://downloads.apache.org/commons/io/source/commons-io-2.16.0-src.tar.gz.sha512">sha512</a></td>
-              <td><a href="https://downloads.apache.org/commons/io/source/commons-io-2.16.0-src.tar.gz.asc">pgp</a></td>
+              <td><a href="[preferred]/commons/io/source/commons-io-2.16.1-src.tar.gz">commons-io-2.16.1-src.tar.gz</a></td>
+              <td><a href="https://downloads.apache.org/commons/io/source/commons-io-2.16.1-src.tar.gz.sha512">sha512</a></td>
+              <td><a href="https://downloads.apache.org/commons/io/source/commons-io-2.16.1-src.tar.gz.asc">pgp</a></td>
           </tr>
           <tr>
-              <td><a href="[preferred]/commons/io/source/commons-io-2.16.0-src.zip">commons-io-2.16.0-src.zip</a></td>
-              <td><a href="https://downloads.apache.org/commons/io/source/commons-io-2.16.0-src.zip.sha512">sha512</a></td>
-              <td><a href="https://downloads.apache.org/commons/io/source/commons-io-2.16.0-src.zip.asc">pgp</a></td>
+              <td><a href="[preferred]/commons/io/source/commons-io-2.16.1-src.zip">commons-io-2.16.1-src.zip</a></td>
+              <td><a href="https://downloads.apache.org/commons/io/source/commons-io-2.16.1-src.zip.sha512">sha512</a></td>
+              <td><a href="https://downloads.apache.org/commons/io/source/commons-io-2.16.1-src.zip.asc">pgp</a></td>
           </tr>
         </table>
       </subsection>


=====================================
src/test/java/org/apache/commons/io/FileSystemUtilsTest.java
=====================================
@@ -16,23 +16,10 @@
  */
 package org.apache.commons.io;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.time.Duration;
-import java.util.Locale;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.EnabledOnOs;
-import org.junit.jupiter.api.condition.OS;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 
@@ -42,479 +29,62 @@ import org.junit.jupiter.params.provider.MethodSource;
 @SuppressWarnings("deprecation") // testing deprecated class
 public class FileSystemUtilsTest {
 
-    static class MockFileSystemUtils extends FileSystemUtils {
-        private final int exitCode;
-        private final byte[] bytes;
-        private final String cmd;
-
-        public MockFileSystemUtils(final int exitCode, final String lines) {
-            this(exitCode, lines, null);
-        }
-
-        public MockFileSystemUtils(final int exitCode, final String lines, final String cmd) {
-            this.exitCode = exitCode;
-            this.bytes = lines.getBytes();
-            this.cmd = cmd;
-        }
-
-        @Override
-        Process openProcess(final String[] cmdArray) {
-            if (cmd != null) {
-                assertEquals(cmd, cmdArray[cmdArray.length - 1]);
-            }
-            return new Process() {
-
-                @Override
-                public void destroy() {
-                    // nnop
-                }
-
-                @Override
-                public int exitValue() {
-                    return exitCode;
-                }
-
-                @Override
-                public InputStream getErrorStream() {
-                    return null;
-                }
-
-                @Override
-                public InputStream getInputStream() {
-                    return new ByteArrayInputStream(bytes);
-                }
-
-                @Override
-                public OutputStream getOutputStream() {
-                    return null;
-                }
-
-                @Override
-                public int waitFor() throws InterruptedException {
-                    return exitCode;
-                }
-            };
-        }
-    }
-
-    private static final Duration NEG_1_TIMEOUT = Duration.ofMillis(-1);
-
     static char[] getIllegalFileNameChars() {
         return FileSystem.getCurrent().getIllegalFileNameChars();
     }
 
-    @Test
-    public void testGetFreeSpace_String() throws Exception {
-        // test coverage, as we can't check value
-        if (File.separatorChar == '/') {
-            // have to figure out UNIX block size
-            final String[] cmd;
-            String osName = System.getProperty("os.name");
-            osName = osName.toLowerCase(Locale.ENGLISH);
-
-            if (osName.contains("hp-ux") || osName.contains("aix")) {
-                cmd = new String[]{"df", "-P", "/"};
-            } else if (osName.contains("sunos") || osName.contains("sun os")
-                    || osName.contains("solaris")) {
-                cmd = new String[]{"/usr/xpg4/bin/df", "-P", "/"};
-            } else {
-                cmd = new String[]{"df", "/"};
-            }
-            final Process proc = Runtime.getRuntime().exec(cmd);
-            boolean kilobyteBlock = true;
-            try (BufferedReader r = new BufferedReader(new InputStreamReader(proc.getInputStream()))){
-                final String line = r.readLine();
-                assertNotNull(line, "Unexpected null line");
-                if (line.contains("512")) {
-                    kilobyteBlock = false;
-                }
-            }
-
-            // now perform the test
-            final long free = FileSystemUtils.freeSpace("/");
-            final long kb = FileSystemUtils.freeSpaceKb("/");
-            // Assume disk space does not fluctuate
-            // more than 1% between the above two calls;
-            // this is also small enough to verify freeSpaceKb uses
-            // kibibytes (1024) instead of SI kilobytes (1000)
-            final double acceptableDelta = kb * 0.01d;
-            if (kilobyteBlock) {
-                assertEquals(free, kb, acceptableDelta);
-            } else {
-                assertEquals(free / 2d, kb, acceptableDelta);
-            }
-        } else {
-            final long bytes = FileSystemUtils.freeSpace("");
-            final long kb = FileSystemUtils.freeSpaceKb("");
-            // Assume disk space does not fluctuate more than 1%
-            final double acceptableDelta = kb * 0.01d;
-            assertEquals((double) bytes / 1024, kb, acceptableDelta);
-        }
-    }
-
-    @Test
-    public void testGetFreeSpaceOS_String_InitError() throws Exception {
-        final FileSystemUtils fsu = new FileSystemUtils();
-        assertThrows(IllegalStateException.class, () -> fsu.freeSpaceOS("", -1, false, NEG_1_TIMEOUT));
-        assertThrows(IllegalStateException.class, () -> fsu.freeSpaceOS("", -1, true, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceOS_String_NullPath() throws Exception {
-        final FileSystemUtils fsu = new FileSystemUtils();
-        assertThrows(NullPointerException.class, () -> fsu.freeSpaceOS(null, 1, false, NEG_1_TIMEOUT));
-        assertThrows(NullPointerException.class, () -> fsu.freeSpaceOS(null, 1, true, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceOS_String_Other() throws Exception {
-        final FileSystemUtils fsu = new FileSystemUtils();
-        assertThrows(IllegalStateException.class, () -> fsu.freeSpaceOS("", 0, false, NEG_1_TIMEOUT));
-        assertThrows(NullPointerException.class, () -> fsu.freeSpaceOS(null, 1, true, NEG_1_TIMEOUT));
-        assertThrows(IllegalStateException.class, () -> fsu.freeSpaceOS("", 0, true, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceOS_String_Unix() throws Exception {
-        final FileSystemUtils fsu = new FileSystemUtils() {
-            @Override
-            protected long freeSpaceUnix(final String path, final boolean kb, final boolean posix, final Duration timeout) throws IOException {
-                return kb ? 12345L : 54321;
-            }
-        };
-        assertEquals(54321L, fsu.freeSpaceOS("", 2, false, NEG_1_TIMEOUT));
-        assertEquals(12345L, fsu.freeSpaceOS("", 2, true, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceOS_String_Windows() throws Exception {
-        final FileSystemUtils fsu = new FileSystemUtils() {
-            @Override
-            protected long freeSpaceWindows(final String path, final Duration timeout) throws IOException {
-                return 12345L;
-            }
-        };
-        assertEquals(12345L, fsu.freeSpaceOS("", 1, false, NEG_1_TIMEOUT));
-        assertEquals(12345L / 1024, fsu.freeSpaceOS("", 1, true, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceUnix_String_EmptyPath() throws Exception {
-        final String lines =
-                "Filesystem           1K-blocks      Used Available Use% Mounted on\n" +
-                        "xxx:/home/users/s     14428928  12956424   1472504  90% /home/users/s";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertThrows(IllegalArgumentException.class, () -> fsu.freeSpaceUnix("", false, false, NEG_1_TIMEOUT));
-        assertThrows(IllegalArgumentException.class, () -> fsu.freeSpaceUnix("", true, false, NEG_1_TIMEOUT));
-        assertThrows(IllegalArgumentException.class, () -> fsu.freeSpaceUnix("", true, true, NEG_1_TIMEOUT));
-        assertThrows(IllegalArgumentException.class, () -> fsu.freeSpaceUnix("", false, true, NEG_1_TIMEOUT));
-    }
-
-    @Test
-
-    public void testGetFreeSpaceUnix_String_EmptyResponse() {
-        final String lines = "";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", false, false, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", true, false, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", false, true, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", true, true, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceUnix_String_InvalidResponse1() {
-        final String lines =
-                "Filesystem           1K-blocks      Used Available Use% Mounted on\n" +
-                        "                      14428928  12956424       100";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", false, false, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", true, false, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", false, true, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", true, true, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceUnix_String_InvalidResponse2() {
-        final String lines =
-                "Filesystem           1K-blocks      Used Available Use% Mounted on\n" +
-                        "xxx:/home/users/s     14428928  12956424   nnnnnnn  90% /home/users/s";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", false, false, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", true, false, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", false, true, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", true, true, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceUnix_String_InvalidResponse3() {
-        final String lines =
-                "Filesystem           1K-blocks      Used Available Use% Mounted on\n" +
-                        "xxx:/home/users/s     14428928  12956424        -1  90% /home/users/s";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", false, false, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", true, false, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", false, true, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", true, true, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceUnix_String_InvalidResponse4() {
-        final String lines =
-                "Filesystem           1K-blocks      Used Available Use% Mounted on\n" +
-                        "xxx-yyyyyyy-zzz:/home/users/s";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", false, false, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", true, false, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", false, true, NEG_1_TIMEOUT));
-        assertThrows(IOException.class, () -> fsu.freeSpaceUnix("/home/users/s", true, true, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceUnix_String_LongResponse() throws Exception {
-        final String lines =
-                "Filesystem           1K-blocks      Used Available Use% Mounted on\n" +
-                        "xxx-yyyyyyy-zzz:/home/users/s\n" +
-                        "                      14428928  12956424   1472504  90% /home/users/s";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertEquals(1472504L, fsu.freeSpaceUnix("/home/users/s", false, false, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceUnix_String_LongResponseKb() throws Exception {
-        final String lines =
-                "Filesystem           1K-blocks      Used Available Use% Mounted on\n" +
-                        "xxx-yyyyyyy-zzz:/home/users/s\n" +
-                        "                      14428928  12956424   1472504  90% /home/users/s";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertEquals(1472504L, fsu.freeSpaceUnix("/home/users/s", true, false, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceUnix_String_NormalResponseFreeBSD() throws Exception {
-        // from Apache 'FreeBSD 6.1-RELEASE (SMP-turbo)'
-        final String lines =
-                "Filesystem  1K-blocks      Used    Avail Capacity  Mounted on\n" +
-                        "/dev/xxxxxx    128990    102902    15770    87%    /";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertEquals(15770L, fsu.freeSpaceUnix("/", false, false, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceUnix_String_NormalResponseKbFreeBSD() throws Exception {
-        // from Apache 'FreeBSD 6.1-RELEASE (SMP-turbo)'
-        // df and df -k are identical, but df -kP uses 512 blocks (not relevant as not used)
-        final String lines =
-                "Filesystem  1K-blocks      Used    Avail Capacity  Mounted on\n" +
-                        "/dev/xxxxxx    128990    102902    15770    87%    /";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertEquals(15770L, fsu.freeSpaceUnix("/", true, false, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceUnix_String_NormalResponseKbLinux() throws Exception {
-        // from Sourceforge 'GNU bash, version 2.05b.0(1)-release (i386-redhat-linux-gnu)'
-        // df, df -k and df -kP are all identical
-        final String lines =
-                "Filesystem           1K-blocks      Used Available Use% Mounted on\n" +
-                        "/dev/xxx                497944    308528    189416  62% /";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertEquals(189416L, fsu.freeSpaceUnix("/", true, false, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceUnix_String_NormalResponseKbSolaris() throws Exception {
-        // from IO-91 - ' SunOS et 5.10 Generic_118822-25 sun4u sparc SUNW,Ultra-4'
-        // non-kb response does not contain free space - see IO-91
-        final String lines =
-                "Filesystem            kbytes    used   avail capacity  Mounted on\n" +
-                        "/dev/dsk/x0x0x0x0    1350955  815754  481163    63%";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertEquals(481163L, fsu.freeSpaceUnix("/dev/dsk/x0x0x0x0", true, false, NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceUnix_String_NormalResponseLinux() throws Exception {
-        // from Sourceforge 'GNU bash, version 2.05b.0(1)-release (i386-redhat-linux-gnu)'
-        final String lines =
-                "Filesystem           1K-blocks      Used Available Use% Mounted on\n" +
-                        "/dev/xxx                497944    308528    189416  62% /";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertEquals(189416L, fsu.freeSpaceUnix("/", false, false, NEG_1_TIMEOUT));
-    }
-
-    @EnabledOnOs(value = OS.WINDOWS)
     @ParameterizedTest
     @MethodSource("getIllegalFileNameChars")
-    public void testGetFreeSpaceWindows_IllegalFileName(final char illegalFileNameChar) throws Exception {
-        assertThrows(IllegalArgumentException.class, () -> new FileSystemUtils().freeSpaceWindows("\\ \"" + illegalFileNameChar, NEG_1_TIMEOUT));
-    }
-
-    @EnabledOnOs(value = OS.WINDOWS)
-    @Test
-    public void testGetFreeSpaceWindows_IllegalFileNames() throws Exception {
-        assertThrows(IllegalArgumentException.class, () -> new FileSystemUtils().freeSpaceWindows("\\ \"", NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceWindows_String_EmptyMultiLineResponse() {
-        final String lines = "\n\n";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertThrows(IOException.class, () -> fsu.freeSpaceWindows("C:", NEG_1_TIMEOUT));
+    public void testGetFreeSpace_IllegalFileName(final char illegalFileNameChar) throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> FileSystemUtils.freeSpace("\\ \"" + illegalFileNameChar));
     }
 
     @Test
-    public void testGetFreeSpaceWindows_String_EmptyPath() throws Exception {
-        final String lines =
-                " Volume in drive C is HDD\n" +
-                        " Volume Serial Number is XXXX-YYYY\n" +
-                        "\n" +
-                        " Directory of C:\\Documents and Settings\\Xxxx\n" +
-                        "\n" +
-                        "19/08/2005  22:43    <DIR>          .\n" +
-                        "19/08/2005  22:43    <DIR>          ..\n" +
-                        "11/08/2005  01:07                81 build.properties\n" +
-                        "17/08/2005  21:44    <DIR>          Desktop\n" +
-                        "               7 File(s)         180260 bytes\n" +
-                        "              10 Dir(s)     41411551232 bytes free";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines, "dir /a /-c \"\"");
-        assertEquals(41411551232L, fsu.freeSpaceWindows("", NEG_1_TIMEOUT));
+    public void testGetFreeSpace_IllegalFileNames() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> FileSystemUtils.freeSpace("\\ \""));
     }
 
     @Test
-    public void testGetFreeSpaceWindows_String_EmptyResponse() {
-        final String lines = "";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertThrows(IOException.class, () -> fsu.freeSpaceWindows("C:", NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceWindows_String_InvalidTextResponse() {
-        final String lines = "BlueScreenOfDeath";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertThrows(IOException.class, () -> fsu.freeSpaceWindows("C:", NEG_1_TIMEOUT));
-    }
-    @Test
-    public void testGetFreeSpaceWindows_String_NormalResponse() throws Exception {
-        final String lines =
-                " Volume in drive C is HDD\n" +
-                        " Volume Serial Number is XXXX-YYYY\n" +
-                        "\n" +
-                        " Directory of C:\\Documents and Settings\\Xxxx\n" +
-                        "\n" +
-                        "19/08/2005  22:43    <DIR>          .\n" +
-                        "19/08/2005  22:43    <DIR>          ..\n" +
-                        "11/08/2005  01:07                81 build.properties\n" +
-                        "17/08/2005  21:44    <DIR>          Desktop\n" +
-                        "               7 File(s)         180260 bytes\n" +
-                        "              10 Dir(s)     41411551232 bytes free";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines, "dir /a /-c \"C:\"");
-        assertEquals(41411551232L, fsu.freeSpaceWindows("C:", NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceWindows_String_NoSuchDirectoryResponse() {
-        final String lines =
-                " Volume in drive C is HDD\n" +
-                        " Volume Serial Number is XXXX-YYYY\n" +
-                        "\n" +
-                        " Directory of C:\\Documents and Settings\\empty" +
-                        "\n";
-        final FileSystemUtils fsu = new MockFileSystemUtils(1, lines);
-        assertThrows(IOException.class, () -> fsu.freeSpaceWindows("C:", NEG_1_TIMEOUT));
-    }
-
-    @Test
-    public void testGetFreeSpaceWindows_String_ParseCommaFormatBytes() throws Exception {
-        // this is the format of response when calling dir /c
-        // we have now switched to dir /-c, so we should never get this
-        final String lines =
-                " Volume in drive C is HDD\n" +
-                        " Volume Serial Number is XXXX-YYYY\n" +
-                        "\n" +
-                        " Directory of C:\\Documents and Settings\\Xxxx\n" +
-                        "\n" +
-                        "19/08/2005  22:43    <DIR>          .\n" +
-                        "19/08/2005  22:43    <DIR>          ..\n" +
-                        "11/08/2005  01:07                81 build.properties\n" +
-                        "17/08/2005  21:44    <DIR>          Desktop\n" +
-                        "               7 File(s)        180,260 bytes\n" +
-                        "              10 Dir(s)  41,411,551,232 bytes free";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertEquals(41411551232L, fsu.freeSpaceWindows("", NEG_1_TIMEOUT));
+    public void testGetFreeSpace_String() throws Exception {
+        assertThrows(NullPointerException.class, () -> FileSystemUtils.freeSpace(null));
+        assertThrows(IllegalArgumentException.class, () -> FileSystemUtils.freeSpace("this directory does not exist, at all."));
+        // "" means current dir.
+        assertTrue(FileSystemUtils.freeSpace("") > 0);
+        assertTrue(FileSystemUtils.freeSpace("target") > 0);
+        // files worked as well in previous versions.
+        assertTrue(FileSystemUtils.freeSpace("pom.xml") > 0);
     }
 
     @Test
-    public void testGetFreeSpaceWindows_String_ParseCommaFormatBytes_Big() throws Exception {
-        // test with very large free space
-        final String lines =
-                " Volume in drive C is HDD\n" +
-                        " Volume Serial Number is XXXX-YYYY\n" +
-                        "\n" +
-                        " Directory of C:\\Documents and Settings\\Xxxx\n" +
-                        "\n" +
-                        "19/08/2005  22:43    <DIR>          .\n" +
-                        "19/08/2005  22:43    <DIR>          ..\n" +
-                        "11/08/2005  01:07                81 build.properties\n" +
-                        "17/08/2005  21:44    <DIR>          Desktop\n" +
-                        "               7 File(s)        180,260 bytes\n" +
-                        "              10 Dir(s)  141,411,551,232 bytes free";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertEquals(141411551232L, fsu.freeSpaceWindows("", NEG_1_TIMEOUT));
+    public void testGetFreeSpaceKb() throws Exception {
+        assertTrue(FileSystemUtils.freeSpaceKb() > 0);
     }
 
     @Test
-    public void testGetFreeSpaceWindows_String_ParseCommaFormatBytes_Small() throws Exception {
-        // test with very large free space
-        final String lines =
-                " Volume in drive C is HDD\n" +
-                        " Volume Serial Number is XXXX-YYYY\n" +
-                        "\n" +
-                        " Directory of C:\\Documents and Settings\\Xxxx\n" +
-                        "\n" +
-                        "19/08/2005  22:43    <DIR>          .\n" +
-                        "19/08/2005  22:43    <DIR>          ..\n" +
-                        "11/08/2005  01:07                81 build.properties\n" +
-                        "17/08/2005  21:44    <DIR>          Desktop\n" +
-                        "               7 File(s)        180,260 bytes\n" +
-                        "              10 Dir(s)  1,232 bytes free";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines);
-        assertEquals(1232L, fsu.freeSpaceWindows("", NEG_1_TIMEOUT));
+    public void testGetFreeSpaceKb_long() throws Exception {
+        assertTrue(FileSystemUtils.freeSpaceKb(0) > 0);
     }
 
     @Test
-    public void testGetFreeSpaceWindows_String_quoted() throws Exception {
-        final String lines =
-                " Volume in drive C is HDD\n" +
-                        " Volume Serial Number is XXXX-YYYY\n" +
-                        "\n" +
-                        " Directory of C:\\Documents and Settings\\Xxxx\n" +
-                        "\n" +
-                        "19/08/2005  22:43    <DIR>          .\n" +
-                        "19/08/2005  22:43    <DIR>          ..\n" +
-                        "11/08/2005  01:07                81 build.properties\n" +
-                        "17/08/2005  21:44    <DIR>          Desktop\n" +
-                        "               7 File(s)         180260 bytes\n" +
-                        "              10 Dir(s)     41411551232 bytes free";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines, "dir /a /-c \"C:\\somedir\"");
-        assertEquals(41411551232L, fsu.freeSpaceWindows("\"C:\\somedir\"", NEG_1_TIMEOUT));
+    public void testGetFreeSpaceKb_String() throws Exception {
+        assertThrows(NullPointerException.class, () -> FileSystemUtils.freeSpaceKb(null));
+        assertThrows(IllegalArgumentException.class, () -> FileSystemUtils.freeSpaceKb("this directory does not exist, at all."));
+        // "" means current dir.
+        assertTrue(FileSystemUtils.freeSpaceKb("") > 0);
+        assertTrue(FileSystemUtils.freeSpaceKb("target") > 0);
+        // files worked as well in previous versions.
+        assertTrue(FileSystemUtils.freeSpaceKb("pom.xml") > 0);
     }
 
     @Test
-    public void testGetFreeSpaceWindows_String_StripDrive() throws Exception {
-        final String lines =
-                " Volume in drive C is HDD\n" +
-                        " Volume Serial Number is XXXX-YYYY\n" +
-                        "\n" +
-                        " Directory of C:\\Documents and Settings\\Xxxx\n" +
-                        "\n" +
-                        "19/08/2005  22:43    <DIR>          .\n" +
-                        "19/08/2005  22:43    <DIR>          ..\n" +
-                        "11/08/2005  01:07                81 build.properties\n" +
-                        "17/08/2005  21:44    <DIR>          Desktop\n" +
-                        "               7 File(s)         180260 bytes\n" +
-                        "              10 Dir(s)     41411551232 bytes free";
-        final FileSystemUtils fsu = new MockFileSystemUtils(0, lines, "dir /a /-c \"C:\\somedir\"");
-        assertEquals(41411551232L, fsu.freeSpaceWindows("C:\\somedir", NEG_1_TIMEOUT));
+    public void testGetFreeSpaceKb_String_long() throws Exception {
+        assertThrows(NullPointerException.class, () -> FileSystemUtils.freeSpaceKb(null, 0));
+        assertThrows(IllegalArgumentException.class, () -> FileSystemUtils.freeSpaceKb("this directory does not exist, at all.", 0));
+        // "" means current dir.
+        assertTrue(FileSystemUtils.freeSpaceKb("", 0) > 0);
+        assertTrue(FileSystemUtils.freeSpaceKb("target", 0) > 0);
+        // files worked as well in previous versions.
+        assertTrue(FileSystemUtils.freeSpaceKb("pom.xml", 0) > 0);
     }
 
 }


=====================================
src/test/java/org/apache/commons/io/FileUtilsTest.java
=====================================
@@ -1414,9 +1414,7 @@ public class FileUtilsTest extends AbstractTempDirTest {
 
     @Test
     public void testCopyToDirectoryWithIterableSourceDoesNotExist() {
-        assertThrows(IOException.class,
-                () -> FileUtils.copyToDirectory(Collections.singleton(new File(tempDirFile, "doesNotExists")),
-                        tempDirFile));
+        assertThrows(IOException.class, () -> FileUtils.copyToDirectory(Collections.singleton(new File(tempDirFile, "doesNotExists")), tempDirFile));
     }
 
     @Test


=====================================
src/test/java/org/apache/commons/io/file/AbstractPathWrapper.java
=====================================
@@ -81,11 +81,6 @@ public abstract class AbstractPathWrapper implements Path {
         return Objects.equals(path, other.path);
     }
 
-    @Override
-    public void forEach(final Consumer<? super Path> action) {
-        path.forEach(action);
-    }
-
     /**
      * Delegates to {@link Files#exists(Path, LinkOption...)}.
      *
@@ -96,6 +91,11 @@ public abstract class AbstractPathWrapper implements Path {
         return Files.exists(path, options);
     }
 
+    @Override
+    public void forEach(final Consumer<? super Path> action) {
+        path.forEach(action);
+    }
+
     /**
      * Gets the delegate Path.
      *


=====================================
src/test/java/org/apache/commons/io/file/AbstractTempDirTest.java
=====================================
@@ -31,6 +31,26 @@ import org.junit.jupiter.api.io.TempDir;
  */
 public abstract class AbstractTempDirTest {
 
+    protected static final String SUB_DIR = "subdir";
+    protected static final String SYMLINKED_DIR = "symlinked-dir";
+
+    /**
+     * Creates directory test fixtures in the given directory {@code rootDir}.
+     * <ol>
+     * <li>{@code rootDir/subdir}</li>
+     * <li>{@code rootDir/symlinked-dir} -> {@code rootDir/subdir}</li>
+     * </ol>
+     * @param rootDir Root for directory entries.
+     * @return Path for {@code tempDirPath/subdir}.
+     * @throws IOException if an I/O error occurs or the parent directory does not exist.
+     */
+    protected static Path createTempSymlinkedRelativeDir(final Path rootDir) throws IOException {
+        final Path targetDir = rootDir.resolve(SUB_DIR);
+        final Path symlinkDir = rootDir.resolve(SYMLINKED_DIR);
+        Files.createDirectory(targetDir);
+        return Files.createSymbolicLink(symlinkDir, targetDir);
+    }
+
     /**
      * A temporary directory managed by JUnit.
      */
@@ -38,14 +58,14 @@ public abstract class AbstractTempDirTest {
     public Path managedTempDirPath;
 
     /**
-     * A temporary directory managed by each test so we can optionally fiddle with its permissions independently.
+     * A File version of this test's Path object.
      */
-    public Path tempDirPath;
+    public File tempDirFile;
 
     /**
-     * A File version of this test's Path object.
+     * A temporary directory managed by each test so we can optionally fiddle with its permissions independently.
      */
-    public File tempDirFile;
+    public Path tempDirPath;
 
     @BeforeEach
     public void beforeEachCreateTempDirs() throws IOException {


=====================================
src/test/java/org/apache/commons/io/file/AccumulatorPathVisitorTest.java
=====================================
@@ -19,6 +19,7 @@ package org.apache.commons.io.file;
 
 import static org.apache.commons.io.file.CounterAssertions.assertCounts;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.IOException;
@@ -111,6 +112,25 @@ public class AccumulatorPathVisitorTest {
         assertEquals(accPathVisitor.hashCode(), accPathVisitor.hashCode());
     }
 
+    @Test
+    public void testEqualsHashCode() {
+        final AccumulatorPathVisitor visitor0 = AccumulatorPathVisitor.withLongCounters();
+        final AccumulatorPathVisitor visitor1 = AccumulatorPathVisitor.withLongCounters();
+        assertEquals(visitor0, visitor0);
+        assertEquals(visitor0, visitor1);
+        assertEquals(visitor1, visitor0);
+        assertEquals(visitor0.hashCode(), visitor0.hashCode());
+        assertEquals(visitor0.hashCode(), visitor1.hashCode());
+        assertEquals(visitor1.hashCode(), visitor0.hashCode());
+        visitor0.getPathCounters().getByteCounter().increment();
+        assertEquals(visitor0, visitor0);
+        assertNotEquals(visitor0, visitor1);
+        assertNotEquals(visitor1, visitor0);
+        assertEquals(visitor0.hashCode(), visitor0.hashCode());
+        assertNotEquals(visitor0.hashCode(), visitor1.hashCode());
+        assertNotEquals(visitor1.hashCode(), visitor0.hashCode());
+    }
+
     /**
      * Tests a directory with one file of size 0.
      */


=====================================
src/test/java/org/apache/commons/io/file/CleaningPathVisitorTest.java
=====================================
@@ -29,6 +29,7 @@ import java.nio.file.Paths;
 
 import org.apache.commons.io.file.Counters.PathCounters;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
@@ -136,4 +137,23 @@ public class CleaningPathVisitorTest extends TestArguments {
         assertEquals(visitFileTree, visitFileTree);
         assertEquals(visitFileTree.hashCode(), visitFileTree.hashCode());
     }
+
+    @Test
+    public void testEqualsHashCode() {
+        final CountingPathVisitor visitor0 = CleaningPathVisitor.withLongCounters();
+        final CountingPathVisitor visitor1 = CleaningPathVisitor.withLongCounters();
+        assertEquals(visitor0, visitor0);
+        assertEquals(visitor0, visitor1);
+        assertEquals(visitor1, visitor0);
+        assertEquals(visitor0.hashCode(), visitor0.hashCode());
+        assertEquals(visitor0.hashCode(), visitor1.hashCode());
+        assertEquals(visitor1.hashCode(), visitor0.hashCode());
+        visitor0.getPathCounters().getByteCounter().increment();
+        assertEquals(visitor0, visitor0);
+        assertNotEquals(visitor0, visitor1);
+        assertNotEquals(visitor1, visitor0);
+        assertEquals(visitor0.hashCode(), visitor0.hashCode());
+        assertNotEquals(visitor0.hashCode(), visitor1.hashCode());
+        assertNotEquals(visitor1.hashCode(), visitor0.hashCode());
+    }
 }


=====================================
src/test/java/org/apache/commons/io/file/DeletingPathVisitorTest.java
=====================================
@@ -18,28 +18,32 @@
 package org.apache.commons.io.file;
 
 import static org.apache.commons.io.file.CounterAssertions.assertCounts;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
 import org.apache.commons.io.file.Counters.PathCounters;
 import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 
 /**
  * Tests {@link DeletingPathVisitor}.
  */
-public class DeletingPathVisitorTest extends TestArguments {
+public class DeletingPathVisitorTest extends AbstractTempDirTest {
 
-    @TempDir
-    private Path tempDir;
+    private static final String ARGS = "org.apache.commons.io.file.TestArguments#";
 
     private void applyDeleteEmptyDirectory(final DeletingPathVisitor visitor) throws IOException {
-        Files.walkFileTree(tempDir, visitor);
+        Files.walkFileTree(tempDirPath, visitor);
         assertCounts(1, 0, 0, visitor);
     }
 
@@ -47,59 +51,59 @@ public class DeletingPathVisitorTest extends TestArguments {
      * Tests an empty folder.
      */
     @ParameterizedTest
-    @MethodSource("deletingPathVisitors")
+    @MethodSource(ARGS + "deletingPathVisitors")
     public void testDeleteEmptyDirectory(final DeletingPathVisitor visitor) throws IOException {
         applyDeleteEmptyDirectory(visitor);
         // This will throw if not empty.
-        Files.deleteIfExists(tempDir);
+        Files.deleteIfExists(tempDirPath);
     }
 
     /**
      * Tests an empty folder.
      */
     @ParameterizedTest
-    @MethodSource("pathCounters")
+    @MethodSource(ARGS + "pathCounters")
     public void testDeleteEmptyDirectoryNullCtorArg(final PathCounters pathCounters) throws IOException {
         applyDeleteEmptyDirectory(new DeletingPathVisitor(pathCounters, (String[]) null));
         // This will throw if not empty.
-        Files.deleteIfExists(tempDir);
+        Files.deleteIfExists(tempDirPath);
     }
 
     /**
      * Tests a directory with one file of size 0.
      */
     @ParameterizedTest
-    @MethodSource("deletingPathVisitors")
+    @MethodSource(ARGS + "deletingPathVisitors")
     public void testDeleteFolders1FileSize0(final DeletingPathVisitor visitor) throws IOException {
-        PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0"), tempDir);
-        assertCounts(1, 1, 0, PathUtils.visitFileTree(visitor, tempDir));
+        PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0"), tempDirPath);
+        assertCounts(1, 1, 0, PathUtils.visitFileTree(visitor, tempDirPath));
         // This will throw if not empty.
-        Files.deleteIfExists(tempDir);
+        Files.deleteIfExists(tempDirPath);
     }
 
     /**
      * Tests a directory with one file of size 1.
      */
     @ParameterizedTest
-    @MethodSource("deletingPathVisitors")
+    @MethodSource(ARGS + "deletingPathVisitors")
     public void testDeleteFolders1FileSize1(final DeletingPathVisitor visitor) throws IOException {
-        PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1"), tempDir);
-        assertCounts(1, 1, 1, PathUtils.visitFileTree(visitor, tempDir));
+        PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1"), tempDirPath);
+        assertCounts(1, 1, 1, PathUtils.visitFileTree(visitor, tempDirPath));
         // This will throw if not empty.
-        Files.deleteIfExists(tempDir);
+        Files.deleteIfExists(tempDirPath);
     }
 
     /**
      * Tests a directory with one file of size 1 but skip that file.
      */
     @ParameterizedTest
-    @MethodSource("pathCounters")
+    @MethodSource(ARGS + "pathCounters")
     public void testDeleteFolders1FileSize1Skip(final PathCounters pathCounters) throws IOException {
-        PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1"), tempDir);
+        PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1"), tempDirPath);
         final String skipFileName = "file-size-1.bin";
         final CountingPathVisitor visitor = new DeletingPathVisitor(pathCounters, skipFileName);
-        assertCounts(1, 1, 1, PathUtils.visitFileTree(visitor, tempDir));
-        final Path skippedFile = tempDir.resolve(skipFileName);
+        assertCounts(1, 1, 1, PathUtils.visitFileTree(visitor, tempDirPath));
+        final Path skippedFile = tempDirPath.resolve(skipFileName);
         Assertions.assertTrue(Files.exists(skippedFile));
         Files.delete(skippedFile);
     }
@@ -108,11 +112,68 @@ public class DeletingPathVisitorTest extends TestArguments {
      * Tests a directory with two subdirectories, each containing one file of size 1.
      */
     @ParameterizedTest
-    @MethodSource("deletingPathVisitors")
+    @MethodSource(ARGS + "deletingPathVisitors")
     public void testDeleteFolders2FileSize2(final DeletingPathVisitor visitor) throws IOException {
-        PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2"), tempDir);
-        assertCounts(3, 2, 2, PathUtils.visitFileTree(visitor, tempDir));
+        PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2"), tempDirPath);
+        assertCounts(3, 2, 2, PathUtils.visitFileTree(visitor, tempDirPath));
         // This will throw if not empty.
-        Files.deleteIfExists(tempDir);
+        Files.deleteIfExists(tempDirPath);
+    }
+
+    @Test
+    public void testEqualsHashCode() {
+        final DeletingPathVisitor visitor0 = DeletingPathVisitor.withLongCounters();
+        final DeletingPathVisitor visitor1 = DeletingPathVisitor.withLongCounters();
+        assertEquals(visitor0, visitor0);
+        assertEquals(visitor0, visitor1);
+        assertEquals(visitor1, visitor0);
+        assertEquals(visitor0.hashCode(), visitor0.hashCode());
+        assertEquals(visitor0.hashCode(), visitor1.hashCode());
+        assertEquals(visitor1.hashCode(), visitor0.hashCode());
+        visitor0.getPathCounters().getByteCounter().increment();
+        assertEquals(visitor0, visitor0);
+        assertNotEquals(visitor0, visitor1);
+        assertNotEquals(visitor1, visitor0);
+        assertEquals(visitor0.hashCode(), visitor0.hashCode());
+        assertNotEquals(visitor0.hashCode(), visitor1.hashCode());
+        assertNotEquals(visitor1.hashCode(), visitor0.hashCode());
+    }
+
+    /**
+     * Tests https://issues.apache.org/jira/browse/IO-850
+     */
+    @Test
+    public void testIO850DirectoriesAndFiles() throws IOException {
+        final Path rootDir = Files.createDirectory(managedTempDirPath.resolve("IO850"));
+        createTempSymlinkedRelativeDir(rootDir);
+        final Path targetDir = rootDir.resolve(SUB_DIR);
+        final Path symlinkDir = rootDir.resolve(SYMLINKED_DIR);
+        Files.write(targetDir.resolve("file0.txt"), "Hello".getBytes(StandardCharsets.UTF_8));
+        final Path subDir0 = Files.createDirectory(targetDir.resolve("subDir0"));
+        Files.write(subDir0.resolve("file1.txt"), "Hello".getBytes(StandardCharsets.UTF_8));
+        final DeletingPathVisitor visitor = DeletingPathVisitor.withLongCounters();
+        Files.walkFileTree(rootDir, visitor);
+        assertFalse(Files.exists(targetDir));
+        assertFalse(Files.exists(symlinkDir));
+        assertFalse(Files.exists(rootDir));
+        assertTrue(visitor.getPathCounters().getDirectoryCounter().get() > 0);
+        assertTrue(visitor.getPathCounters().getFileCounter().get() > 0);
+    }
+
+    /**
+     * Tests https://issues.apache.org/jira/browse/IO-850
+     */
+    @Test
+    public void testIO850DirectoriesOnly() throws IOException {
+        final Path rootDir = Files.createDirectory(managedTempDirPath.resolve("IO850"));
+        createTempSymlinkedRelativeDir(rootDir);
+        final Path targetDir = rootDir.resolve(SUB_DIR);
+        final Path symlinkDir = rootDir.resolve(SYMLINKED_DIR);
+        final DeletingPathVisitor visitor = DeletingPathVisitor.withLongCounters();
+        Files.walkFileTree(rootDir, visitor);
+        assertFalse(Files.exists(targetDir));
+        assertFalse(Files.exists(symlinkDir));
+        assertFalse(Files.exists(rootDir));
+        assertTrue(visitor.getPathCounters().getDirectoryCounter().get() > 0);
     }
 }


=====================================
src/test/java/org/apache/commons/io/file/PathUtilsDeleteFileTest.java
=====================================
@@ -32,44 +32,23 @@ import java.nio.file.Paths;
 
 import org.apache.commons.io.file.Counters.PathCounters;
 import org.apache.commons.lang3.SystemUtils;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 /**
  * Tests {@link DeletingPathVisitor}.
  */
-public class PathUtilsDeleteFileTest {
-
-    private Path tempDir;
-
-    @AfterEach
-    public void afterEach() throws IOException {
-        // backstop
-        if (Files.exists(tempDir) && PathUtils.isEmptyDirectory(tempDir)) {
-            Files.deleteIfExists(tempDir);
-        }
-    }
-
-    @BeforeEach
-    public void beforeEach() throws IOException {
-        tempDir = Files.createTempDirectory(getClass().getCanonicalName());
-    }
+public class PathUtilsDeleteFileTest extends AbstractTempDirTest {
 
     @Test
     public void testDeleteBrokenLink() throws IOException {
         assumeFalse(SystemUtils.IS_OS_WINDOWS);
-
-        final Path missingFile = tempDir.resolve("missing.txt");
-        final Path brokenLink = tempDir.resolve("broken.txt");
+        final Path missingFile = tempDirPath.resolve("missing.txt");
+        final Path brokenLink = tempDirPath.resolve("broken.txt");
         Files.createSymbolicLink(brokenLink, missingFile);
-
         assertTrue(Files.exists(brokenLink, LinkOption.NOFOLLOW_LINKS));
         assertFalse(Files.exists(missingFile, LinkOption.NOFOLLOW_LINKS));
-
         PathUtils.deleteFile(brokenLink);
-
         assertFalse(Files.exists(brokenLink, LinkOption.NOFOLLOW_LINKS), "Symbolic link not removed");
     }
 
@@ -79,10 +58,10 @@ public class PathUtilsDeleteFileTest {
     @Test
     public void testDeleteFileDirectory1FileSize0() throws IOException {
         final String fileName = "file-size-0.bin";
-        PathUtils.copyFileToDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0/" + fileName), tempDir);
-        assertCounts(0, 1, 0, PathUtils.deleteFile(tempDir.resolve(fileName)));
+        PathUtils.copyFileToDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0/" + fileName), tempDirPath);
+        assertCounts(0, 1, 0, PathUtils.deleteFile(tempDirPath.resolve(fileName)));
         // This will throw if not empty.
-        Files.deleteIfExists(tempDir);
+        Files.deleteIfExists(tempDirPath);
     }
 
     /**
@@ -91,10 +70,10 @@ public class PathUtilsDeleteFileTest {
     @Test
     public void testDeleteFileDirectory1FileSize1() throws IOException {
         final String fileName = "file-size-1.bin";
-        PathUtils.copyFileToDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/" + fileName), tempDir);
-        assertCounts(0, 1, 1, PathUtils.deleteFile(tempDir.resolve(fileName)));
+        PathUtils.copyFileToDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/" + fileName), tempDirPath);
+        assertCounts(0, 1, 1, PathUtils.deleteFile(tempDirPath.resolve(fileName)));
         // This will throw if not empty.
-        Files.deleteIfExists(tempDir);
+        Files.deleteIfExists(tempDirPath);
     }
 
     /**
@@ -102,9 +81,9 @@ public class PathUtilsDeleteFileTest {
      */
     @Test
     public void testDeleteFileDoesNotExist() throws IOException {
-        testDeleteFileEmpty(PathUtils.deleteFile(tempDir.resolve("file-does-not-exist.bin")));
+        testDeleteFileEmpty(PathUtils.deleteFile(tempDirPath.resolve("file-does-not-exist.bin")));
         // This will throw if not empty.
-        Files.deleteIfExists(tempDir);
+        Files.deleteIfExists(tempDirPath);
     }
 
     private void testDeleteFileEmpty(final PathCounters pathCounts) {
@@ -116,9 +95,9 @@ public class PathUtilsDeleteFileTest {
      */
     @Test
     public void testDeleteFileEmptyDirectory() throws IOException {
-        Assertions.assertThrows(NoSuchFileException.class, () -> testDeleteFileEmpty(PathUtils.deleteFile(tempDir)));
+        Assertions.assertThrows(NoSuchFileException.class, () -> testDeleteFileEmpty(PathUtils.deleteFile(tempDirPath)));
         // This will throw if not empty.
-        Files.deleteIfExists(tempDir);
+        Files.deleteIfExists(tempDirPath);
     }
 
     /**
@@ -127,8 +106,8 @@ public class PathUtilsDeleteFileTest {
     @Test
     public void testDeleteReadOnlyFileDirectory1FileSize1() throws IOException {
         final String fileName = "file-size-1.bin";
-        PathUtils.copyFileToDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/" + fileName), tempDir);
-        final Path resolved = tempDir.resolve(fileName);
+        PathUtils.copyFileToDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/" + fileName), tempDirPath);
+        final Path resolved = tempDirPath.resolve(fileName);
         PathUtils.setReadOnly(resolved, true);
         if (SystemUtils.IS_OS_WINDOWS) {
             // Fails on Windows's Ubuntu subsystem.
@@ -137,7 +116,7 @@ public class PathUtilsDeleteFileTest {
         }
         assertCounts(0, 1, 1, PathUtils.deleteFile(resolved, StandardDeleteOption.OVERRIDE_READ_ONLY));
         // This will throw if not empty.
-        Files.deleteIfExists(tempDir);
+        Files.deleteIfExists(tempDirPath);
     }
 
     /**
@@ -146,8 +125,8 @@ public class PathUtilsDeleteFileTest {
     @Test
     public void testSetReadOnlyFileDirectory1FileSize1() throws IOException {
         final String fileName = "file-size-1.bin";
-        PathUtils.copyFileToDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/" + fileName), tempDir);
-        final Path resolved = tempDir.resolve(fileName);
+        PathUtils.copyFileToDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/" + fileName), tempDirPath);
+        final Path resolved = tempDirPath.resolve(fileName);
         PathUtils.setReadOnly(resolved, true);
         if (SystemUtils.IS_OS_WINDOWS) {
             // Fails on Windows's Ubuntu subsystem.
@@ -157,6 +136,6 @@ public class PathUtilsDeleteFileTest {
         PathUtils.setReadOnly(resolved, false);
         PathUtils.deleteFile(resolved);
         // This will throw if not empty.
-        Files.deleteIfExists(tempDir);
+        Files.deleteIfExists(tempDirPath);
     }
 }


=====================================
src/test/java/org/apache/commons/io/file/PathUtilsTest.java
=====================================
@@ -73,24 +73,6 @@ public class PathUtilsTest extends AbstractTempDirTest {
 
     private static final String PATH_FIXTURE = "NOTICE.txt";
 
-    /**
-     * Creates directory test fixtures.
-     * <ol>
-     * <li>tempDirPath/subdir</li>
-     * <li>tempDirPath/symlinked-dir -> tempDirPath/subdir</li>
-     * </ol>
-     *
-     * @return Path to tempDirPath/subdir
-     * @throws IOException if an I/O error occurs or the parent directory does not exist.
-     */
-    private Path createTempSymlinkedRelativeDir() throws IOException {
-        final Path targetDir = tempDirPath.resolve("subdir");
-        final Path symlinkDir = tempDirPath.resolve("symlinked-dir");
-        Files.createDirectory(targetDir);
-        Files.createSymbolicLink(symlinkDir, targetDir);
-        return symlinkDir;
-    }
-
     private Path current() {
         return PathUtils.current();
     }
@@ -234,7 +216,7 @@ public class PathUtilsTest extends AbstractTempDirTest {
 
     @Test
     public void testCreateDirectoriesSymlink() throws IOException {
-        final Path symlinkedDir = createTempSymlinkedRelativeDir();
+        final Path symlinkedDir = createTempSymlinkedRelativeDir(tempDirPath);
         final String leafDirName = "child";
         final Path newDirFollowed = PathUtils.createParentDirectories(symlinkedDir.resolve(leafDirName), PathUtils.NULL_LINK_OPTION);
         assertEquals(Files.readSymbolicLink(symlinkedDir), newDirFollowed);
@@ -242,7 +224,7 @@ public class PathUtilsTest extends AbstractTempDirTest {
 
     @Test
     public void testCreateDirectoriesSymlinkClashing() throws IOException {
-        final Path symlinkedDir = createTempSymlinkedRelativeDir();
+        final Path symlinkedDir = createTempSymlinkedRelativeDir(tempDirPath);
         assertEquals(symlinkedDir, PathUtils.createParentDirectories(symlinkedDir.resolve("child")));
     }
 
@@ -428,7 +410,7 @@ public class PathUtilsTest extends AbstractTempDirTest {
 
     @Test
     public void testNewOutputStreamNewFileInsideExistingSymlinkedDir() throws IOException {
-        final Path symlinkDir = createTempSymlinkedRelativeDir();
+        final Path symlinkDir = createTempSymlinkedRelativeDir(tempDirPath);
         final Path file = symlinkDir.resolve("test.txt");
         try (OutputStream outputStream = PathUtils.newOutputStream(file, new LinkOption[] {})) {
             // empty


=====================================
src/test/java/org/apache/commons/io/input/BoundedInputStreamTest.java
=====================================
@@ -27,6 +27,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.mutable.MutableInt;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
@@ -36,10 +37,12 @@ import org.junit.jupiter.params.provider.ValueSource;
  */
 public class BoundedInputStreamTest {
 
-    private void compare(final String msg, final byte[] expected, final byte[] actual) {
-        assertEquals(expected.length, actual.length, msg + " length");
+    private void compare(final String message, final byte[] expected, final byte[] actual) {
+        assertEquals(expected.length, actual.length, () -> message + " (array length equals check)");
+        final MutableInt mi = new MutableInt();
         for (int i = 0; i < expected.length; i++) {
-            assertEquals(expected[i], actual[i], msg + " byte[" + i + "]");
+            mi.setValue(i);
+            assertEquals(expected[i], actual[i], () -> message + " byte[" + mi + "]");
         }
     }
 
@@ -61,6 +64,7 @@ public class BoundedInputStreamTest {
         // limit = length
         try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld)).setCount(startCount)
                 .setMaxCount(helloWorld.length).get()) {
+            assertTrue(bounded.markSupported());
             assertEquals(helloWorld.length, bounded.getMaxCount());
             assertEquals(helloWorld.length, bounded.getMaxLength());
             assertEquals(actualStart, bounded.getCount());
@@ -85,11 +89,14 @@ public class BoundedInputStreamTest {
             assertEquals(readCount + actualStart, bounded.getCount());
             assertEquals(0, bounded.getRemaining());
             assertEquals(0, bounded.available());
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
         // limit > length
         final int maxCountP1 = helloWorld.length + 1;
         try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld)).setCount(startCount)
                 .setMaxCount(maxCountP1).get()) {
+            assertTrue(bounded.markSupported());
             assertEquals(maxCountP1, bounded.getMaxLength());
             assertEquals(actualStart, bounded.getCount());
             assertEquals(Math.max(0, bounded.getMaxCount() - actualStart), bounded.getRemaining());
@@ -113,9 +120,12 @@ public class BoundedInputStreamTest {
             assertEquals(maxCountP1, bounded.getMaxLength());
             assertEquals(readCount + actualStart, bounded.getCount());
             assertEquals(Math.max(0, maxCountP1 - bounded.getCount()), bounded.getRemaining());
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
         // limit < length
         try (BoundedInputStream bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), hello.length)) {
+            assertTrue(bounded.markSupported());
             assertEquals(hello.length, bounded.getMaxLength());
             assertEquals(0, bounded.getCount());
             assertEquals(bounded.getMaxLength(), bounded.getRemaining());
@@ -132,6 +142,107 @@ public class BoundedInputStreamTest {
             assertEquals(hello.length, bounded.getMaxLength());
             assertEquals(readCount, bounded.getCount());
             assertEquals(bounded.getMaxLength() - readCount, bounded.getRemaining());
+            // should be invariant
+            assertTrue(bounded.markSupported());
+        }
+    }
+
+    @Test
+    public void testMarkReset() throws Exception {
+        final byte[] helloWorld = "Hello World".getBytes(StandardCharsets.UTF_8);
+        final int helloWorldLen = helloWorld.length;
+        final byte[] hello = "Hello".getBytes(StandardCharsets.UTF_8);
+        final byte[] world = " World".getBytes(StandardCharsets.UTF_8);
+        final int helloLen = hello.length;
+        // limit = -1
+        try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld)).get()) {
+            assertTrue(bounded.markSupported());
+            bounded.mark(0);
+            compare("limit = -1", helloWorld, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+            // again
+            bounded.reset();
+            compare("limit = -1", hello, IOUtils.toByteArray(bounded, helloLen));
+            bounded.mark(helloWorldLen);
+            compare("limit = -1", world, IOUtils.toByteArray(bounded));
+            bounded.reset();
+            compare("limit = -1", world, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+        }
+        // limit = 0
+        try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld)).setMaxCount(0).get()) {
+            assertTrue(bounded.markSupported());
+            bounded.mark(0);
+            compare("limit = 0", IOUtils.EMPTY_BYTE_ARRAY, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+            // again
+            bounded.reset();
+            compare("limit = 0", IOUtils.EMPTY_BYTE_ARRAY, IOUtils.toByteArray(bounded));
+            bounded.mark(helloWorldLen);
+            compare("limit = 0", IOUtils.EMPTY_BYTE_ARRAY, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+        }
+        // limit = length
+        try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld))
+                .setMaxCount(helloWorld.length).get()) {
+            assertTrue(bounded.markSupported());
+            bounded.mark(0);
+            compare("limit = length", helloWorld, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+            // again
+            bounded.reset();
+            compare("limit = length", hello, IOUtils.toByteArray(bounded, helloLen));
+            bounded.mark(helloWorldLen);
+            compare("limit = length", world, IOUtils.toByteArray(bounded));
+            bounded.reset();
+            compare("limit = length", world, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+        }
+        // limit > length
+        try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld))
+                .setMaxCount(helloWorld.length + 1).get()) {
+            assertTrue(bounded.markSupported());
+            bounded.mark(0);
+            compare("limit > length", helloWorld, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+            // again
+            bounded.reset();
+            compare("limit > length", helloWorld, IOUtils.toByteArray(bounded));
+            bounded.reset();
+            compare("limit > length", hello, IOUtils.toByteArray(bounded, helloLen));
+            bounded.mark(helloWorldLen);
+            compare("limit > length", world, IOUtils.toByteArray(bounded));
+            bounded.reset();
+            compare("limit > length", world, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+        }
+        // limit < length
+        try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld))
+                .setMaxCount(helloWorld.length - (hello.length + 1)).get()) {
+            assertTrue(bounded.markSupported());
+            bounded.mark(0);
+            compare("limit < length", hello, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+            // again
+            bounded.reset();
+            compare("limit < length", hello, IOUtils.toByteArray(bounded));
+            bounded.reset();
+            compare("limit < length", hello, IOUtils.toByteArray(bounded, helloLen));
+            bounded.mark(helloWorldLen);
+            compare("limit < length", IOUtils.EMPTY_BYTE_ARRAY, IOUtils.toByteArray(bounded));
+            bounded.reset();
+            compare("limit < length", IOUtils.EMPTY_BYTE_ARRAY, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
     }
 
@@ -141,147 +252,248 @@ public class BoundedInputStreamTest {
         final byte[] helloWorld = "Hello World".getBytes(StandardCharsets.UTF_8);
         final byte[] hello = "Hello".getBytes(StandardCharsets.UTF_8);
         final AtomicBoolean boolRef = new AtomicBoolean();
-
         // limit = length
-        BoundedInputStream bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), helloWorld.length) {
+        try (BoundedInputStream bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), helloWorld.length) {
             @Override
             protected void onMaxLength(final long max, final long readCount) {
                 boolRef.set(true);
             }
-        };
-        assertEquals(helloWorld.length, bounded.getMaxCount());
-        assertEquals(helloWorld.length, bounded.getMaxLength());
-        assertEquals(0, bounded.getCount());
-        assertEquals(bounded.getMaxCount(), bounded.getRemaining());
-        assertEquals(bounded.getMaxLength(), bounded.getRemaining());
-        assertFalse(boolRef.get());
-        int readCount = 0;
-        for (int i = 0; i < helloWorld.length; i++) {
-            assertEquals(helloWorld[i], bounded.read(), "limit = length byte[" + i + "]");
-            readCount++;
+        }) {
+            assertTrue(bounded.markSupported());
             assertEquals(helloWorld.length, bounded.getMaxCount());
             assertEquals(helloWorld.length, bounded.getMaxLength());
+            assertEquals(0, bounded.getCount());
+            assertEquals(bounded.getMaxCount(), bounded.getRemaining());
+            assertEquals(bounded.getMaxLength(), bounded.getRemaining());
+            assertFalse(boolRef.get());
+            int readCount = 0;
+            for (int i = 0; i < helloWorld.length; i++) {
+                assertEquals(helloWorld[i], bounded.read(), "limit = length byte[" + i + "]");
+                readCount++;
+                assertEquals(helloWorld.length, bounded.getMaxCount());
+                assertEquals(helloWorld.length, bounded.getMaxLength());
+                assertEquals(readCount, bounded.getCount());
+                assertEquals(bounded.getMaxCount() - readCount, bounded.getRemaining());
+                assertEquals(bounded.getMaxLength() - readCount, bounded.getRemaining());
+            }
+            assertEquals(-1, bounded.read(), "limit = length end");
+            assertEquals(0, bounded.available());
+            assertEquals(helloWorld.length, bounded.getMaxLength());
             assertEquals(readCount, bounded.getCount());
-            assertEquals(bounded.getMaxCount() - readCount, bounded.getRemaining());
             assertEquals(bounded.getMaxLength() - readCount, bounded.getRemaining());
+            assertTrue(boolRef.get());
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
-        assertEquals(-1, bounded.read(), "limit = length end");
-        assertEquals(0, bounded.available());
-        assertEquals(helloWorld.length, bounded.getMaxLength());
-        assertEquals(readCount, bounded.getCount());
-        assertEquals(bounded.getMaxLength() - readCount, bounded.getRemaining());
-        assertTrue(boolRef.get());
-
         // limit > length
         boolRef.set(false);
         final int length2 = helloWorld.length + 1;
-        bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), length2) {
+        try (BoundedInputStream bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), length2) {
             @Override
             protected void onMaxLength(final long max, final long readCount) {
                 boolRef.set(true);
             }
-        };
-        assertEquals(length2, bounded.getMaxLength());
-        assertEquals(0, bounded.getCount());
-        assertEquals(bounded.getMaxLength(), bounded.getRemaining());
-        assertFalse(boolRef.get());
-        readCount = 0;
-        for (int i = 0; i < helloWorld.length; i++) {
-            assertEquals(helloWorld[i], bounded.read(), "limit > length byte[" + i + "]");
-            readCount++;
+        }) {
+            assertTrue(bounded.markSupported());
+            assertEquals(length2, bounded.getMaxLength());
+            assertEquals(0, bounded.getCount());
+            assertEquals(bounded.getMaxLength(), bounded.getRemaining());
+            assertFalse(boolRef.get());
+            int readCount = 0;
+            for (int i = 0; i < helloWorld.length; i++) {
+                assertEquals(helloWorld[i], bounded.read(), "limit > length byte[" + i + "]");
+                readCount++;
+                assertEquals(length2, bounded.getMaxLength());
+                assertEquals(readCount, bounded.getCount());
+                assertEquals(bounded.getMaxLength() - readCount, bounded.getRemaining());
+            }
+            assertEquals(0, bounded.available());
+            assertEquals(-1, bounded.read(), "limit > length end");
             assertEquals(length2, bounded.getMaxLength());
             assertEquals(readCount, bounded.getCount());
             assertEquals(bounded.getMaxLength() - readCount, bounded.getRemaining());
+            assertFalse(boolRef.get());
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
-        assertEquals(0, bounded.available());
-        assertEquals(-1, bounded.read(), "limit > length end");
-        assertEquals(length2, bounded.getMaxLength());
-        assertEquals(readCount, bounded.getCount());
-        assertEquals(bounded.getMaxLength() - readCount, bounded.getRemaining());
-        assertFalse(boolRef.get());
-
         // limit < length
         boolRef.set(false);
-        bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), hello.length) {
+        try (BoundedInputStream bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), hello.length) {
             @Override
             protected void onMaxLength(final long max, final long readCount) {
                 boolRef.set(true);
             }
-        };
-        assertEquals(hello.length, bounded.getMaxLength());
-        assertEquals(0, bounded.getCount());
-        assertEquals(bounded.getMaxLength(), bounded.getRemaining());
-        assertFalse(boolRef.get());
-        readCount = 0;
-        for (int i = 0; i < hello.length; i++) {
-            assertEquals(hello[i], bounded.read(), "limit < length byte[" + i + "]");
-            readCount++;
+        }) {
+            assertTrue(bounded.markSupported());
+            assertEquals(hello.length, bounded.getMaxLength());
+            assertEquals(0, bounded.getCount());
+            assertEquals(bounded.getMaxLength(), bounded.getRemaining());
+            assertFalse(boolRef.get());
+            int readCount = 0;
+            for (int i = 0; i < hello.length; i++) {
+                assertEquals(hello[i], bounded.read(), "limit < length byte[" + i + "]");
+                readCount++;
+                assertEquals(hello.length, bounded.getMaxLength());
+                assertEquals(readCount, bounded.getCount());
+                assertEquals(bounded.getMaxLength() - readCount, bounded.getRemaining());
+            }
+            assertEquals(-1, bounded.read(), "limit < length end");
             assertEquals(hello.length, bounded.getMaxLength());
             assertEquals(readCount, bounded.getCount());
             assertEquals(bounded.getMaxLength() - readCount, bounded.getRemaining());
+            assertTrue(boolRef.get());
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
-        assertEquals(-1, bounded.read(), "limit < length end");
-        assertEquals(hello.length, bounded.getMaxLength());
-        assertEquals(readCount, bounded.getCount());
-        assertEquals(bounded.getMaxLength() - readCount, bounded.getRemaining());
-        assertTrue(boolRef.get());
     }
 
     @Test
     public void testReadArray() throws Exception {
-
         final byte[] helloWorld = "Hello World".getBytes(StandardCharsets.UTF_8);
         final byte[] hello = "Hello".getBytes(StandardCharsets.UTF_8);
-
         try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld)).get()) {
+            assertTrue(bounded.markSupported());
             compare("limit = -1", helloWorld, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
-
         try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld)).setMaxCount(0).get()) {
+            assertTrue(bounded.markSupported());
             compare("limit = 0", IOUtils.EMPTY_BYTE_ARRAY, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
-
         try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld))
                 .setMaxCount(helloWorld.length).get()) {
+            assertTrue(bounded.markSupported());
             compare("limit = length", helloWorld, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
-
         try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld))
                 .setMaxCount(helloWorld.length + 1).get()) {
+            assertTrue(bounded.markSupported());
             compare("limit > length", helloWorld, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
         try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld))
                 .setMaxCount(helloWorld.length - 6).get()) {
+            assertTrue(bounded.markSupported());
             compare("limit < length", hello, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
     }
 
     @SuppressWarnings("deprecation")
     @Test
     public void testReadSingle() throws Exception {
-        BoundedInputStream bounded;
         final byte[] helloWorld = "Hello World".getBytes(StandardCharsets.UTF_8);
         final byte[] hello = "Hello".getBytes(StandardCharsets.UTF_8);
-
         // limit = length
-        bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), helloWorld.length);
-        for (int i = 0; i < helloWorld.length; i++) {
-            assertEquals(helloWorld[i], bounded.read(), "limit = length byte[" + i + "]");
+        try (BoundedInputStream bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), helloWorld.length)) {
+            assertTrue(bounded.markSupported());
+            for (int i = 0; i < helloWorld.length; i++) {
+                assertEquals(helloWorld[i], bounded.read(), "limit = length byte[" + i + "]");
+            }
+            assertEquals(-1, bounded.read(), "limit = length end");
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
-        assertEquals(-1, bounded.read(), "limit = length end");
-
         // limit > length
-        bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), helloWorld.length + 1);
-        for (int i = 0; i < helloWorld.length; i++) {
-            assertEquals(helloWorld[i], bounded.read(), "limit > length byte[" + i + "]");
+        try (BoundedInputStream bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), helloWorld.length + 1)) {
+            assertTrue(bounded.markSupported());
+            for (int i = 0; i < helloWorld.length; i++) {
+                assertEquals(helloWorld[i], bounded.read(), "limit > length byte[" + i + "]");
+            }
+            assertEquals(-1, bounded.read(), "limit > length end");
+            // should be invariant
+            assertTrue(bounded.markSupported());
+        }
+        // limit < length
+        try (BoundedInputStream bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), hello.length)) {
+            assertTrue(bounded.markSupported());
+            for (int i = 0; i < hello.length; i++) {
+                assertEquals(hello[i], bounded.read(), "limit < length byte[" + i + "]");
+            }
+            assertEquals(-1, bounded.read(), "limit < length end");
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
-        assertEquals(-1, bounded.read(), "limit > length end");
+    }
 
+    @Test
+    public void testReset() throws Exception {
+        final byte[] helloWorld = "Hello World".getBytes(StandardCharsets.UTF_8);
+        final byte[] hello = "Hello".getBytes(StandardCharsets.UTF_8);
+        // limit = -1
+        try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld)).get()) {
+            assertTrue(bounded.markSupported());
+            bounded.reset();
+            compare("limit = -1", helloWorld, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+            // again
+            bounded.reset();
+            compare("limit = -1", helloWorld, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+        }
+        // limit = 0
+        try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld)).setMaxCount(0).get()) {
+            assertTrue(bounded.markSupported());
+            bounded.reset();
+            compare("limit = 0", IOUtils.EMPTY_BYTE_ARRAY, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+            // again
+            bounded.reset();
+            compare("limit = 0", IOUtils.EMPTY_BYTE_ARRAY, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+        }
+        // limit = length
+        try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld))
+                .setMaxCount(helloWorld.length).get()) {
+            assertTrue(bounded.markSupported());
+            bounded.reset();
+            compare("limit = length", helloWorld, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+            // again
+            bounded.reset();
+            compare("limit = length", helloWorld, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+        }
+        // limit > length
+        try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld))
+                .setMaxCount(helloWorld.length + 1).get()) {
+            assertTrue(bounded.markSupported());
+            bounded.reset();
+            compare("limit > length", helloWorld, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+            // again
+            bounded.reset();
+            compare("limit > length", helloWorld, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+        }
         // limit < length
-        bounded = new BoundedInputStream(new ByteArrayInputStream(helloWorld), hello.length);
-        for (int i = 0; i < hello.length; i++) {
-            assertEquals(hello[i], bounded.read(), "limit < length byte[" + i + "]");
+        try (BoundedInputStream bounded = BoundedInputStream.builder().setInputStream(new ByteArrayInputStream(helloWorld))
+                .setMaxCount(helloWorld.length - 6).get()) {
+            assertTrue(bounded.markSupported());
+            bounded.reset();
+            compare("limit < length", hello, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
+            // again
+            bounded.reset();
+            compare("limit < length", hello, IOUtils.toByteArray(bounded));
+            // should be invariant
+            assertTrue(bounded.markSupported());
         }
-        assertEquals(-1, bounded.read(), "limit < length end");
     }
 }


=====================================
src/test/java/org/apache/commons/io/output/DeferredFileOutputStreamTest.java
=====================================
@@ -277,6 +277,28 @@ public class DeferredFileOutputStreamTest extends AbstractTempDirTest {
         assertThrows(NullPointerException.class, () -> new DeferredFileOutputStream(testBytes.length - 5, prefix, suffix, tempDirFile));
     }
 
+    /**
+     * Tests the case where the threshold is negative, and therefore the data is always written to disk. The actual data
+     * written to disk is verified, as is the file itself.
+     */
+    @ParameterizedTest(name = "initialBufferSize = {0}")
+    @MethodSource("data")
+    public void testThresholdNegative(final int initialBufferSize) throws IOException {
+        final File testFile = Files.createTempFile(tempDirPath, "testThresholdNegative", "dat").toFile();
+        try (DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
+                .setThreshold(-1)
+                .setBufferSize(initialBufferSize)
+                .setOutputFile(testFile)
+                .get()) {
+            dfos.write(testBytes, 0, testBytes.length);
+            dfos.close();
+            assertFalse(dfos.isInMemory());
+            assertNull(dfos.getData());
+            assertEquals(testFile.length(), dfos.getByteCount());
+            verifyResultFile(testFile);
+        }
+    }
+
     /**
      * Tests the case where there are multiple writes beyond the threshold, to ensure that the
      * {@code thresholdReached()} method is only called once, as the threshold is crossed for the first time.


=====================================
src/test/java/org/apache/commons/io/output/ThresholdingOutputStreamTest.java
=====================================
@@ -32,29 +32,6 @@ import org.junit.jupiter.api.Test;
  */
 public class ThresholdingOutputStreamTest {
 
-    @Test
-    public void testThresholdLessThanZero() throws IOException {
-        try (final ThresholdingOutputStream out = new ThresholdingOutputStream(-1)) {
-            assertTrue(out.isThresholdExceeded());
-        }
-    }
-
-    @Test
-    public void testThresholdZero() throws IOException {
-        final AtomicBoolean reached = new AtomicBoolean();
-        try (final ThresholdingOutputStream out = new ThresholdingOutputStream(0) {
-            @Override
-            protected void thresholdReached() throws IOException {
-                reached.set(true);
-            }
-        }) {
-            assertFalse(out.isThresholdExceeded());
-            out.write(89);
-            assertTrue(reached.get());
-            assertTrue(out.isThresholdExceeded());
-        }
-    }
-
     @Test
     public void testSetByteCount_OutputStream() throws Exception {
         final AtomicBoolean reached = new AtomicBoolean();
@@ -155,4 +132,61 @@ public class ThresholdingOutputStreamTest {
             assertThrows(IllegalStateException.class, () -> tos.write('a'));
         }
     }
+
+    /**
+     * Tests the case where the threshold is negative.
+     * The threshold is not reached until something is written to the stream.
+     */
+    @Test
+    public void testThresholdLessThanZero() throws IOException {
+        final AtomicBoolean reached = new AtomicBoolean();
+        try (final ThresholdingOutputStream out = new ThresholdingOutputStream(-1) {
+            @Override
+            protected void thresholdReached() throws IOException {
+                reached.set(true);
+            }
+        }) {
+            assertFalse(reached.get());
+            out.write(89);
+            assertTrue(reached.get());
+            assertTrue(out.isThresholdExceeded());
+        }
+    }
+
+    @Test
+    public void testThresholdZero() throws IOException {
+        final AtomicBoolean reached = new AtomicBoolean();
+        try (final ThresholdingOutputStream out = new ThresholdingOutputStream(0) {
+            @Override
+            protected void thresholdReached() throws IOException {
+                reached.set(true);
+            }
+        }) {
+            assertFalse(out.isThresholdExceeded());
+            out.write(89);
+            assertTrue(reached.get());
+            assertTrue(out.isThresholdExceeded());
+        }
+    }
+
+    /**
+     * Tests the case where no bytes are written.
+     * The threshold is not reached until something is written to the stream.
+     */
+    @Test
+    public void testThresholdZeroWrite() throws IOException {
+        final AtomicBoolean reached = new AtomicBoolean();
+        try (final ThresholdingOutputStream out = new ThresholdingOutputStream(7) {
+            @Override
+            protected void thresholdReached() throws IOException {
+                reached.set(true);
+            }
+        }) {
+            assertFalse(out.isThresholdExceeded());
+            assertFalse(reached.get());
+            out.write(new byte[0]);
+            assertFalse(out.isThresholdExceeded());
+            assertFalse(reached.get());
+        }
+    }
 }
\ No newline at end of file



View it on GitLab: https://salsa.debian.org/java-team/commons-io/-/commit/dccb96fef7661eb2ffa276566d55ba963b96c305

-- 
View it on GitLab: https://salsa.debian.org/java-team/commons-io/-/commit/dccb96fef7661eb2ffa276566d55ba963b96c305
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20240426/5e01a852/attachment.htm>


More information about the pkg-java-commits mailing list