From 533107497ee74b3cc8dce16f419da1a525c3d11a Mon Sep 17 00:00:00 2001
From: Gary Gregory
Date: Thu, 2 Oct 2025 10:30:56 -0400
Subject: [PATCH 001/174] Reuse AbstractStreamBuilder.getChannel(Channel)
---
.../io/input/ReversedLinesFileReader.java | 34 +++++++++----------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java b/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java
index 949ff8cbd8e..f87b109abb9 100644
--- a/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java
+++ b/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java
@@ -25,7 +25,6 @@
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
@@ -93,6 +92,7 @@ public static class Builder extends AbstractStreamBuilder 0) {
- this.totalBlockCount = this.totalByteLength / blockSize + 1;
+ this.totalBlockCount = totalByteLength / blockSize + 1;
} else {
- this.totalBlockCount = this.totalByteLength / blockSize;
- if (this.totalByteLength > 0) {
+ this.totalBlockCount = totalByteLength / blockSize;
+ if (totalByteLength > 0) {
lastBlockLength = blockSize;
}
}
From 028dd621009cb0cca677116cd0acdc69192d3d66 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Thu, 2 Oct 2025 16:31:54 +0200
Subject: [PATCH 002/174] Improve `FileUtils.forceDelete()` tests on Windows
(#791)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Improve `FileUtils.forceDelete()` tests on Windows
On Windows, the `DeleteFile` Win32 API has a little quirk: it refuses to delete files with the legacy **DOS read-only attribute** set. (Because apparently 99% of Windows users don’t realize that “deleting a file” is actually an operation on the *directory*, not the file itself 😉).
So the usual drill is: clear the read-only flag first, then delete.
* Until JDK 25, `File.delete()` did this for you behind the scenes: it quietly stripped the flag before calling into `DeleteFile`. That meant your file might be left behind (with the flag missing) if the *real* ACLs didn’t allow deletion.
* From JDK 25 onward, `File.delete()` doesn’t touch the flag anymore. If the bit is set, `DeleteFile` fails, end of story.
* `FileUtils.forceDelete()` already knows how to juggle the flag itself, so its behavior didn’t change.
This PR:
* Updates two tests that were (unfairly) comparing `File.delete` with `FileUtils.forceDelete`. With JDK 25, their expectations diverged.
* Adds a new test to confirm that `FileUtils.forceDelete` restores the read-only flag if the actual deletion fails.
> [!WARNING]
> I didn’t develop this on a Windows box, so I couldn’t test it locally. Leaving this as a **draft PR** until CI tells us whether Windows agrees with me.
* fix: always clear read-only bit
* fix: check for `IOException`, not `AccessDeniedException`
* feat: add holder for file and parent attributes
Introduce a helper that snapshots file and parent directory attributes before `setReadOnly` is applied.
If a deletion attempt fails, the holder can restore the original attributes to keep the filesystem state consistent.
* fix: Spotbugs failure
* fix: prefer POSIX to DOS attributes when both are available
On Linux/Unix, both POSIX and DOS attribute views may be supported,
while on Windows only DOS attributes are available.
Check for POSIX first to ensure the correct view is used across platforms.
* fix: revert read-only related changes
---
.../org/apache/commons/io/FileUtilsTest.java | 57 ++++++++++++-------
1 file changed, 36 insertions(+), 21 deletions(-)
diff --git a/src/test/java/org/apache/commons/io/FileUtilsTest.java b/src/test/java/org/apache/commons/io/FileUtilsTest.java
index 4f6efa0f946..ced4b106638 100644
--- a/src/test/java/org/apache/commons/io/FileUtilsTest.java
+++ b/src/test/java/org/apache/commons/io/FileUtilsTest.java
@@ -52,6 +52,7 @@
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.DosFileAttributeView;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
@@ -1735,21 +1736,31 @@ void testForceDeleteReadOnlyDirectory() throws Exception {
void testForceDeleteReadOnlyFile() throws Exception {
try (TempFile destination = TempFile.create("test-", ".txt")) {
final File file = destination.toFile();
- assertTrue(file.setReadOnly());
- assertTrue(file.canRead());
- assertFalse(file.canWrite());
- // sanity check that File.delete() deletes read-only files.
- assertTrue(file.delete());
+ assertTrue(file.setReadOnly(), "Setting file read-only successful");
+ assertTrue(file.canRead(), "File must be readable");
+ assertFalse(file.canWrite(), "File must not be writable");
+ assertTrue(file.exists(), "File doesn't exist to delete");
+ // Since JDK 25 on Windows, File.delete() refuses to remove files
+ // with the DOS readonly bit set (JDK-8355954).
+ // We clear the bit here for consistency across JDK versions.
+ setDosReadOnly(file.toPath(), false);
+ assertTrue(file.delete(), "File.delete() must delete read-only file");
}
try (TempFile destination = TempFile.create("test-", ".txt")) {
final File file = destination.toFile();
// real test
- assertTrue(file.setReadOnly());
- assertTrue(file.canRead());
- assertFalse(file.canWrite());
+ assertTrue(file.setReadOnly(), "Setting file read-only successful");
+ assertTrue(file.canRead(), "File must be readable");
+ assertFalse(file.canWrite(), "File must not be writable");
assertTrue(file.exists(), "File doesn't exist to delete");
FileUtils.forceDelete(file);
- assertFalse(file.exists(), "Check deletion");
+ assertFalse(file.exists(), "FileUtils.forceDelete() must delete read-only file");
+ }
+ }
+
+ private static void setDosReadOnly(Path p, boolean readOnly) throws IOException {
+ if (Files.getFileStore(p).supportsFileAttributeView(DosFileAttributeView.class)) {
+ Files.setAttribute(p, "dos:readonly", readOnly, LinkOption.NOFOLLOW_LINKS);
}
}
@@ -1823,23 +1834,27 @@ void testForceDeleteUnwritableDirectory() throws Exception {
void testForceDeleteUnwritableFile() throws Exception {
try (TempFile destination = TempFile.create("test-", ".txt")) {
final File file = destination.toFile();
- assertTrue(file.canWrite());
- assertTrue(file.setWritable(false));
- assertFalse(file.canWrite());
- assertTrue(file.canRead());
- // sanity check that File.delete() deletes unwritable files.
- assertTrue(file.delete());
+ assertTrue(file.canWrite(), "File must be writable");
+ assertTrue(file.setWritable(false), "Setting file unwritable successful");
+ assertFalse(file.canWrite(), "File must not be writable");
+ assertTrue(file.canRead(), "File must be readable");
+ assertTrue(file.exists(), "File must exist to delete");
+ // Since JDK 25 on Windows, File.delete() refuses to remove files
+ // with the DOS readonly bit set (JDK-8355954).
+ // We clear the bit here for consistency across JDK versions.
+ setDosReadOnly(file.toPath(), false);
+ assertTrue(file.delete(), "File.delete() must delete unwritable file");
}
try (TempFile destination = TempFile.create("test-", ".txt")) {
final File file = destination.toFile();
// real test
- assertTrue(file.canWrite());
- assertTrue(file.setWritable(false));
- assertFalse(file.canWrite());
- assertTrue(file.canRead());
- assertTrue(file.exists(), "File doesn't exist to delete");
+ assertTrue(file.canWrite(), "File must be writable");
+ assertTrue(file.setWritable(false), "Setting file unwritable successful");
+ assertFalse(file.canWrite(), "File must not be writable");
+ assertTrue(file.canRead(), "File must be readable");
+ assertTrue(file.exists(), "File must exist to delete");
FileUtils.forceDelete(file);
- assertFalse(file.exists(), "Check deletion");
+ assertFalse(file.exists(), "FileUtils.forceDelete() must delete unwritable file");
}
}
From c2ee341e0134df62c967c6f03741894383ef83a0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 2 Oct 2025 20:27:46 -0400
Subject: [PATCH 003/174] Bump ossf/scorecard-action from 2.4.2 to 2.4.3 (#792)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.2 to 2.4.3.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](https://github.com/ossf/scorecard-action/compare/05b42c624433fc40578a4040d5cf5e36ddca8cde...4eaacf0543bb3f2c246792bd56e8cdeffafb205a)
---
updated-dependencies:
- dependency-name: ossf/scorecard-action
dependency-version: 2.4.3
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/scorecards-analysis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml
index cf4d6ba87b0..dc25de392ba 100644
--- a/.github/workflows/scorecards-analysis.yml
+++ b/.github/workflows/scorecards-analysis.yml
@@ -47,7 +47,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
- uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # 2.4.2
+ uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # 2.4.3
with:
results_file: results.sarif
results_format: sarif
From ac2d382eeaa8b1d630051cdbc8dd302372ee4f21 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 2 Oct 2025 20:28:20 -0400
Subject: [PATCH 004/174] Bump github/codeql-action from 3.30.4 to 3.30.6
(#793)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.4 to 3.30.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9...64d10c13136e1c5bce3e5fbde8d4906eeaafc885)
---
updated-dependencies:
- dependency-name: github/codeql-action
dependency-version: 3.30.6
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/codeql-analysis.yml | 6 +++---
.github/workflows/scorecards-analysis.yml | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index fcc16fe390a..db18ec4ae56 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -62,7 +62,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # 3.29.5
+ uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # 3.29.5
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -73,7 +73,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # 3.29.5
+ uses: github/codeql-action/autobuild@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # 3.29.5
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -87,4 +87,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # 3.29.5
+ uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # 3.29.5
diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml
index dc25de392ba..3b7d3020e33 100644
--- a/.github/workflows/scorecards-analysis.yml
+++ b/.github/workflows/scorecards-analysis.yml
@@ -66,6 +66,6 @@ jobs:
retention-days: 5
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # 3.29.5
+ uses: github/codeql-action/upload-sarif@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # 3.29.5
with:
sarif_file: results.sarif
From 07d2cd9c493baae1e82553b3da420d17a6093c05 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 2 Oct 2025 20:29:27 -0400
Subject: [PATCH 005/174] Bump actions/dependency-review-action from 4.7.3 to
4.8.0 (#794)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.7.3 to 4.8.0.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](https://github.com/actions/dependency-review-action/compare/595b5aeba73380359d98a5e087f648dbb0edce1b...56339e523c0409420f6c2c9a2f4292bbb3c07dd3)
---
updated-dependencies:
- dependency-name: actions/dependency-review-action
dependency-version: 4.8.0
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/dependency-review.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index 1e043924237..edfe8756642 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -28,4 +28,4 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: 'Dependency Review PR'
- uses: actions/dependency-review-action@595b5aeba73380359d98a5e087f648dbb0edce1b # v4.7.3
+ uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3 # v4.8.0
From 45578a74f54886a2e2b4b72f8faeda0728146f14 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Fri, 3 Oct 2025 21:32:36 +0200
Subject: [PATCH 006/174] Add missing `read` argument validation in `IOUtils`
(#795)
* Add missing `read` argument validation in `IOUtils`
In #790 I introduced `IOUtils#checkIndexFromLength` calls to validate arguments across the codebase. Ironically, the `IOUtils` class itself was left out.
This PR addresses that omission by adding argument validation to `IOUtils#read` and `IOUtils#readFully`.
Key points:
* Ensures consistency with the rest of Commons IO by validating `offset` and `length`.
* Fixes inconsistent exception behavior:
* Previously, `length < 0` resulted in an `IllegalArgumentException`.
* `offset < 0` did not trigger validation and failed later with an `IndexOutOfBoundsException`.
* With this change, both invalid cases are handled consistently and upfront.
* fix: failing tests
---
.../java/org/apache/commons/io/IOUtils.java | 60 +++++++------------
.../org/apache/commons/io/IOUtilsTest.java | 35 ++++++++++-
2 files changed, 55 insertions(+), 40 deletions(-)
diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java
index a882d4d87cb..66d785885ee 100644
--- a/src/main/java/org/apache/commons/io/IOUtils.java
+++ b/src/main/java/org/apache/commons/io/IOUtils.java
@@ -2054,6 +2054,7 @@ public static LineIterator lineIterator(final Reader reader) {
* @param input where to read input from.
* @param buffer destination.
* @return actual length read; may be less than requested if EOF was reached.
+ * @throws NullPointerException if {@code input} or {@code buffer} is null.
* @throws IOException if a read error occurs.
* @since 2.2
*/
@@ -2074,40 +2075,19 @@ public static int read(final InputStream input, final byte[] buffer) throws IOEx
* @param offset initial offset into buffer.
* @param length length to read, must be >= 0.
* @return actual length read; may be less than requested if EOF was reached.
- * @throws IllegalArgumentException if length is negative.
+ * @throws NullPointerException if {@code input} or {@code buffer} is null.
+ * @throws IndexOutOfBoundsException if {@code offset} or {@code length} is negative, or if
+ * {@code offset + length} is greater than {@code buffer.length}.
* @throws IOException if a read error occurs.
* @since 2.2
*/
public static int read(final InputStream input, final byte[] buffer, final int offset, final int length)
throws IOException {
- if (length == 0) {
- return 0;
- }
- return read(input::read, buffer, offset, length);
- }
-
- /**
- * Reads bytes from an input. This implementation guarantees that it will read as many bytes as possible before giving up; this may not always be the case
- * for subclasses of {@link InputStream}.
- *
- * @param input How to read input.
- * @param buffer destination.
- * @param offset initial offset into buffer.
- * @param length length to read, must be >= 0.
- * @return actual length read; may be less than requested if EOF was reached.
- * @throws IllegalArgumentException if length is negative.
- * @throws IOException if a read error occurs.
- * @since 2.2
- */
- static int read(final IOTriFunction input, final byte[] buffer, final int offset, final int length)
- throws IOException {
- if (length < 0) {
- throw new IllegalArgumentException("Length must not be negative: " + length);
- }
+ checkFromIndexSize(buffer, offset, length);
int remaining = length;
while (remaining > 0) {
final int location = length - remaining;
- final int count = input.apply(buffer, offset + location, remaining);
+ final int count = input.read(buffer, offset + location, remaining);
if (EOF == count) {
break;
}
@@ -2172,15 +2152,15 @@ public static int read(final Reader reader, final char[] buffer) throws IOExcept
* @param offset initial offset into buffer.
* @param length length to read, must be >= 0.
* @return actual length read; may be less than requested if EOF was reached.
- * @throws IllegalArgumentException if length is negative.
+ * @throws NullPointerException if {@code reader} or {@code buffer} is null.
+ * @throws IndexOutOfBoundsException if {@code offset} or {@code length} is negative, or if
+ * {@code offset + length} is greater than {@code buffer.length}.
* @throws IOException if a read error occurs.
* @since 2.2
*/
public static int read(final Reader reader, final char[] buffer, final int offset, final int length)
throws IOException {
- if (length < 0) {
- throw new IllegalArgumentException("Length must not be negative: " + length);
- }
+ checkFromIndexSize(buffer, offset, length);
int remaining = length;
while (remaining > 0) {
final int location = length - remaining;
@@ -2202,9 +2182,9 @@ public static int read(final Reader reader, final char[] buffer, final int offse
*
* @param input where to read input from.
* @param buffer destination.
- * @throws IOException if there is a problem reading the file.
- * @throws IllegalArgumentException if length is negative.
+ * @throws NullPointerException if {@code input} or {@code buffer} is null.
* @throws EOFException if the number of bytes read was incorrect.
+ * @throws IOException if there is a problem reading the file.
* @since 2.2
*/
public static void readFully(final InputStream input, final byte[] buffer) throws IOException {
@@ -2222,9 +2202,11 @@ public static void readFully(final InputStream input, final byte[] buffer) throw
* @param buffer destination.
* @param offset initial offset into buffer.
* @param length length to read, must be >= 0.
- * @throws IOException if there is a problem reading the file.
- * @throws IllegalArgumentException if length is negative.
+ * @throws NullPointerException if {@code input} or {@code buffer} is null.
+ * @throws IndexOutOfBoundsException if {@code offset} or {@code length} is negative, or if
+ * {@code offset + length} is greater than {@code buffer.length}.
* @throws EOFException if the number of bytes read was incorrect.
+ * @throws IOException if there is a problem reading the file.
* @since 2.2
*/
public static void readFully(final InputStream input, final byte[] buffer, final int offset, final int length)
@@ -2286,9 +2268,9 @@ public static void readFully(final ReadableByteChannel input, final ByteBuffer b
*
* @param reader where to read input from.
* @param buffer destination.
- * @throws IOException if there is a problem reading the file.
- * @throws IllegalArgumentException if length is negative.
+ * @throws NullPointerException if {@code reader} or {@code buffer} is null.
* @throws EOFException if the number of characters read was incorrect.
+ * @throws IOException if there is a problem reading the file.
* @since 2.2
*/
public static void readFully(final Reader reader, final char[] buffer) throws IOException {
@@ -2306,9 +2288,11 @@ public static void readFully(final Reader reader, final char[] buffer) throws IO
* @param buffer destination.
* @param offset initial offset into buffer.
* @param length length to read, must be >= 0.
- * @throws IOException if there is a problem reading the file.
- * @throws IllegalArgumentException if length is negative.
+ * @throws NullPointerException if {@code reader} or {@code buffer} is null.
+ * @throws IndexOutOfBoundsException if {@code offset} or {@code length} is negative, or if
+ * {@code offset + length} is greater than {@code buffer.length}.
* @throws EOFException if the number of characters read was incorrect.
+ * @throws IOException if there is a problem reading the file.
* @since 2.2
*/
public static void readFully(final Reader reader, final char[] buffer, final int offset, final int length)
diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java
index 02574946bf8..a2164ce49d1 100644
--- a/src/test/java/org/apache/commons/io/IOUtilsTest.java
+++ b/src/test/java/org/apache/commons/io/IOUtilsTest.java
@@ -1195,6 +1195,31 @@ void testRead_ReadableByteChannel() throws Exception {
}
}
+ static Stream invalidRead_InputStream_Offset_ArgumentsProvider() {
+ final InputStream input = new ByteArrayInputStream(new byte[10]);
+ final byte[] b = new byte[10];
+ return Stream.of(
+ // input is null
+ Arguments.of(null, b, 0, 1, NullPointerException.class),
+ // b is null
+ Arguments.of(input, null, 0, 1, NullPointerException.class),
+ // off is negative
+ Arguments.of(input, b, -1, 1, IndexOutOfBoundsException.class),
+ // len is negative
+ Arguments.of(input, b, 0, -1, IndexOutOfBoundsException.class),
+ // off + len is too big
+ Arguments.of(input, b, 1, 10, IndexOutOfBoundsException.class),
+ // off + len is too big
+ Arguments.of(input, b, 10, 1, IndexOutOfBoundsException.class)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidRead_InputStream_Offset_ArgumentsProvider")
+ void testRead_InputStream_Offset_ArgumentsValidation(InputStream input, byte[] b, int off, int len, Class extends Throwable> expected) {
+ assertThrows(expected, () -> IOUtils.read(input, b, off, len));
+ }
+
@Test
void testReadFully_InputStream__ReturnByteArray() throws Exception {
final byte[] bytes = "abcd1234".getBytes(StandardCharsets.UTF_8);
@@ -1213,7 +1238,7 @@ void testReadFully_InputStream_ByteArray() throws Exception {
final byte[] buffer = new byte[size];
final InputStream input = new ByteArrayInputStream(new byte[size]);
- assertThrows(IllegalArgumentException.class, () -> IOUtils.readFully(input, buffer, 0, -1), "Should have failed with IllegalArgumentException");
+ assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.readFully(input, buffer, 0, -1), "Should have failed with IndexOutOfBoundsException");
IOUtils.readFully(input, buffer, 0, 0);
IOUtils.readFully(input, buffer, 0, size - 1);
@@ -1260,7 +1285,7 @@ void testReadFully_Reader() throws Exception {
IOUtils.readFully(input, buffer, 0, 0);
IOUtils.readFully(input, buffer, 0, size - 3);
- assertThrows(IllegalArgumentException.class, () -> IOUtils.readFully(input, buffer, 0, -1), "Should have failed with IllegalArgumentException");
+ assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.readFully(input, buffer, 0, -1), "Should have failed with IndexOutOfBoundsException");
assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer, 0, 5), "Should have failed with EOFException");
IOUtils.closeQuietly(input);
}
@@ -1274,6 +1299,12 @@ void testReadFully_Reader_Offset() throws Exception {
IOUtils.closeQuietly(reader);
}
+ @ParameterizedTest
+ @MethodSource("invalidRead_InputStream_Offset_ArgumentsProvider")
+ void testReadFully_InputStream_Offset_ArgumentsValidation(InputStream input, byte[] b, int off, int len, Class extends Throwable> expected) {
+ assertThrows(expected, () -> IOUtils.read(input, b, off, len));
+ }
+
@Test
void testReadLines_CharSequence() throws IOException {
final File file = TestUtils.newFile(temporaryFolder, "lines.txt");
From 8178f48ed70b7c023f741a602b493e110880aa5b Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Sat, 4 Oct 2025 13:15:45 +0200
Subject: [PATCH 007/174] Fix inconsistent exception in `IOUtils.toByteArray`
(#796)
The implementation of `IOUtils.toByteArray(InputStream, int, int)` added in #776 throws different exceptions depending on the requested size:
* For request sizes larger than the internal chunk size, it correctly throws an `EOFException`.
* For smaller requests, it incorrectly throws a generic `IOException`.
This PR makes the behavior consistent by always throwing an `EOFException` when the stream ends prematurely.
Note: This also affects `RandomAccessFiles.read`. Its previous truncation behavior was undocumented and inconsistent with `RandomAccessFile.read` (which reads as much as possible). The new behavior is not explicitly documented here either, since it is unclear whether throwing on truncation is actually desirable.
---
src/main/java/org/apache/commons/io/IOUtils.java | 3 ++-
src/test/java/org/apache/commons/io/IOUtilsTest.java | 11 ++++++++++-
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java
index 66d785885ee..4d5b7d1a526 100644
--- a/src/main/java/org/apache/commons/io/IOUtils.java
+++ b/src/main/java/org/apache/commons/io/IOUtils.java
@@ -2941,6 +2941,7 @@ public static byte[] toByteArray(final InputStream input, final long size) throw
* @param input the input to read, not null.
* @param size the size of the input to read, where 0 < {@code size} <= length of input.
* @return byte [] of length {@code size}.
+ * @throws EOFException if the end of the input is reached before reading {@code size} bytes.
* @throws IOException if an I/O error occurs or input length is smaller than parameter {@code size}.
* @throws IllegalArgumentException if {@code size} is less than zero.
*/
@@ -2958,7 +2959,7 @@ static byte[] toByteArray(final IOTriFunction
offset += read;
}
if (offset != size) {
- throw new IOException("Unexpected read size, current: " + offset + ", expected: " + size);
+ throw new EOFException("Unexpected read size, current: " + offset + ", expected: " + size);
}
return data;
}
diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java
index a2164ce49d1..9d99d10f214 100644
--- a/src/test/java/org/apache/commons/io/IOUtilsTest.java
+++ b/src/test/java/org/apache/commons/io/IOUtilsTest.java
@@ -158,7 +158,9 @@ static Stream testToByteArray_InputStream_Size_BufferSize_Throws() {
Arguments.of(-1, 128, IllegalArgumentException.class),
// Invalid buffer size
Arguments.of(0, 0, IllegalArgumentException.class),
- // Huge size: should not cause OutOfMemoryError
+ // Truncation with requested size < chunk size
+ Arguments.of(64, 128, EOFException.class),
+ // Truncation with requested size > chunk size
Arguments.of(Integer.MAX_VALUE, 128, EOFException.class));
}
@@ -1788,6 +1790,13 @@ void testToByteArray_InputStream_Size() throws Exception {
}
}
+ @Test
+ void testToByteArray_InputStream_Size_Truncated() throws Exception {
+ try (InputStream in = new NullInputStream(0)) {
+ assertThrows(EOFException.class, () -> IOUtils.toByteArray(in, 1), "Should have failed with EOFException");
+ }
+ }
+
@ParameterizedTest
@MethodSource
void testToByteArray_InputStream_Size_BufferSize_Succeeds(final byte[] data, final int size, final int bufferSize) throws IOException {
From 6b41b2efbcf7e7dac52d2c485c9e1f5d0873c23b Mon Sep 17 00:00:00 2001
From: "Gary D. Gregory"
Date: Sat, 4 Oct 2025 07:17:15 -0400
Subject: [PATCH 008/174] IOUtils.toByteArray now throws EOFException when not
enough data is available #796
---
src/changes/changes.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index fb68216e20a..3bcc4145197 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -55,6 +55,7 @@ The type attribute can be add,update,fix,remove.
Deprecate IOUtils.readFully(InputStream, int) in favor of toByteArray(InputStream, int).IOUtils.toByteArray(InputStream) now throws IOException on byte array overflow.Javadoc general improvements.
+ JIOUtils.toByteArray now throws EOFException when not enough data is available #796.FileUtils#byteCountToDisplaySize() supports Zettabyte, Yottabyte, Ronnabyte and Quettabyte #763.Add org.apache.commons.io.FileUtils.ONE_RB #763.
From d190051aceefe8ba4eb1b79aa69d4ee8b502beff Mon Sep 17 00:00:00 2001
From: "Gary D. Gregory"
Date: Sat, 4 Oct 2025 07:49:05 -0400
Subject: [PATCH 009/174] Better exception messages
Better unit test messages
---
src/main/java/org/apache/commons/io/IOUtils.java | 13 +++++++------
.../commons/io/input/ReadAheadInputStream.java | 2 +-
.../java/org/apache/commons/io/IOUtilsTest.java | 11 ++++-------
3 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java
index 4d5b7d1a526..aac71be0fa5 100644
--- a/src/main/java/org/apache/commons/io/IOUtils.java
+++ b/src/main/java/org/apache/commons/io/IOUtils.java
@@ -2898,15 +2898,16 @@ public static byte[] toByteArray(final InputStream input, final int size) throws
public static byte[] toByteArray(final InputStream input, final int size, final int chunkSize) throws IOException {
Objects.requireNonNull(input, "input");
if (chunkSize <= 0) {
- throw new IllegalArgumentException("Chunk size must be greater than zero: " + chunkSize);
+ throw new IllegalArgumentException(String.format("chunkSize <= 0, chunkSize = %,d", chunkSize));
}
if (size <= chunkSize) {
// throws if size < 0
return toByteArray(input::read, size);
}
final UnsynchronizedByteArrayOutputStream output = copyToOutputStream(input, size, chunkSize);
- if (output.size() != size) {
- throw new EOFException("Unexpected read size, current: " + output.size() + ", expected: " + size);
+ final int outSize = output.size();
+ if (outSize != size) {
+ throw new EOFException(String.format("Expected read size: %,d, actual: %,d", size, outSize));
}
return output.toByteArray();
}
@@ -2930,7 +2931,7 @@ public static byte[] toByteArray(final InputStream input, final int size, final
*/
public static byte[] toByteArray(final InputStream input, final long size) throws IOException {
if (size > Integer.MAX_VALUE) {
- throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + size);
+ throw new IllegalArgumentException(String.format("size > Integer.MAX_VALUE, size = %,d", size));
}
return toByteArray(input, (int) size);
}
@@ -2947,7 +2948,7 @@ public static byte[] toByteArray(final InputStream input, final long size) throw
*/
static byte[] toByteArray(final IOTriFunction input, final int size) throws IOException {
if (size < 0) {
- throw new IllegalArgumentException("Size must be equal or greater than zero: " + size);
+ throw new IllegalArgumentException(String.format("size < 0, size = %,d", size));
}
if (size == 0) {
return EMPTY_BYTE_ARRAY;
@@ -2959,7 +2960,7 @@ static byte[] toByteArray(final IOTriFunction
offset += read;
}
if (offset != size) {
- throw new EOFException("Unexpected read size, current: " + offset + ", expected: " + size);
+ throw new EOFException(String.format("Expected read size: %,d, actual: %,d", size, offset));
}
return data;
}
diff --git a/src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java b/src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java
index bcee009a431..57edd41bcb7 100644
--- a/src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java
@@ -239,7 +239,7 @@ private ReadAheadInputStream(final InputStream inputStream, final int bufferSize
final boolean shutdownExecutorService) {
super(Objects.requireNonNull(inputStream, "inputStream"));
if (bufferSizeInBytes <= 0) {
- throw new IllegalArgumentException("bufferSizeInBytes should be greater than 0, but the value is " + bufferSizeInBytes);
+ throw new IllegalArgumentException(String.format("bufferSizeInBytes <= 0, bufferSizeInBytes = %,d", bufferSizeInBytes));
}
this.executorService = Objects.requireNonNull(executorService, "executorService");
this.shutdownExecutorService = shutdownExecutorService;
diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java
index 9d99d10f214..9e9b6c8bfc8 100644
--- a/src/test/java/org/apache/commons/io/IOUtilsTest.java
+++ b/src/test/java/org/apache/commons/io/IOUtilsTest.java
@@ -1772,10 +1772,8 @@ void testToByteArray_InputStream_LongerThanIntegerMaxValue() throws Exception {
@Test
void testToByteArray_InputStream_NegativeSize() throws Exception {
try (InputStream fin = Files.newInputStream(testFilePath)) {
- final IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> IOUtils.toByteArray(fin, -1),
- "Should have failed with IllegalArgumentException");
- assertTrue(exc.getMessage().startsWith("Size must be equal or greater than zero"),
- "Exception message does not start with \"Size must be equal or greater than zero\"");
+ final IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> IOUtils.toByteArray(fin, -1));
+ assertTrue(exc.getMessage().startsWith("size < 0"), exc.getMessage());
}
}
@@ -1820,7 +1818,7 @@ void testToByteArray_InputStream_SizeIllegal() throws Exception {
try (InputStream fin = Files.newInputStream(testFilePath)) {
final IOException exc = assertThrows(IOException.class, () -> IOUtils.toByteArray(fin, testFile.length() + 1),
"Should have failed with IOException");
- assertTrue(exc.getMessage().startsWith("Unexpected read size"), "Exception message does not start with \"Unexpected read size\"");
+ assertTrue(exc.getMessage().startsWith("Expected read size"), exc.getMessage());
}
}
@@ -1829,8 +1827,7 @@ void testToByteArray_InputStream_SizeLong() throws Exception {
try (InputStream fin = Files.newInputStream(testFilePath)) {
final IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> IOUtils.toByteArray(fin, (long) Integer.MAX_VALUE + 1),
"Should have failed with IllegalArgumentException");
- assertTrue(exc.getMessage().startsWith("Size cannot be greater than Integer max value"),
- "Exception message does not start with \"Size cannot be greater than Integer max value\"");
+ assertTrue(exc.getMessage().startsWith("size > Integer.MAX_VALUE"), exc.getMessage());
}
}
From 26e5aa9661a72bfd9697fb384ca72f58e5d672e9 Mon Sep 17 00:00:00 2001
From: "Gary D. Gregory"
Date: Sat, 4 Oct 2025 07:57:41 -0400
Subject: [PATCH 010/174] Don't override JUnit messages to say the same thing
Reduce vertical space
---
.../org/apache/commons/io/IOUtilsTest.java | 96 +++++--------------
1 file changed, 25 insertions(+), 71 deletions(-)
diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java
index 9e9b6c8bfc8..8a830ec316a 100644
--- a/src/test/java/org/apache/commons/io/IOUtilsTest.java
+++ b/src/test/java/org/apache/commons/io/IOUtilsTest.java
@@ -683,13 +683,10 @@ void testConsumeReader() throws Exception {
final long size = (long) Integer.MAX_VALUE + (long) 1;
final Reader in = new NullReader(size);
final Writer out = NullWriter.INSTANCE;
-
// Test copy() method
assertEquals(-1, IOUtils.copy(in, out));
-
// reset the input
in.close();
-
// Test consume() method
assertEquals(size, IOUtils.consume(in), "consume()");
}
@@ -913,12 +910,9 @@ void testCopy_ByteArray_OutputStream() throws Exception {
// Create our byte[]. Rely on testInputStreamToByteArray() to make sure this is valid.
in = IOUtils.toByteArray(fin);
}
-
try (OutputStream fout = Files.newOutputStream(destination.toPath())) {
CopyUtils.copy(in, fout);
-
fout.flush();
-
TestUtils.checkFile(destination, testFile);
TestUtils.checkWrite(fout);
}
@@ -933,7 +927,6 @@ void testCopy_ByteArray_Writer() throws Exception {
// Create our byte[]. Rely on testInputStreamToByteArray() to make sure this is valid.
in = IOUtils.toByteArray(fin);
}
-
try (Writer fout = Files.newBufferedWriter(destination.toPath())) {
CopyUtils.copy(in, fout);
fout.flush();
@@ -951,11 +944,9 @@ void testCopy_String_Writer() throws Exception {
// Create our String. Rely on testReaderToString() to make sure this is valid.
str = IOUtils.toString(fin);
}
-
try (Writer fout = Files.newBufferedWriter(destination.toPath())) {
CopyUtils.copy(str, fout);
fout.flush();
-
TestUtils.checkFile(destination, testFile);
TestUtils.checkWrite(fout);
}
@@ -970,19 +961,16 @@ void testCopyLarge_CharExtraLength() throws IOException {
// Create streams
is = new CharArrayReader(carr);
os = new CharArrayWriter();
-
// Test our copy method
// for extra length, it reads till EOF
assertEquals(200, IOUtils.copyLarge(is, os, 0, 2000));
final char[] oarr = os.toCharArray();
-
// check that output length is correct
assertEquals(200, oarr.length);
// check that output data corresponds to input data
assertEquals(1, oarr[1]);
assertEquals(79, oarr[79]);
assertEquals((char) -1, oarr[80]);
-
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
@@ -997,18 +985,15 @@ void testCopyLarge_CharFullLength() throws IOException {
// Create streams
is = new CharArrayReader(carr);
os = new CharArrayWriter();
-
// Test our copy method
assertEquals(200, IOUtils.copyLarge(is, os, 0, -1));
final char[] oarr = os.toCharArray();
-
// check that output length is correct
assertEquals(200, oarr.length);
// check that output data corresponds to input data
assertEquals(1, oarr[1]);
assertEquals(79, oarr[79]);
assertEquals((char) -1, oarr[80]);
-
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
@@ -1023,18 +1008,15 @@ void testCopyLarge_CharNoSkip() throws IOException {
// Create streams
is = new CharArrayReader(carr);
os = new CharArrayWriter();
-
// Test our copy method
assertEquals(100, IOUtils.copyLarge(is, os, 0, 100));
final char[] oarr = os.toCharArray();
-
// check that output length is correct
assertEquals(100, oarr.length);
// check that output data corresponds to input data
assertEquals(1, oarr[1]);
assertEquals(79, oarr[79]);
assertEquals((char) -1, oarr[80]);
-
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
@@ -1049,18 +1031,15 @@ void testCopyLarge_CharSkip() throws IOException {
// Create streams
is = new CharArrayReader(carr);
os = new CharArrayWriter();
-
// Test our copy method
assertEquals(100, IOUtils.copyLarge(is, os, 10, 100));
final char[] oarr = os.toCharArray();
-
// check that output length is correct
assertEquals(100, oarr.length);
// check that output data corresponds to input data
assertEquals(11, oarr[1]);
assertEquals(79, oarr[69]);
assertEquals((char) -1, oarr[70]);
-
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
@@ -1076,15 +1055,12 @@ void testCopyLarge_CharSkipInvalid() {
@Test
void testCopyLarge_ExtraLength() throws IOException {
- try (ByteArrayInputStream is = new ByteArrayInputStream(iarr);
- ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ try (ByteArrayInputStream is = new ByteArrayInputStream(iarr); ByteArrayOutputStream os = new ByteArrayOutputStream()) {
// Create streams
-
// Test our copy method
// for extra length, it reads till EOF
assertEquals(200, IOUtils.copyLarge(is, os, 0, 2000));
final byte[] oarr = os.toByteArray();
-
// check that output length is correct
assertEquals(200, oarr.length);
// check that output data corresponds to input data
@@ -1096,12 +1072,10 @@ void testCopyLarge_ExtraLength() throws IOException {
@Test
void testCopyLarge_FullLength() throws IOException {
- try (ByteArrayInputStream is = new ByteArrayInputStream(iarr);
- ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ try (ByteArrayInputStream is = new ByteArrayInputStream(iarr); ByteArrayOutputStream os = new ByteArrayOutputStream()) {
// Test our copy method
assertEquals(200, IOUtils.copyLarge(is, os, 0, -1));
final byte[] oarr = os.toByteArray();
-
// check that output length is correct
assertEquals(200, oarr.length);
// check that output data corresponds to input data
@@ -1113,12 +1087,10 @@ void testCopyLarge_FullLength() throws IOException {
@Test
void testCopyLarge_NoSkip() throws IOException {
- try (ByteArrayInputStream is = new ByteArrayInputStream(iarr);
- ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ try (ByteArrayInputStream is = new ByteArrayInputStream(iarr); ByteArrayOutputStream os = new ByteArrayOutputStream()) {
// Test our copy method
assertEquals(100, IOUtils.copyLarge(is, os, 0, 100));
final byte[] oarr = os.toByteArray();
-
// check that output length is correct
assertEquals(100, oarr.length);
// check that output data corresponds to input data
@@ -1130,12 +1102,10 @@ void testCopyLarge_NoSkip() throws IOException {
@Test
void testCopyLarge_Skip() throws IOException {
- try (ByteArrayInputStream is = new ByteArrayInputStream(iarr);
- ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ try (ByteArrayInputStream is = new ByteArrayInputStream(iarr); ByteArrayOutputStream os = new ByteArrayOutputStream()) {
// Test our copy method
assertEquals(100, IOUtils.copyLarge(is, os, 10, 100));
final byte[] oarr = os.toByteArray();
-
// check that output length is correct
assertEquals(100, oarr.length);
// check that output data corresponds to input data
@@ -1162,18 +1132,15 @@ void testCopyLarge_SkipWithInvalidOffset() throws IOException {
// Create streams
is = new ByteArrayInputStream(iarr);
os = new ByteArrayOutputStream();
-
// Test our copy method
assertEquals(100, IOUtils.copyLarge(is, os, -10, 100));
final byte[] oarr = os.toByteArray();
-
// check that output length is correct
assertEquals(100, oarr.length);
// check that output data corresponds to input data
assertEquals(1, oarr[1]);
assertEquals(79, oarr[79]);
assertEquals(-1, oarr[80]);
-
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
@@ -1191,7 +1158,7 @@ void testRead_ReadableByteChannel() throws Exception {
assertEquals(0, buffer.remaining());
assertEquals(0, input.read(buffer));
buffer.clear();
- assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer), "Should have failed with EOFException");
+ assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer));
} finally {
IOUtils.closeQuietly(input, fileInputStream);
}
@@ -1226,11 +1193,8 @@ void testRead_InputStream_Offset_ArgumentsValidation(InputStream input, byte[] b
void testReadFully_InputStream__ReturnByteArray() throws Exception {
final byte[] bytes = "abcd1234".getBytes(StandardCharsets.UTF_8);
final ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
-
final byte[] result = IOUtils.readFully(stream, bytes.length);
-
IOUtils.closeQuietly(stream);
-
assertEqualContent(result, bytes);
}
@@ -1240,11 +1204,11 @@ void testReadFully_InputStream_ByteArray() throws Exception {
final byte[] buffer = new byte[size];
final InputStream input = new ByteArrayInputStream(new byte[size]);
- assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.readFully(input, buffer, 0, -1), "Should have failed with IndexOutOfBoundsException");
+ assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.readFully(input, buffer, 0, -1));
IOUtils.readFully(input, buffer, 0, 0);
IOUtils.readFully(input, buffer, 0, size - 1);
- assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer, 0, 2), "Should have failed with EOFException");
+ assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer, 0, 2));
IOUtils.closeQuietly(input);
}
@@ -1273,7 +1237,7 @@ void testReadFully_ReadableByteChannel() throws Exception {
assertEquals(0, input.read(buffer));
IOUtils.readFully(input, buffer);
buffer.clear();
- assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer), "Should have failed with EOFxception");
+ assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer));
} finally {
IOUtils.closeQuietly(input, fileInputStream);
}
@@ -1287,8 +1251,8 @@ void testReadFully_Reader() throws Exception {
IOUtils.readFully(input, buffer, 0, 0);
IOUtils.readFully(input, buffer, 0, size - 3);
- assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.readFully(input, buffer, 0, -1), "Should have failed with IndexOutOfBoundsException");
- assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer, 0, 5), "Should have failed with EOFException");
+ assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.readFully(input, buffer, 0, -1));
+ assertThrows(EOFException.class, () -> IOUtils.readFully(input, buffer, 0, 5));
IOUtils.closeQuietly(input);
}
@@ -1628,13 +1592,11 @@ void testSkip_ReadableByteChannel() throws Exception {
@Test
void testSkipFully_InputStream() throws Exception {
final int size = 1027;
-
try (InputStream input = new ByteArrayInputStream(new byte[size])) {
- assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1), "Should have failed with IllegalArgumentException");
-
+ assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1));
IOUtils.skipFully(input, 0);
IOUtils.skipFully(input, size - 1);
- assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2), "Should have failed with IOException");
+ assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2));
}
}
@@ -1643,11 +1605,10 @@ void testSkipFully_InputStream_Buffer_New_bytes() throws Exception {
final int size = 1027;
final Supplier bas = () -> new byte[size];
try (InputStream input = new ByteArrayInputStream(new byte[size])) {
- assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1, bas), "Should have failed with IllegalArgumentException");
-
+ assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1, bas));
IOUtils.skipFully(input, 0, bas);
IOUtils.skipFully(input, size - 1, bas);
- assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2, bas), "Should have failed with IOException");
+ assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2, bas));
}
}
@@ -1657,11 +1618,10 @@ void testSkipFully_InputStream_Buffer_Reuse_bytes() throws Exception {
final byte[] ba = new byte[size];
final Supplier bas = () -> ba;
try (InputStream input = new ByteArrayInputStream(new byte[size])) {
- assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1, bas), "Should have failed with IllegalArgumentException");
-
+ assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1, bas));
IOUtils.skipFully(input, 0, bas);
IOUtils.skipFully(input, size - 1, bas);
- assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2, bas), "Should have failed with IOException");
+ assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2, bas));
}
}
@@ -1670,11 +1630,10 @@ void testSkipFully_InputStream_Buffer_Reuse_ThreadLocal() throws Exception {
final int size = 1027;
final ThreadLocal tl = ThreadLocal.withInitial(() -> new byte[size]);
try (InputStream input = new ByteArrayInputStream(new byte[size])) {
- assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1, tl::get), "Should have failed with IllegalArgumentException");
-
+ assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1, tl::get));
IOUtils.skipFully(input, 0, tl::get);
IOUtils.skipFully(input, size - 1, tl::get);
- assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2, tl::get), "Should have failed with IOException");
+ assertThrows(IOException.class, () -> IOUtils.skipFully(input, 2, tl::get));
}
}
@@ -1683,10 +1642,10 @@ void testSkipFully_ReadableByteChannel() throws Exception {
final FileInputStream fileInputStream = new FileInputStream(testFile);
final FileChannel fileChannel = fileInputStream.getChannel();
try {
- assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(fileChannel, -1), "Should have failed with IllegalArgumentException");
+ assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(fileChannel, -1));
IOUtils.skipFully(fileChannel, 0);
IOUtils.skipFully(fileChannel, FILE_SIZE - 1);
- assertThrows(IOException.class, () -> IOUtils.skipFully(fileChannel, 2), "Should have failed with IOException");
+ assertThrows(IOException.class, () -> IOUtils.skipFully(fileChannel, 2));
} finally {
IOUtils.closeQuietly(fileChannel, fileInputStream);
}
@@ -1698,8 +1657,8 @@ void testSkipFully_Reader() throws Exception {
try (Reader input = new CharArrayReader(new char[size])) {
IOUtils.skipFully(input, 0);
IOUtils.skipFully(input, size - 3);
- assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1), "Should have failed with IllegalArgumentException");
- assertThrows(IOException.class, () -> IOUtils.skipFully(input, 5), "Should have failed with IOException");
+ assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(input, -1));
+ assertThrows(IOException.class, () -> IOUtils.skipFully(input, 5));
}
}
@@ -1711,7 +1670,6 @@ void testStringToOutputStream() throws Exception {
// Create our String. Rely on testReaderToString() to make sure this is valid.
str = IOUtils.toString(fin);
}
-
try (OutputStream fout = Files.newOutputStream(destination.toPath())) {
CopyUtils.copy(str, fout);
// Note: this method *does* flush. It is equivalent to:
@@ -1720,7 +1678,6 @@ void testStringToOutputStream() throws Exception {
// _out.flush();
// out = fout;
// note: we don't flush here; this IOUtils method does it for us
-
TestUtils.checkFile(destination, testFile);
TestUtils.checkWrite(fout);
}
@@ -1791,7 +1748,7 @@ void testToByteArray_InputStream_Size() throws Exception {
@Test
void testToByteArray_InputStream_Size_Truncated() throws Exception {
try (InputStream in = new NullInputStream(0)) {
- assertThrows(EOFException.class, () -> IOUtils.toByteArray(in, 1), "Should have failed with EOFException");
+ assertThrows(EOFException.class, () -> IOUtils.toByteArray(in, 1));
}
}
@@ -1816,8 +1773,7 @@ void testToByteArray_InputStream_Size_BufferSize_Throws(
@Test
void testToByteArray_InputStream_SizeIllegal() throws Exception {
try (InputStream fin = Files.newInputStream(testFilePath)) {
- final IOException exc = assertThrows(IOException.class, () -> IOUtils.toByteArray(fin, testFile.length() + 1),
- "Should have failed with IOException");
+ final IOException exc = assertThrows(IOException.class, () -> IOUtils.toByteArray(fin, testFile.length() + 1));
assertTrue(exc.getMessage().startsWith("Expected read size"), exc.getMessage());
}
}
@@ -1825,8 +1781,7 @@ void testToByteArray_InputStream_SizeIllegal() throws Exception {
@Test
void testToByteArray_InputStream_SizeLong() throws Exception {
try (InputStream fin = Files.newInputStream(testFilePath)) {
- final IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> IOUtils.toByteArray(fin, (long) Integer.MAX_VALUE + 1),
- "Should have failed with IllegalArgumentException");
+ final IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> IOUtils.toByteArray(fin, (long) Integer.MAX_VALUE + 1));
assertTrue(exc.getMessage().startsWith("size > Integer.MAX_VALUE"), exc.getMessage());
}
}
@@ -1864,7 +1819,6 @@ void testToByteArray_String() throws Exception {
try (Reader fin = Files.newBufferedReader(testFilePath)) {
// Create our String. Rely on testReaderToString() to make sure this is valid.
final String str = IOUtils.toString(fin);
-
final byte[] out = IOUtils.toByteArray(str);
assertEqualContent(str.getBytes(), out);
}
From 66706b4961259d7a5b2e4c32783ced10e12a3402 Mon Sep 17 00:00:00 2001
From: "Gary D. Gregory"
Date: Tue, 7 Oct 2025 08:49:59 -0400
Subject: [PATCH 011/174] Javadoc
---
.../org/apache/commons/io/filefilter/FileFilterUtils.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/apache/commons/io/filefilter/FileFilterUtils.java b/src/main/java/org/apache/commons/io/filefilter/FileFilterUtils.java
index 5e37676ac9c..937895c822c 100644
--- a/src/main/java/org/apache/commons/io/filefilter/FileFilterUtils.java
+++ b/src/main/java/org/apache/commons/io/filefilter/FileFilterUtils.java
@@ -153,7 +153,7 @@ public static IOFileFilter and(final IOFileFilter... filters) {
* @return a filter that ANDs the two specified filters
* @see #and(IOFileFilter...)
* @see AndFileFilter
- * @deprecated use {@link #and(IOFileFilter...)}
+ * @deprecated Use {@link #and(IOFileFilter...)}
*/
@Deprecated
public static IOFileFilter andFileFilter(final IOFileFilter filter1, final IOFileFilter filter2) {
@@ -603,7 +603,7 @@ public static IOFileFilter or(final IOFileFilter... filters) {
* @return a filter that ORs the two specified filters
* @see #or(IOFileFilter...)
* @see OrFileFilter
- * @deprecated use {@link #or(IOFileFilter...)}
+ * @deprecated Use {@link #or(IOFileFilter...)}
*/
@Deprecated
public static IOFileFilter orFileFilter(final IOFileFilter filter1, final IOFileFilter filter2) {
From eed5803bf23ab83b6506cfda48fae7c351d51ef2 Mon Sep 17 00:00:00 2001
From: Gary Gregory
Date: Tue, 7 Oct 2025 09:43:16 -0400
Subject: [PATCH 012/174] Javadoc
---
.../java/org/apache/commons/io/CopyUtils.java | 6 ++--
.../java/org/apache/commons/io/FileUtils.java | 12 +++----
.../java/org/apache/commons/io/HexDump.java | 2 +-
.../java/org/apache/commons/io/IOUtils.java | 34 +++++++++----------
.../commons/io/charset/CharsetDecoders.java | 2 +-
.../commons/io/charset/CharsetEncoders.java | 2 +-
.../io/filefilter/MagicNumberFileFilter.java | 2 +-
.../commons/io/input/ReaderInputStream.java | 4 +--
.../io/input/ReversedLinesFileReader.java | 2 +-
.../org/apache/commons/io/input/Tailer.java | 2 +-
.../output/AbstractByteArrayOutputStream.java | 2 +-
.../commons/io/output/LockableFileWriter.java | 2 +-
.../commons/io/output/WriterOutputStream.java | 6 ++--
13 files changed, 39 insertions(+), 39 deletions(-)
diff --git a/src/main/java/org/apache/commons/io/CopyUtils.java b/src/main/java/org/apache/commons/io/CopyUtils.java
index ad426952e4e..e22adef83be 100644
--- a/src/main/java/org/apache/commons/io/CopyUtils.java
+++ b/src/main/java/org/apache/commons/io/CopyUtils.java
@@ -179,7 +179,7 @@ public static int copy(final InputStream input, final OutputStream output) throw
* Copies and convert bytes from an {@link InputStream} to chars on a
* {@link Writer}.
*
- * This method uses the virtual machine's {@link Charset#defaultCharset() default charset} for byte-to-char conversion.
+ * This method uses the virtual machine's {@linkplain Charset#defaultCharset() default charset} for byte-to-char conversion.
*
*
* @param input the {@link InputStream} to read from
@@ -221,7 +221,7 @@ public static void copy(
* Serialize chars from a {@link Reader} to bytes on an
* {@link OutputStream}, and flush the {@link OutputStream}.
*
- * This method uses the virtual machine's {@link Charset#defaultCharset() default charset} for byte-to-char conversion.
+ * This method uses the virtual machine's {@linkplain Charset#defaultCharset() default charset} for byte-to-char conversion.
*
*
* @param input the {@link Reader} to read from
@@ -293,7 +293,7 @@ public static int copy(
* {@link OutputStream}, and
* flush the {@link OutputStream}.
*
- * This method uses the virtual machine's {@link Charset#defaultCharset() default charset} for byte-to-char conversion.
+ * This method uses the virtual machine's {@linkplain Charset#defaultCharset() default charset} for byte-to-char conversion.
*
*
* @param input the {@link String} to read from
diff --git a/src/main/java/org/apache/commons/io/FileUtils.java b/src/main/java/org/apache/commons/io/FileUtils.java
index 203fe84bfc5..523c2c1fd26 100644
--- a/src/main/java/org/apache/commons/io/FileUtils.java
+++ b/src/main/java/org/apache/commons/io/FileUtils.java
@@ -2755,7 +2755,7 @@ public static byte[] readFileToByteArray(final File file) throws IOException {
}
/**
- * Reads the contents of a file into a String using the virtual machine's {@link Charset#defaultCharset() default charset}. The
+ * Reads the contents of a file into a String using the virtual machine's {@linkplain Charset#defaultCharset() default charset}. The
* file is always closed.
*
* @param file the file to read, must not be {@code null}.
@@ -2804,7 +2804,7 @@ public static String readFileToString(final File file, final String charsetName)
}
/**
- * Reads the contents of a file line by line to a List of Strings using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * Reads the contents of a file line by line to a List of Strings using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
* The file is always closed.
*
* @param file the file to read, must not be {@code null}.
@@ -3212,7 +3212,7 @@ public static boolean waitFor(final File file, final int seconds) {
}
/**
- * Writes a CharSequence to a file creating the file if it does not exist using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * Writes a CharSequence to a file creating the file if it does not exist using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* @param file the file to write.
* @param data the content to write to the file.
@@ -3226,7 +3226,7 @@ public static void write(final File file, final CharSequence data) throws IOExce
}
/**
- * Writes a CharSequence to a file creating the file if it does not exist using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * Writes a CharSequence to a file creating the file if it does not exist using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* @param file the file to write.
* @param data the content to write to the file.
@@ -3501,7 +3501,7 @@ public static void writeLines(final File file, final String charsetName, final C
}
/**
- * Writes a String to a file creating the file if it does not exist using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * Writes a String to a file creating the file if it does not exist using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* @param file the file to write
* @param data the content to write to the file
@@ -3514,7 +3514,7 @@ public static void writeStringToFile(final File file, final String data) throws
}
/**
- * Writes a String to a file creating the file if it does not exist using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * Writes a String to a file creating the file if it does not exist using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* @param file the file to write
* @param data the content to write to the file
diff --git a/src/main/java/org/apache/commons/io/HexDump.java b/src/main/java/org/apache/commons/io/HexDump.java
index cb7efa7b2f2..41c026b2fb1 100644
--- a/src/main/java/org/apache/commons/io/HexDump.java
+++ b/src/main/java/org/apache/commons/io/HexDump.java
@@ -169,7 +169,7 @@ public static void dump(final byte[] data, final long offset,
* data array are dumped.
*
*
- * This method uses the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * This method uses the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
*
* @param data the byte array to be dumped
diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java
index aac71be0fa5..52eb5c971fc 100644
--- a/src/main/java/org/apache/commons/io/IOUtils.java
+++ b/src/main/java/org/apache/commons/io/IOUtils.java
@@ -1269,7 +1269,7 @@ public static long copy(final InputStream inputStream, final OutputStream output
/**
* Copies bytes from an {@link InputStream} to chars on a
- * {@link Writer} using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * {@link Writer} using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* This method buffers the input internally, so there is no need to use a
* {@link BufferedInputStream}.
@@ -1421,8 +1421,8 @@ public static long copy(final Reader reader, final Appendable output, final Char
}
/**
- * Copies chars from a {@link Reader} to bytes on an {@link OutputStream} using the the virtual machine's {@link Charset#defaultCharset() default charset},
- * and calling flush.
+ * Copies chars from a {@link Reader} to bytes on an {@link OutputStream} using the the virtual machine's {@linkplain Charset#defaultCharset() default
+ * charset}, and calling flush.
*
* This method buffers the input internally, so there is no need to use a {@link BufferedReader}.
*
@@ -2319,7 +2319,7 @@ public static List readLines(final CharSequence csq) throws UncheckedIOE
/**
* Gets the contents of an {@link InputStream} as a list of Strings,
- * one entry per line, using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * one entry per line, using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* This method buffers the input internally, so there is no need to use a
* {@link BufferedInputStream}.
@@ -2967,7 +2967,7 @@ static byte[] toByteArray(final IOTriFunction
/**
* Gets the contents of a {@link Reader} as a {@code byte[]}
- * using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* This method buffers the input internally, so there is no need to use a
* {@link BufferedReader}.
@@ -3032,7 +3032,7 @@ public static byte[] toByteArray(final Reader reader, final String charsetName)
/**
* Gets the contents of a {@link String} as a {@code byte[]}
- * using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* This is the same as {@link String#getBytes()}.
*
@@ -3093,7 +3093,7 @@ public static byte[] toByteArray(final URLConnection urlConnection) throws IOExc
/**
* Gets the contents of an {@link InputStream} as a character array
- * using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* This method buffers the input internally, so there is no need to use a
* {@link BufferedInputStream}.
@@ -3178,7 +3178,7 @@ public static char[] toCharArray(final Reader reader) throws IOException {
/**
* Converts the specified CharSequence to an input stream, encoded as bytes
- * using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* @param input the CharSequence to convert.
* @return an input stream.
@@ -3223,7 +3223,7 @@ public static InputStream toInputStream(final CharSequence input, final String c
/**
* Converts the specified string to an input stream, encoded as bytes
- * using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* @param input the string to convert.
* @return an input stream.
@@ -3268,7 +3268,7 @@ public static InputStream toInputStream(final String input, final String charset
/**
* Gets the contents of a {@code byte[]} as a String
- * using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* @param input the byte array to read.
* @return the requested String.
@@ -3300,7 +3300,7 @@ public static String toString(final byte[] input, final String charsetName) {
/**
* Gets the contents of an {@link InputStream} as a String
- * using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* This method buffers the input internally, so there is no need to use a
* {@link BufferedInputStream}.
@@ -3429,7 +3429,7 @@ public static String toString(final Reader reader) throws IOException {
}
/**
- * Gets the contents at the given URI using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * Gets the contents at the given URI using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* @param uri The URI source.
* @return The contents of the URL as a String.
@@ -3470,7 +3470,7 @@ public static String toString(final URI uri, final String charsetName) throws IO
}
/**
- * Gets the contents at the given URL using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * Gets the contents at the given URL using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* @param url The URL source.
* @return The contents of the URL as a String.
@@ -3528,7 +3528,7 @@ public static void write(final byte[] data, final OutputStream output)
/**
* Writes bytes from a {@code byte[]} to chars on a {@link Writer}
- * using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* This method uses {@link String#String(byte[])}.
*
@@ -3591,7 +3591,7 @@ public static void write(final byte[] data, final Writer writer, final String ch
/**
* Writes chars from a {@code char[]} to bytes on an {@link OutputStream}.
*
- * This method uses the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * This method uses the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
*
* @param data the char array to write, do not modify during output, null ignored.
@@ -3742,7 +3742,7 @@ public static void write(final CharSequence data, final Writer writer) throws IO
/**
* Writes chars from a {@link String} to bytes on an
- * {@link OutputStream} using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * {@link OutputStream} using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* This method uses {@link String#getBytes()}.
*
@@ -3941,7 +3941,7 @@ public static void writeChunked(final char[] data, final Writer writer) throws I
/**
* Writes the {@link #toString()} value of each item in a collection to an {@link OutputStream} line by line, using the virtual machine's
- * {@link Charset#defaultCharset() default charset} and the specified line ending.
+ * {@linkplain Charset#defaultCharset() default charset} and the specified line ending.
*
* @param lines the lines to write, null entries produce blank lines.
* @param lineEnding the line separator to use, null is system default.
diff --git a/src/main/java/org/apache/commons/io/charset/CharsetDecoders.java b/src/main/java/org/apache/commons/io/charset/CharsetDecoders.java
index 8595be79e8a..a8aa9a3d50b 100644
--- a/src/main/java/org/apache/commons/io/charset/CharsetDecoders.java
+++ b/src/main/java/org/apache/commons/io/charset/CharsetDecoders.java
@@ -30,7 +30,7 @@ public final class CharsetDecoders {
/**
* Returns the given non-null CharsetDecoder or a new default CharsetDecoder.
*
- * Null input maps to the virtual machine's {@link Charset#defaultCharset() default charset} decoder.
+ * Null input maps to the virtual machine's {@linkplain Charset#defaultCharset() default charset} decoder.
*
*
* @param charsetDecoder The CharsetDecoder to test.
diff --git a/src/main/java/org/apache/commons/io/charset/CharsetEncoders.java b/src/main/java/org/apache/commons/io/charset/CharsetEncoders.java
index d0f9cd04be1..a1fff5cf1f3 100644
--- a/src/main/java/org/apache/commons/io/charset/CharsetEncoders.java
+++ b/src/main/java/org/apache/commons/io/charset/CharsetEncoders.java
@@ -31,7 +31,7 @@ public final class CharsetEncoders {
/**
* Returns the given non-null CharsetEncoder or a new default CharsetEncoder.
*
- * Null input maps to the virtual machine's {@link Charset#defaultCharset() default charset} decoder.
+ * Null input maps to the virtual machine's {@linkplain Charset#defaultCharset() default charset} decoder.
*
*
* @param charsetEncoder The CharsetEncoder to test.
diff --git a/src/main/java/org/apache/commons/io/filefilter/MagicNumberFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/MagicNumberFileFilter.java
index 79aa55d68c1..5868af39a32 100644
--- a/src/main/java/org/apache/commons/io/filefilter/MagicNumberFileFilter.java
+++ b/src/main/java/org/apache/commons/io/filefilter/MagicNumberFileFilter.java
@@ -218,7 +218,7 @@ public MagicNumberFileFilter(final String magicNumber) {
* MagicNumberFileFilter tarFileFilter = MagicNumberFileFilter("ustar", 257);
*
*
- * This method uses the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * This method uses the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
*
* @param magicNumber the magic number to look for in the file.
diff --git a/src/main/java/org/apache/commons/io/input/ReaderInputStream.java b/src/main/java/org/apache/commons/io/input/ReaderInputStream.java
index 4d22d28f891..09d7bbaa1a8 100644
--- a/src/main/java/org/apache/commons/io/input/ReaderInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/ReaderInputStream.java
@@ -225,8 +225,8 @@ private ReaderInputStream(final Builder builder) throws IOException {
}
/**
- * Constructs a new {@link ReaderInputStream} that uses the virtual machine's {@link Charset#defaultCharset() default charset} with a default input buffer
- * size of {@value IOUtils#DEFAULT_BUFFER_SIZE} characters.
+ * Constructs a new {@link ReaderInputStream} that uses the virtual machine's {@linkplain Charset#defaultCharset() default charset} with a default input
+ * buffer size of {@value IOUtils#DEFAULT_BUFFER_SIZE} characters.
*
* @param reader the target {@link Reader}
* @deprecated Use {@link ReaderInputStream#builder()} instead
diff --git a/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java b/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java
index f87b109abb9..e88606d9e76 100644
--- a/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java
+++ b/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java
@@ -354,7 +354,7 @@ private ReversedLinesFileReader(final Builder builder) throws IOException {
}
/**
- * Constructs a ReversedLinesFileReader with default block size of 4KB and the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * Constructs a ReversedLinesFileReader with default block size of 4KB and the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* @param file the file to be read
* @throws IOException if an I/O error occurs.
diff --git a/src/main/java/org/apache/commons/io/input/Tailer.java b/src/main/java/org/apache/commons/io/input/Tailer.java
index 4d9fe9c4751..620ba6febcd 100644
--- a/src/main/java/org/apache/commons/io/input/Tailer.java
+++ b/src/main/java/org/apache/commons/io/input/Tailer.java
@@ -495,7 +495,7 @@ public String toString() {
private static final String RAF_READ_ONLY_MODE = "r";
/**
- * The the virtual machine's {@link Charset#defaultCharset() default charset} used for reading files.
+ * The the virtual machine's {@linkplain Charset#defaultCharset() default charset} used for reading files.
*/
private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
diff --git a/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java b/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java
index 6b095474fb2..dc2de64a4a2 100644
--- a/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java
+++ b/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java
@@ -276,7 +276,7 @@ protected InputStream toInputStream(final InputStreamCon
}
/**
- * Gets the current contents of this byte stream as a string using the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * Gets the current contents of this byte stream as a string using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
* @return the contents of the byte array as a String.
* @see java.io.ByteArrayOutputStream#toString()
diff --git a/src/main/java/org/apache/commons/io/output/LockableFileWriter.java b/src/main/java/org/apache/commons/io/output/LockableFileWriter.java
index f15048e5c0e..1ee9391af8f 100644
--- a/src/main/java/org/apache/commons/io/output/LockableFileWriter.java
+++ b/src/main/java/org/apache/commons/io/output/LockableFileWriter.java
@@ -204,7 +204,7 @@ public LockableFileWriter(final File file, final boolean append) throws IOExcept
/**
* Constructs a LockableFileWriter.
*
- * The new instance uses the virtual machine's {@link Charset#defaultCharset() default charset}.
+ * The new instance uses the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
*
*
* @param file the file to write to, not null.
diff --git a/src/main/java/org/apache/commons/io/output/WriterOutputStream.java b/src/main/java/org/apache/commons/io/output/WriterOutputStream.java
index d17318f79b6..9292aaf70e8 100644
--- a/src/main/java/org/apache/commons/io/output/WriterOutputStream.java
+++ b/src/main/java/org/apache/commons/io/output/WriterOutputStream.java
@@ -251,9 +251,9 @@ private WriterOutputStream(final Builder builder) throws IOException {
}
/**
- * Constructs a new {@link WriterOutputStream} that uses the virtual machine's {@link Charset#defaultCharset() default charset} and with a default output
- * buffer size of {@value #BUFFER_SIZE} characters. The output buffer will only be flushed when it overflows or when {@link #flush()} or {@link #close()} is
- * called.
+ * Constructs a new {@link WriterOutputStream} that uses the virtual machine's {@linkplain Charset#defaultCharset() default charset} and with a default
+ * output buffer size of {@value #BUFFER_SIZE} characters. The output buffer will only be flushed when it overflows or when {@link #flush()} or
+ * {@link #close()} is called.
*
* @param writer the target {@link Writer}.
* @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
From c174b656695569f6817f0d877447ce9ec555d98b Mon Sep 17 00:00:00 2001
From: "Gary D. Gregory"
Date: Wed, 8 Oct 2025 14:59:04 -0400
Subject: [PATCH 013/174] Bump org.apache.commons:commons-parent from 88 to 89
---
pom.xml | 2 +-
src/changes/changes.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/pom.xml b/pom.xml
index c356baf502a..10db84b657f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,7 @@
org.apache.commonscommons-parent
- 88
+ 894.0.0commons-io
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 3bcc4145197..cec7600b7c5 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -72,7 +72,7 @@ The type attribute can be add,update,fix,remove.
Add CloseShieldChannel to close-shielded NIO Channels #786.Added IOUtils.checkFromIndexSize as a Java 8 backport of Objects.checkFromIndexSize #790.
- Bump org.apache.commons:commons-parent from 85 to 88 #774, #783.
+ Bump org.apache.commons:commons-parent from 85 to 89 #774, #783.[test] Bump commons-codec:commons-codec from 1.18.0 to 1.19.0.[test] Bump commons.bytebuddy.version from 1.17.6 to 1.17.7 #769.[test] Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0.
From 806ada585296d5bd1043d581e133242c29072a7e Mon Sep 17 00:00:00 2001
From: "Gary D. Gregory"
Date: Wed, 8 Oct 2025 14:59:19 -0400
Subject: [PATCH 014/174] Fix typos
---
src/changes/changes.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index cec7600b7c5..302b397962e 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -55,7 +55,7 @@ The type attribute can be add,update,fix,remove.
Deprecate IOUtils.readFully(InputStream, int) in favor of toByteArray(InputStream, int).IOUtils.toByteArray(InputStream) now throws IOException on byte array overflow.Javadoc general improvements.
- JIOUtils.toByteArray now throws EOFException when not enough data is available #796.
+ IOUtils.toByteArray() now throws EOFException when not enough data is available #796.FileUtils#byteCountToDisplaySize() supports Zettabyte, Yottabyte, Ronnabyte and Quettabyte #763.Add org.apache.commons.io.FileUtils.ONE_RB #763.
From 5ad31b0c1a3f670b2eac34747218a25d493d7bc8 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 10 Oct 2025 07:22:58 +0200
Subject: [PATCH 015/174] Bump github/codeql-action from 3.30.6 to 4.30.7
(#797)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.6 to 4.30.7.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/64d10c13136e1c5bce3e5fbde8d4906eeaafc885...e296a935590eb16afc0c0108289f68c87e2a89a5)
---
updated-dependencies:
- dependency-name: github/codeql-action
dependency-version: 4.30.7
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/codeql-analysis.yml | 6 +++---
.github/workflows/scorecards-analysis.yml | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index db18ec4ae56..4d91dcc9f39 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -62,7 +62,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # 3.29.5
+ uses: github/codeql-action/init@e296a935590eb16afc0c0108289f68c87e2a89a5 # 3.29.5
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -73,7 +73,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # 3.29.5
+ uses: github/codeql-action/autobuild@e296a935590eb16afc0c0108289f68c87e2a89a5 # 3.29.5
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -87,4 +87,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # 3.29.5
+ uses: github/codeql-action/analyze@e296a935590eb16afc0c0108289f68c87e2a89a5 # 3.29.5
diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml
index 3b7d3020e33..0de672c8692 100644
--- a/.github/workflows/scorecards-analysis.yml
+++ b/.github/workflows/scorecards-analysis.yml
@@ -66,6 +66,6 @@ jobs:
retention-days: 5
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # 3.29.5
+ uses: github/codeql-action/upload-sarif@e296a935590eb16afc0c0108289f68c87e2a89a5 # 3.29.5
with:
sarif_file: results.sarif
From 12084ca7597f3e35f7d6d2cb8f6de646eab54f38 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 10 Oct 2025 07:23:56 +0200
Subject: [PATCH 016/174] Bump commons.bytebuddy.version from 1.17.7 to 1.17.8
(#798)
Bumps `commons.bytebuddy.version` from 1.17.7 to 1.17.8.
Updates `net.bytebuddy:byte-buddy` from 1.17.7 to 1.17.8
- [Release notes](https://github.com/raphw/byte-buddy/releases)
- [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md)
- [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.17.7...byte-buddy-1.17.8)
Updates `net.bytebuddy:byte-buddy-agent` from 1.17.7 to 1.17.8
- [Release notes](https://github.com/raphw/byte-buddy/releases)
- [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md)
- [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.17.7...byte-buddy-1.17.8)
---
updated-dependencies:
- dependency-name: net.bytebuddy:byte-buddy
dependency-version: 1.17.8
dependency-type: direct:development
update-type: version-update:semver-patch
- dependency-name: net.bytebuddy:byte-buddy-agent
dependency-version: 1.17.8
dependency-type: direct:development
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 10db84b657f..2f2162f800e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -146,7 +146,7 @@ file comparators, endian transformation classes, and much more.
https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-io/site-content
- 1.17.7
+ 1.17.8falsetrue
From e19519ad1a61ec6852601d28ffe94a8967e4e3e0 Mon Sep 17 00:00:00 2001
From: "Gary D. Gregory"
Date: Fri, 10 Oct 2025 06:58:55 -0400
Subject: [PATCH 017/174] [test] Bump commons.bytebuddy.version from 1.17.7 to
1.17.8
---
src/changes/changes.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 302b397962e..ecee3aeca01 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -74,7 +74,7 @@ The type attribute can be add,update,fix,remove.
Bump org.apache.commons:commons-parent from 85 to 89 #774, #783.[test] Bump commons-codec:commons-codec from 1.18.0 to 1.19.0.
- [test] Bump commons.bytebuddy.version from 1.17.6 to 1.17.7 #769.
+ [test] Bump commons.bytebuddy.version from 1.17.6 to 1.17.8 #769.[test] Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0.Inline private constant field ProxyInputStream.exceptionHandler #780.
From b95cbfa29df2928578a133b395a3e03dd05658fb Mon Sep 17 00:00:00 2001
From: "Gary D. Gregory"
Date: Sat, 11 Oct 2025 06:59:52 -0400
Subject: [PATCH 018/174] PMD: Discontinue using Rule name
category/java/errorprone.xml/UselessOperationOnImmutable as it is scheduled
for removal from PMD.
PMD 8.0.0 will remove support for this Rule.
---
src/conf/maven-pmd-plugin.xml | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/conf/maven-pmd-plugin.xml b/src/conf/maven-pmd-plugin.xml
index 215ac295ede..9d51e182ff9 100644
--- a/src/conf/maven-pmd-plugin.xml
+++ b/src/conf/maven-pmd-plugin.xml
@@ -78,7 +78,6 @@ under the License.
-
From 2542ebc485b095931bb9215a1715554a2bfd77de Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Tue, 14 Oct 2025 20:58:57 +0200
Subject: [PATCH 019/174] Fixes interface discovery in `CloseShieldChannel`
(#800)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This PR is split from #799.
The `CloseShieldChannel` implementation only inspects interfaces **directly** implemented by the given channel’s class, ignoring those inherited from its superclasses.
As a result, proxies for types such as `FileChannel` does not expose any of the interfaces declared on `FileChannel` itself.
---
.../io/channels/CloseShieldChannel.java | 10 +++++---
.../io/channels/CloseShieldChannelTest.java | 24 +++++++++++++++++++
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java b/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
index a9a462da73b..bde0feb25cc 100644
--- a/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
+++ b/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
@@ -40,11 +40,15 @@ public final class CloseShieldChannel {
private static final Class>[] EMPTY = {};
private static Set> collectChannelInterfaces(final Class> type, final Set> out) {
+ Class> currentType = type;
// Visit interfaces
- for (final Class> iface : type.getInterfaces()) {
- if (Channel.class.isAssignableFrom(iface) && out.add(iface)) {
- collectChannelInterfaces(iface, out);
+ while (currentType != null) {
+ for (final Class> iface : currentType.getInterfaces()) {
+ if (Channel.class.isAssignableFrom(iface) && out.add(iface)) {
+ collectChannelInterfaces(iface, out);
+ }
}
+ currentType = currentType.getSuperclass();
}
return out;
}
diff --git a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
index ac7e85b7a89..42a23f0af1c 100644
--- a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
+++ b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
@@ -20,6 +20,7 @@
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -32,11 +33,13 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import java.io.IOException;
import java.nio.channels.AsynchronousByteChannel;
import java.nio.channels.AsynchronousChannel;
import java.nio.channels.ByteChannel;
import java.nio.channels.Channel;
import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.InterruptibleChannel;
import java.nio.channels.MulticastChannel;
@@ -45,9 +48,12 @@
import java.nio.channels.ScatteringByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.WritableByteChannel;
+import java.nio.file.Path;
import java.util.stream.Stream;
+import org.apache.commons.io.FileUtils;
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;
@@ -280,4 +286,22 @@ void testWritableByteChannelMethods() throws Exception {
assertThrows(ClosedChannelException.class, () -> shield.write(null));
verifyNoMoreInteractions(channel);
}
+
+ @Test
+ void testCorrectlyDetectsInterfaces(@TempDir Path tempDir) throws IOException {
+ final Path testFile = tempDir.resolve("test.txt");
+ FileUtils.touch(testFile.toFile());
+ try (FileChannel channel = FileChannel.open(testFile); Channel shield = CloseShieldChannel.wrap(channel)) {
+ assertInstanceOf(SeekableByteChannel.class, shield);
+ assertInstanceOf(GatheringByteChannel.class, shield);
+ assertInstanceOf(WritableByteChannel.class, shield);
+ assertInstanceOf(ScatteringByteChannel.class, shield);
+ assertInstanceOf(ReadableByteChannel.class, shield);
+ assertInstanceOf(InterruptibleChannel.class, shield);
+ assertInstanceOf(ByteChannel.class, shield);
+ assertInstanceOf(Channel.class, shield);
+ // These are not interfaces, so can not be implemented
+ assertFalse(shield instanceof FileChannel, "not FileChannel");
+ }
+ }
}
From a0a6db8d55928b02888351fe40257bcd0d871095 Mon Sep 17 00:00:00 2001
From: Gary Gregory
Date: Tue, 14 Oct 2025 15:00:55 -0400
Subject: [PATCH 020/174] Fix interface discovery in `CloseShieldChannel` #800
Sort members
---
src/changes/changes.xml | 1 +
.../io/channels/CloseShieldChannelTest.java | 36 +++++++++----------
2 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ecee3aeca01..ead8994de83 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -56,6 +56,7 @@ The type attribute can be add,update,fix,remove.
IOUtils.toByteArray(InputStream) now throws IOException on byte array overflow.Javadoc general improvements.IOUtils.toByteArray() now throws EOFException when not enough data is available #796.
+ Fix interface discovery in `CloseShieldChannel` #800.FileUtils#byteCountToDisplaySize() supports Zettabyte, Yottabyte, Ronnabyte and Quettabyte #763.Add org.apache.commons.io.FileUtils.ONE_RB #763.
diff --git a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
index 42a23f0af1c..47d04df95d6 100644
--- a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
+++ b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
@@ -119,6 +119,24 @@ void testCloseIsShielded(final Class extends Channel> channelClass) throws Exc
verify(channel, times(2)).isOpen();
}
+ @Test
+ void testCorrectlyDetectsInterfaces(@TempDir Path tempDir) throws IOException {
+ final Path testFile = tempDir.resolve("test.txt");
+ FileUtils.touch(testFile.toFile());
+ try (FileChannel channel = FileChannel.open(testFile); Channel shield = CloseShieldChannel.wrap(channel)) {
+ assertInstanceOf(SeekableByteChannel.class, shield);
+ assertInstanceOf(GatheringByteChannel.class, shield);
+ assertInstanceOf(WritableByteChannel.class, shield);
+ assertInstanceOf(ScatteringByteChannel.class, shield);
+ assertInstanceOf(ReadableByteChannel.class, shield);
+ assertInstanceOf(InterruptibleChannel.class, shield);
+ assertInstanceOf(ByteChannel.class, shield);
+ assertInstanceOf(Channel.class, shield);
+ // These are not interfaces, so can not be implemented
+ assertFalse(shield instanceof FileChannel, "not FileChannel");
+ }
+ }
+
@Test
void testDoesNotDoubleWrap() {
final ByteChannel channel = mock(ByteChannel.class);
@@ -286,22 +304,4 @@ void testWritableByteChannelMethods() throws Exception {
assertThrows(ClosedChannelException.class, () -> shield.write(null));
verifyNoMoreInteractions(channel);
}
-
- @Test
- void testCorrectlyDetectsInterfaces(@TempDir Path tempDir) throws IOException {
- final Path testFile = tempDir.resolve("test.txt");
- FileUtils.touch(testFile.toFile());
- try (FileChannel channel = FileChannel.open(testFile); Channel shield = CloseShieldChannel.wrap(channel)) {
- assertInstanceOf(SeekableByteChannel.class, shield);
- assertInstanceOf(GatheringByteChannel.class, shield);
- assertInstanceOf(WritableByteChannel.class, shield);
- assertInstanceOf(ScatteringByteChannel.class, shield);
- assertInstanceOf(ReadableByteChannel.class, shield);
- assertInstanceOf(InterruptibleChannel.class, shield);
- assertInstanceOf(ByteChannel.class, shield);
- assertInstanceOf(Channel.class, shield);
- // These are not interfaces, so can not be implemented
- assertFalse(shield instanceof FileChannel, "not FileChannel");
- }
- }
}
From 5c6a2e60b9a2b1aecf6765f8dfceebc9d58153f9 Mon Sep 17 00:00:00 2001
From: Gary Gregory
Date: Tue, 14 Oct 2025 15:01:43 -0400
Subject: [PATCH 021/174] Don't need need this entry since we have one already
for the new class
---
src/changes/changes.xml | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ead8994de83..ecee3aeca01 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -56,7 +56,6 @@ The type attribute can be add,update,fix,remove.
IOUtils.toByteArray(InputStream) now throws IOException on byte array overflow.Javadoc general improvements.IOUtils.toByteArray() now throws EOFException when not enough data is available #796.
- Fix interface discovery in `CloseShieldChannel` #800.FileUtils#byteCountToDisplaySize() supports Zettabyte, Yottabyte, Ronnabyte and Quettabyte #763.Add org.apache.commons.io.FileUtils.ONE_RB #763.
From f51d19cabad7b8c67e2992a2ab22465658249485 Mon Sep 17 00:00:00 2001
From: Gary Gregory
Date: Tue, 14 Oct 2025 15:02:23 -0400
Subject: [PATCH 022/174] Sort members
---
.../java/org/apache/commons/io/IOUtils.java | 12 +-
.../org/apache/commons/io/FileUtilsTest.java | 12 +-
.../org/apache/commons/io/IOUtilsTest.java | 184 +++++++++---------
.../UnsynchronizedBufferedReaderTest.java | 54 ++---
4 files changed, 131 insertions(+), 131 deletions(-)
diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java
index 52eb5c971fc..a1f9b0709a2 100644
--- a/src/main/java/org/apache/commons/io/IOUtils.java
+++ b/src/main/java/org/apache/commons/io/IOUtils.java
@@ -500,6 +500,12 @@ public static void checkFromIndexSize(final char[] array, final int off, final i
checkFromIndexSize(off, len, Objects.requireNonNull(array, "char array").length);
}
+ static void checkFromIndexSize(final int off, final int len, final int arrayLength) {
+ if ((off | len | arrayLength) < 0 || arrayLength - len < off) {
+ throw new IndexOutOfBoundsException(String.format("Range [%s, % list(final File startDirectory) throws IOException {
*/
private static final ListDirectoryWalker LIST_WALKER = new ListDirectoryWalker();
+ private static void setDosReadOnly(Path p, boolean readOnly) throws IOException {
+ if (Files.getFileStore(p).supportsFileAttributeView(DosFileAttributeView.class)) {
+ Files.setAttribute(p, "dos:readonly", readOnly, LinkOption.NOFOLLOW_LINKS);
+ }
+ }
private File testFile1;
private File testFile2;
private long testFile1Size;
+
private long testFile2Size;
private void assertContentMatchesAfterCopyURLToFileFor(final String resourceName, final File destination) throws IOException {
@@ -1758,12 +1764,6 @@ void testForceDeleteReadOnlyFile() throws Exception {
}
}
- private static void setDosReadOnly(Path p, boolean readOnly) throws IOException {
- if (Files.getFileStore(p).supportsFileAttributeView(DosFileAttributeView.class)) {
- Files.setAttribute(p, "dos:readonly", readOnly, LinkOption.NOFOLLOW_LINKS);
- }
- }
-
@Test
public void testForceDeleteSymlink() throws Exception {
final ImmutablePair pair = createTempSymbolicLinkedRelativeDir();
diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java
index 8a830ec316a..e9fed5c0c0e 100644
--- a/src/test/java/org/apache/commons/io/IOUtilsTest.java
+++ b/src/test/java/org/apache/commons/io/IOUtilsTest.java
@@ -138,6 +138,71 @@ public static void beforeAll() {
IO.clear();
}
+ static Stream invalidRead_InputStream_Offset_ArgumentsProvider() {
+ final InputStream input = new ByteArrayInputStream(new byte[10]);
+ final byte[] b = new byte[10];
+ return Stream.of(
+ // input is null
+ Arguments.of(null, b, 0, 1, NullPointerException.class),
+ // b is null
+ Arguments.of(input, null, 0, 1, NullPointerException.class),
+ // off is negative
+ Arguments.of(input, b, -1, 1, IndexOutOfBoundsException.class),
+ // len is negative
+ Arguments.of(input, b, 0, -1, IndexOutOfBoundsException.class),
+ // off + len is too big
+ Arguments.of(input, b, 1, 10, IndexOutOfBoundsException.class),
+ // off + len is too big
+ Arguments.of(input, b, 10, 1, IndexOutOfBoundsException.class)
+ );
+ }
+
+ static Stream testCheckFromIndexSizeInvalidCases() {
+ return Stream.of(
+ Arguments.of(-1, 0, 42),
+ Arguments.of(0, -1, 42),
+ Arguments.of(0, 0, -1),
+ // off + len > arrayLength
+ Arguments.of(1, 42, 42),
+ Arguments.of(Integer.MAX_VALUE, 1, Integer.MAX_VALUE)
+ );
+ }
+
+ static Stream testCheckFromIndexSizeValidCases() {
+ return Stream.of(
+ // Valid cases
+ Arguments.of(0, 0, 42),
+ Arguments.of(0, 1, 42),
+ Arguments.of(0, 42, 42),
+ Arguments.of(41, 1, 42),
+ Arguments.of(42, 0, 42)
+ );
+ }
+
+ static Stream testCheckFromToIndexInvalidCases() {
+ return Stream.of(
+ Arguments.of(-1, 0, 42),
+ Arguments.of(0, -1, 42),
+ Arguments.of(0, 0, -1),
+ // from > to
+ Arguments.of(1, 0, 42),
+ // to > arrayLength
+ Arguments.of(0, 43, 42),
+ Arguments.of(1, 43, 42)
+ );
+ }
+
+ static Stream testCheckFromToIndexValidCases() {
+ return Stream.of(
+ // Valid cases
+ Arguments.of(0, 0, 42),
+ Arguments.of(0, 1, 42),
+ Arguments.of(0, 42, 42),
+ Arguments.of(41, 42, 42),
+ Arguments.of(42, 42, 42)
+ );
+ }
+
private static Stream testToByteArray_InputStream_Size_BufferSize_Succeeds() {
final byte[] data = new byte[1024];
for (int i = 0; i < 1024; i++) {
@@ -354,34 +419,6 @@ void testByteArrayWithNegativeSize() {
assertThrows(NegativeArraySizeException.class, () -> IOUtils.byteArray(-1));
}
- static Stream testCheckFromIndexSizeValidCases() {
- return Stream.of(
- // Valid cases
- Arguments.of(0, 0, 42),
- Arguments.of(0, 1, 42),
- Arguments.of(0, 42, 42),
- Arguments.of(41, 1, 42),
- Arguments.of(42, 0, 42)
- );
- }
-
- @ParameterizedTest
- @MethodSource
- void testCheckFromIndexSizeValidCases(int off, int len, int arrayLength) {
- assertDoesNotThrow(() -> IOUtils.checkFromIndexSize(off, len, arrayLength));
- }
-
- static Stream testCheckFromIndexSizeInvalidCases() {
- return Stream.of(
- Arguments.of(-1, 0, 42),
- Arguments.of(0, -1, 42),
- Arguments.of(0, 0, -1),
- // off + len > arrayLength
- Arguments.of(1, 42, 42),
- Arguments.of(Integer.MAX_VALUE, 1, Integer.MAX_VALUE)
- );
- }
-
@ParameterizedTest
@MethodSource
void testCheckFromIndexSizeInvalidCases(int off, int len, int arrayLength) {
@@ -402,34 +439,10 @@ void testCheckFromIndexSizeInvalidCases(int off, int len, int arrayLength) {
}
}
- static Stream testCheckFromToIndexValidCases() {
- return Stream.of(
- // Valid cases
- Arguments.of(0, 0, 42),
- Arguments.of(0, 1, 42),
- Arguments.of(0, 42, 42),
- Arguments.of(41, 42, 42),
- Arguments.of(42, 42, 42)
- );
- }
-
@ParameterizedTest
@MethodSource
- void testCheckFromToIndexValidCases(int from, int to, int arrayLength) {
- assertDoesNotThrow(() -> IOUtils.checkFromToIndex(from, to, arrayLength));
- }
-
- static Stream testCheckFromToIndexInvalidCases() {
- return Stream.of(
- Arguments.of(-1, 0, 42),
- Arguments.of(0, -1, 42),
- Arguments.of(0, 0, -1),
- // from > to
- Arguments.of(1, 0, 42),
- // to > arrayLength
- Arguments.of(0, 43, 42),
- Arguments.of(1, 43, 42)
- );
+ void testCheckFromIndexSizeValidCases(int off, int len, int arrayLength) {
+ assertDoesNotThrow(() -> IOUtils.checkFromIndexSize(off, len, arrayLength));
}
@ParameterizedTest
@@ -452,6 +465,12 @@ void testCheckFromToIndexInvalidCases(int from, int to, int arrayLength) {
}
}
+ @ParameterizedTest
+ @MethodSource
+ void testCheckFromToIndexValidCases(int from, int to, int arrayLength) {
+ assertDoesNotThrow(() -> IOUtils.checkFromToIndex(from, to, arrayLength));
+ }
+
@Test
void testClose() {
assertDoesNotThrow(() -> IOUtils.close((Closeable) null));
@@ -1147,6 +1166,12 @@ void testCopyLarge_SkipWithInvalidOffset() throws IOException {
}
}
+ @ParameterizedTest
+ @MethodSource("invalidRead_InputStream_Offset_ArgumentsProvider")
+ void testRead_InputStream_Offset_ArgumentsValidation(InputStream input, byte[] b, int off, int len, Class extends Throwable> expected) {
+ assertThrows(expected, () -> IOUtils.read(input, b, off, len));
+ }
+
@Test
void testRead_ReadableByteChannel() throws Exception {
final ByteBuffer buffer = ByteBuffer.allocate(FILE_SIZE);
@@ -1164,31 +1189,6 @@ void testRead_ReadableByteChannel() throws Exception {
}
}
- static Stream invalidRead_InputStream_Offset_ArgumentsProvider() {
- final InputStream input = new ByteArrayInputStream(new byte[10]);
- final byte[] b = new byte[10];
- return Stream.of(
- // input is null
- Arguments.of(null, b, 0, 1, NullPointerException.class),
- // b is null
- Arguments.of(input, null, 0, 1, NullPointerException.class),
- // off is negative
- Arguments.of(input, b, -1, 1, IndexOutOfBoundsException.class),
- // len is negative
- Arguments.of(input, b, 0, -1, IndexOutOfBoundsException.class),
- // off + len is too big
- Arguments.of(input, b, 1, 10, IndexOutOfBoundsException.class),
- // off + len is too big
- Arguments.of(input, b, 10, 1, IndexOutOfBoundsException.class)
- );
- }
-
- @ParameterizedTest
- @MethodSource("invalidRead_InputStream_Offset_ArgumentsProvider")
- void testRead_InputStream_Offset_ArgumentsValidation(InputStream input, byte[] b, int off, int len, Class extends Throwable> expected) {
- assertThrows(expected, () -> IOUtils.read(input, b, off, len));
- }
-
@Test
void testReadFully_InputStream__ReturnByteArray() throws Exception {
final byte[] bytes = "abcd1234".getBytes(StandardCharsets.UTF_8);
@@ -1221,6 +1221,12 @@ void testReadFully_InputStream_Offset() throws Exception {
IOUtils.closeQuietly(stream);
}
+ @ParameterizedTest
+ @MethodSource("invalidRead_InputStream_Offset_ArgumentsProvider")
+ void testReadFully_InputStream_Offset_ArgumentsValidation(InputStream input, byte[] b, int off, int len, Class extends Throwable> expected) {
+ assertThrows(expected, () -> IOUtils.read(input, b, off, len));
+ }
+
@Test
void testReadFully_ReadableByteChannel() throws Exception {
final ByteBuffer buffer = ByteBuffer.allocate(FILE_SIZE);
@@ -1265,12 +1271,6 @@ void testReadFully_Reader_Offset() throws Exception {
IOUtils.closeQuietly(reader);
}
- @ParameterizedTest
- @MethodSource("invalidRead_InputStream_Offset_ArgumentsProvider")
- void testReadFully_InputStream_Offset_ArgumentsValidation(InputStream input, byte[] b, int off, int len, Class extends Throwable> expected) {
- assertThrows(expected, () -> IOUtils.read(input, b, off, len));
- }
-
@Test
void testReadLines_CharSequence() throws IOException {
final File file = TestUtils.newFile(temporaryFolder, "lines.txt");
@@ -1745,13 +1745,6 @@ void testToByteArray_InputStream_Size() throws Exception {
}
}
- @Test
- void testToByteArray_InputStream_Size_Truncated() throws Exception {
- try (InputStream in = new NullInputStream(0)) {
- assertThrows(EOFException.class, () -> IOUtils.toByteArray(in, 1));
- }
- }
-
@ParameterizedTest
@MethodSource
void testToByteArray_InputStream_Size_BufferSize_Succeeds(final byte[] data, final int size, final int bufferSize) throws IOException {
@@ -1770,6 +1763,13 @@ void testToByteArray_InputStream_Size_BufferSize_Throws(
}
}
+ @Test
+ void testToByteArray_InputStream_Size_Truncated() throws Exception {
+ try (InputStream in = new NullInputStream(0)) {
+ assertThrows(EOFException.class, () -> IOUtils.toByteArray(in, 1));
+ }
+ }
+
@Test
void testToByteArray_InputStream_SizeIllegal() throws Exception {
try (InputStream fin = Files.newInputStream(testFilePath)) {
diff --git a/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java b/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java
index a144bc418d0..2a2b21a22ad 100644
--- a/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java
+++ b/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java
@@ -347,6 +347,33 @@ void testRead() throws IOException {
}
}
+ @Test
+ void testReadArray_HARMONY_54() throws IOException {
+ // Regression for HARMONY-54
+ final char[] ch = {};
+ @SuppressWarnings("resource")
+ final UnsynchronizedBufferedReader reader = new UnsynchronizedBufferedReader(new CharArrayReader(ch));
+ // Check exception thrown when the reader is open.
+ assertThrows(NullPointerException.class, () -> reader.read(null, 1, 0));
+
+ // Now check IOException is thrown in preference to
+ // NullPointerexception when the reader is closed.
+ reader.close();
+ assertThrows(IOException.class, () -> reader.read(null, 1, 0));
+
+ // And check that the IOException is thrown before
+ // ArrayIndexOutOfBoundException
+ assertThrows(IOException.class, () -> reader.read(ch, 0, 42));
+ }
+
+ @Test
+ void testReadArray_HARMONY_831() throws IOException {
+ // regression for HARMONY-831
+ try (Reader reader = new UnsynchronizedBufferedReader(new PipedReader(), 9)) {
+ assertThrows(IndexOutOfBoundsException.class, () -> reader.read(new char[] {}, 7, 0));
+ }
+ }
+
/**
* Tests {@link UnsynchronizedBufferedReader#read(char[], int, int)}.
*
@@ -437,33 +464,6 @@ public boolean ready() throws IOException {
}
}
- @Test
- void testReadArray_HARMONY_831() throws IOException {
- // regression for HARMONY-831
- try (Reader reader = new UnsynchronizedBufferedReader(new PipedReader(), 9)) {
- assertThrows(IndexOutOfBoundsException.class, () -> reader.read(new char[] {}, 7, 0));
- }
- }
-
- @Test
- void testReadArray_HARMONY_54() throws IOException {
- // Regression for HARMONY-54
- final char[] ch = {};
- @SuppressWarnings("resource")
- final UnsynchronizedBufferedReader reader = new UnsynchronizedBufferedReader(new CharArrayReader(ch));
- // Check exception thrown when the reader is open.
- assertThrows(NullPointerException.class, () -> reader.read(null, 1, 0));
-
- // Now check IOException is thrown in preference to
- // NullPointerexception when the reader is closed.
- reader.close();
- assertThrows(IOException.class, () -> reader.read(null, 1, 0));
-
- // And check that the IOException is thrown before
- // ArrayIndexOutOfBoundException
- assertThrows(IOException.class, () -> reader.read(ch, 0, 42));
- }
-
/**
* Tests {@link UnsynchronizedBufferedReader#read(char[], int, int)}.
*
From 08573a672d6b24f6340e7111324a85d15c1542ea Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Thu, 16 Oct 2025 16:02:39 +0200
Subject: [PATCH 023/174] Fixes issues in `CloseShieldChannel` (#799)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Fixes issues in `CloseShieldChannel`
Two bugs in the `CloseShieldChannel` helper make it unreliable in practice:
1. **Type-erasure bug in `T wrap(T)`**
The method signature only works correctly when `T` is an **interface** extending `Channel`.
Since Java’s type system doesn’t allow constraining `T` to “interface types only,” this could lead to unexpected runtime `ClassCastException`s even though the code compiles successfully.
2. **Incomplete interface discovery**
The implementation only inspected interfaces **directly** implemented by the given channel’s class, ignoring those inherited from its superclasses.
As a result, proxies for types such as `FileChannel` did not expose any of the interfaces declared on `FileChannel` itself.
#### Fixes
This PR addresses both issues:
* **Reworks the API signature**
* Replaces `T wrap(T)` with its erasure: `Channel wrap(Channel)`.
* Introduces a new overload: `T wrap(T, Class)`, which allows callers to explicitly specify the interface type they expect.
This version fails fast with a clear `IllegalArgumentException` if the provided type is not an interface, instead of allowing a `ClassCastException` later.
* **Improves interface collection logic**
* Updates the implementation to include interfaces declared on superclasses, ensuring all relevant `Channel` interfaces are correctly proxied.
* Fixes interface discovery in `CloseShieldChannel`
This PR is split from #799.
The `CloseShieldChannel` implementation only inspects interfaces **directly** implemented by the given channel’s class, ignoring those inherited from its superclasses.
As a result, proxies for types such as `FileChannel` does not expose any of the interfaces declared on `FileChannel` itself.
* fix: add overloads for commons channel types
* fix: add `ByteChannel` overload to resolve ambiguity
* fix: Limit interfaces to those verified.
* fix: rollback previous test
* fix: Restore generic method
---
.../io/channels/CloseShieldChannel.java | 35 ++++++++++++++++---
.../channels/CloseShieldChannelHandler.java | 31 ++++++++++++++++
.../io/channels/CloseShieldChannelTest.java | 5 ---
3 files changed, 61 insertions(+), 10 deletions(-)
diff --git a/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java b/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
index bde0feb25cc..ba890d8a6a7 100644
--- a/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
+++ b/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
@@ -19,7 +19,16 @@
import java.io.Closeable;
import java.lang.reflect.Proxy;
+import java.nio.channels.AsynchronousChannel;
+import java.nio.channels.ByteChannel;
import java.nio.channels.Channel;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.InterruptibleChannel;
+import java.nio.channels.NetworkChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.channels.WritableByteChannel;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
@@ -27,9 +36,23 @@
/**
* Creates a close-shielding proxy for a {@link Channel}.
*
- *
- * The returned proxy will implement all {@link Channel} sub-interfaces that the delegate implements.
- *
+ *
The returned proxy implements all {@link Channel} sub-interfaces that are both supported by this implementation and actually implemented by the given
+ * delegate.
+ *
+ *
The following interfaces are supported:
+ *
+ *
+ *
{@link AsynchronousChannel}
+ *
{@link ByteChannel}
+ *
{@link Channel}
+ *
{@link GatheringByteChannel}
+ *
{@link InterruptibleChannel}
+ *
{@link NetworkChannel}
+ *
{@link ReadableByteChannel}
+ *
{@link ScatteringByteChannel}
+ *
{@link SeekableByteChannel}
+ *
{@link WritableByteChannel}
+ *
*
* @see Channel
* @see Closeable
@@ -44,7 +67,7 @@ private static Set> collectChannelInterfaces(final Class> type, final
// Visit interfaces
while (currentType != null) {
for (final Class> iface : currentType.getInterfaces()) {
- if (Channel.class.isAssignableFrom(iface) && out.add(iface)) {
+ if (CloseShieldChannelHandler.isSupported(iface) && out.add(iface)) {
collectChannelInterfaces(iface, out);
}
}
@@ -57,8 +80,10 @@ private static Set> collectChannelInterfaces(final Class> type, final
* Wraps a channel to shield it from being closed.
*
* @param channel The underlying channel to shield, not {@code null}.
- * @param Any Channel type (interface or class).
+ * @param A supported channel type.
* @return A proxy that shields {@code close()} and enforces closed semantics on other calls.
+ * @throws ClassCastException if {@code T} is not a supported channel type.
+ * @throws NullPointerException if {@code channel} is {@code null}.
*/
@SuppressWarnings({ "unchecked", "resource" }) // caller closes
public static T wrap(final T channel) {
diff --git a/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java b/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java
index f13b101c197..a7f3d3694f1 100644
--- a/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java
+++ b/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java
@@ -21,14 +21,45 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
+import java.nio.channels.AsynchronousChannel;
+import java.nio.channels.ByteChannel;
import java.nio.channels.Channel;
import java.nio.channels.ClosedChannelException;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.InterruptibleChannel;
import java.nio.channels.NetworkChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.ScatteringByteChannel;
import java.nio.channels.SeekableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.Objects;
+import java.util.Set;
final class CloseShieldChannelHandler implements InvocationHandler {
+ private static final Set> SUPPORTED_INTERFACES;
+
+ static {
+ final Set> interfaces = new HashSet<>();
+ interfaces.add(AsynchronousChannel.class);
+ interfaces.add(ByteChannel.class);
+ interfaces.add(Channel.class);
+ interfaces.add(GatheringByteChannel.class);
+ interfaces.add(InterruptibleChannel.class);
+ interfaces.add(NetworkChannel.class);
+ interfaces.add(ReadableByteChannel.class);
+ interfaces.add(ScatteringByteChannel.class);
+ interfaces.add(SeekableByteChannel.class);
+ interfaces.add(WritableByteChannel.class);
+ SUPPORTED_INTERFACES = Collections.unmodifiableSet(interfaces);
+ }
+
+ static boolean isSupported(final Class> interfaceClass) {
+ return SUPPORTED_INTERFACES.contains(interfaceClass);
+ }
+
/**
* Tests whether the given method is allowed to be called after the shield is closed.
*
diff --git a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
index 47d04df95d6..1f448f42cc2 100644
--- a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
+++ b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
@@ -34,7 +34,6 @@
import static org.mockito.Mockito.when;
import java.io.IOException;
-import java.nio.channels.AsynchronousByteChannel;
import java.nio.channels.AsynchronousChannel;
import java.nio.channels.ByteChannel;
import java.nio.channels.Channel;
@@ -42,7 +41,6 @@
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.InterruptibleChannel;
-import java.nio.channels.MulticastChannel;
import java.nio.channels.NetworkChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.ScatteringByteChannel;
@@ -65,14 +63,11 @@ class CloseShieldChannelTest {
static Stream> testedInterfaces() {
// @formatter:off
return Stream.of(
- AsynchronousByteChannel.class,
AsynchronousChannel.class,
ByteChannel.class,
Channel.class,
GatheringByteChannel.class,
InterruptibleChannel.class,
- MulticastChannel.class,
- NetworkChannel.class,
NetworkChannel.class,
ReadableByteChannel.class,
ScatteringByteChannel.class,
From 13c52f99be0cfc604080723ac96e2ad10b9bcf36 Mon Sep 17 00:00:00 2001
From: "Gary D. Gregory"
Date: Thu, 16 Oct 2025 10:11:34 -0400
Subject: [PATCH 024/174] More precise generics for
CloseShieldChannelHandler.SUPPORTED_INTERFACES
---
.../apache/commons/io/channels/CloseShieldChannelHandler.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java b/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java
index a7f3d3694f1..919b86d7ceb 100644
--- a/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java
+++ b/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java
@@ -39,10 +39,10 @@
final class CloseShieldChannelHandler implements InvocationHandler {
- private static final Set> SUPPORTED_INTERFACES;
+ private static final Set> SUPPORTED_INTERFACES;
static {
- final Set> interfaces = new HashSet<>();
+ final Set> interfaces = new HashSet<>();
interfaces.add(AsynchronousChannel.class);
interfaces.add(ByteChannel.class);
interfaces.add(Channel.class);
From c28dc1bdc28cc3a47ca3a897ce31cac1fc9592c2 Mon Sep 17 00:00:00 2001
From: "Gary D. Gregory"
Date: Thu, 16 Oct 2025 10:25:58 -0400
Subject: [PATCH 025/174] Minor refactoring in CloseShieldChannelTest
---
.../io/channels/CloseShieldChannelTest.java | 45 ++++++++++---------
1 file changed, 25 insertions(+), 20 deletions(-)
diff --git a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
index 1f448f42cc2..b21a5b8153b 100644
--- a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
+++ b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
@@ -47,9 +47,11 @@
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Path;
+import java.util.List;
import java.util.stream.Stream;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.ClassUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
@@ -60,7 +62,10 @@
*/
class CloseShieldChannelTest {
- static Stream> testedInterfaces() {
+ /**
+ * JRE {@link Channel} interfaces.
+ */
+ static Stream> channelInterfaces() {
// @formatter:off
return Stream.of(
AsynchronousChannel.class,
@@ -76,8 +81,15 @@ static Stream> testedInterfaces() {
// @formatter:on
}
+ /**
+ * Gets all interfaces implemented by the class {@link FileChannel}.
+ */
+ static List> fileChannelInterfaces() {
+ return ClassUtils.getAllInterfaces(FileChannel.class);
+ }
+
@ParameterizedTest
- @MethodSource("testedInterfaces")
+ @MethodSource("channelInterfaces")
void testCloseDoesNotCloseDelegate(final Class extends Channel> channelClass) throws Exception {
final Channel channel = mock(channelClass);
final Channel shield = CloseShieldChannel.wrap(channel);
@@ -86,7 +98,7 @@ void testCloseDoesNotCloseDelegate(final Class extends Channel> channelClass)
}
@ParameterizedTest
- @MethodSource("testedInterfaces")
+ @MethodSource("channelInterfaces")
void testCloseIsIdempotent(final Class extends Channel> channelClass) throws Exception {
final Channel channel = mock(channelClass);
final Channel shield = CloseShieldChannel.wrap(channel);
@@ -98,9 +110,9 @@ void testCloseIsIdempotent(final Class extends Channel> channelClass) throws E
}
@ParameterizedTest
- @MethodSource("testedInterfaces")
- void testCloseIsShielded(final Class extends Channel> channelClass) throws Exception {
- final Channel channel = mock(channelClass);
+ @MethodSource("channelInterfaces")
+ void testCloseIsShielded(final Class extends Channel> channelInterface) throws Exception {
+ final Channel channel = mock(channelInterface);
when(channel.isOpen()).thenReturn(true, false, true, false);
final Channel shield = CloseShieldChannel.wrap(channel);
// Reflects delegate state initially
@@ -115,19 +127,12 @@ void testCloseIsShielded(final Class extends Channel> channelClass) throws Exc
}
@Test
- void testCorrectlyDetectsInterfaces(@TempDir Path tempDir) throws IOException {
+ void testWrapFileChannel(final @TempDir Path tempDir) throws IOException {
final Path testFile = tempDir.resolve("test.txt");
FileUtils.touch(testFile.toFile());
try (FileChannel channel = FileChannel.open(testFile); Channel shield = CloseShieldChannel.wrap(channel)) {
- assertInstanceOf(SeekableByteChannel.class, shield);
- assertInstanceOf(GatheringByteChannel.class, shield);
- assertInstanceOf(WritableByteChannel.class, shield);
- assertInstanceOf(ScatteringByteChannel.class, shield);
- assertInstanceOf(ReadableByteChannel.class, shield);
- assertInstanceOf(InterruptibleChannel.class, shield);
- assertInstanceOf(ByteChannel.class, shield);
- assertInstanceOf(Channel.class, shield);
- // These are not interfaces, so can not be implemented
+ fileChannelInterfaces().forEach(iface -> assertInstanceOf(iface, shield));
+ // FileChannel is not an interface, so can not be implemented.
assertFalse(shield instanceof FileChannel, "not FileChannel");
}
}
@@ -141,7 +146,7 @@ void testDoesNotDoubleWrap() {
}
@ParameterizedTest
- @MethodSource("testedInterfaces")
+ @MethodSource("channelInterfaces")
void testEquals(final Class extends Channel> channelClass) throws Exception {
final Channel channel = mock(channelClass);
final Channel shield = CloseShieldChannel.wrap(channel);
@@ -168,7 +173,7 @@ void testGatheringByteChannelMethods() throws Exception {
}
@ParameterizedTest
- @MethodSource("testedInterfaces")
+ @MethodSource("channelInterfaces")
void testHashCode(final Class extends Channel> channelClass) throws Exception {
final Channel channel = mock(channelClass);
final Channel shield = CloseShieldChannel.wrap(channel);
@@ -208,7 +213,7 @@ void testNetworkChannelMethods() throws Exception {
}
@ParameterizedTest
- @MethodSource("testedInterfaces")
+ @MethodSource("channelInterfaces")
void testPreservesInterfaces(final Class extends Channel> channelClass) {
final Channel channel = mock(channelClass);
final Channel shield = CloseShieldChannel.wrap(channel);
@@ -275,7 +280,7 @@ void testSeekableByteChannelMethods() throws Exception {
}
@ParameterizedTest
- @MethodSource("testedInterfaces")
+ @MethodSource("channelInterfaces")
void testToString(final Class extends Channel> channelClass) throws Exception {
final Channel channel = mock(channelClass);
when(channel.toString()).thenReturn("MyChannel");
From 5a5c6aee83347bd90cc60a5bfe4645f1d9e7f28f Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 16 Oct 2025 20:30:46 -0400
Subject: [PATCH 026/174] Bump actions/dependency-review-action from 4.8.0 to
4.8.1 (#802)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.8.0 to 4.8.1.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](https://github.com/actions/dependency-review-action/compare/56339e523c0409420f6c2c9a2f4292bbb3c07dd3...40c09b7dc99638e5ddb0bfd91c1673effc064d8a)
---
updated-dependencies:
- dependency-name: actions/dependency-review-action
dependency-version: 4.8.1
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/dependency-review.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index edfe8756642..a657a4ae2cd 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -28,4 +28,4 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: 'Dependency Review PR'
- uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3 # v4.8.0
+ uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1
From 3c5166e34e19efe35b522badd08e5e7960d9c49e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 16 Oct 2025 20:31:36 -0400
Subject: [PATCH 027/174] Bump github/codeql-action from 4.30.7 to 4.30.8
(#803)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.30.7 to 4.30.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/e296a935590eb16afc0c0108289f68c87e2a89a5...f443b600d91635bebf5b0d9ebc620189c0d6fba5)
---
updated-dependencies:
- dependency-name: github/codeql-action
dependency-version: 4.30.8
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/codeql-analysis.yml | 6 +++---
.github/workflows/scorecards-analysis.yml | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 4d91dcc9f39..ea037c8f839 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -62,7 +62,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@e296a935590eb16afc0c0108289f68c87e2a89a5 # 3.29.5
+ uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # 3.29.5
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -73,7 +73,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@e296a935590eb16afc0c0108289f68c87e2a89a5 # 3.29.5
+ uses: github/codeql-action/autobuild@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # 3.29.5
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -87,4 +87,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@e296a935590eb16afc0c0108289f68c87e2a89a5 # 3.29.5
+ uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # 3.29.5
diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml
index 0de672c8692..77678628b08 100644
--- a/.github/workflows/scorecards-analysis.yml
+++ b/.github/workflows/scorecards-analysis.yml
@@ -66,6 +66,6 @@ jobs:
retention-days: 5
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@e296a935590eb16afc0c0108289f68c87e2a89a5 # 3.29.5
+ uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # 3.29.5
with:
sarif_file: results.sarif
From f76ee129d348f04c540d205856853e587ae22766 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Fri, 17 Oct 2025 15:23:57 +0200
Subject: [PATCH 028/174] Fix concurrency issue in `IOUtils.skip` (#801)
* Fix concurrency issue in `IOUtils.skip`
This patch addresses a concurrency problem in `IOUtils.skip`, as reported in [COMPRESS-666](https://issues.apache.org/jira/browse/COMPRESS-666) and [COMPRESS-697](https://issues.apache.org/jira/browse/COMPRESS-697).
Previously, `IOUtils.skip` relied on `InputStream#read` to skip bytes, using a buffer shared across **all** threads. Although `IOUtils.skip` itself does not consume the data read, certain `InputStream` implementations (e.g. `ChecksumInputStream`) may process that data internally.
In concurrent scenarios, this shared buffer could be overwritten by another thread between the `read` and the subsequent internal processing (such as checksum calculation), leading to incorrect behavior.
This change reverts commit c12eaff7f747353a7a9a97df735fd3301f42e313 and restores the use of a **per-thread buffer** in `IOUtils.skip`, ensuring thread safety and correct behavior in concurrent environments.
* Adds a reentrancy guard to the thread-local pool
* Apply suggestion from @Copilot (1)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Apply suggestions from @Copilot (2)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/changes/changes.xml | 1 +
.../java/org/apache/commons/io/CopyUtils.java | 18 +-
.../java/org/apache/commons/io/IOUtils.java | 273 ++++++++++--------
.../org/apache/commons/io/IOCaseTest.java | 65 +++--
.../commons/io/IOUtilsConcurrentTest.java | 212 ++++++++++++++
5 files changed, 419 insertions(+), 150 deletions(-)
create mode 100644 src/test/java/org/apache/commons/io/IOUtilsConcurrentTest.java
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ecee3aeca01..3aa41940132 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -56,6 +56,7 @@ The type attribute can be add,update,fix,remove.
IOUtils.toByteArray(InputStream) now throws IOException on byte array overflow.Javadoc general improvements.IOUtils.toByteArray() now throws EOFException when not enough data is available #796.
+ Fix IOUtils.skip() usage in concurrent scenarios.FileUtils#byteCountToDisplaySize() supports Zettabyte, Yottabyte, Ronnabyte and Quettabyte #763.Add org.apache.commons.io.FileUtils.ONE_RB #763.
diff --git a/src/main/java/org/apache/commons/io/CopyUtils.java b/src/main/java/org/apache/commons/io/CopyUtils.java
index e22adef83be..fdb137d2039 100644
--- a/src/main/java/org/apache/commons/io/CopyUtils.java
+++ b/src/main/java/org/apache/commons/io/CopyUtils.java
@@ -278,14 +278,18 @@ public static int copy(
final Reader input,
final Writer output)
throws IOException {
- final char[] buffer = IOUtils.getScratchCharArray();
- int count = 0;
- int n;
- while (EOF != (n = input.read(buffer))) {
- output.write(buffer, 0, n);
- count += n;
+ final char[] buffer = IOUtils.ScratchBufferHolder.getScratchCharArray();
+ try {
+ int count = 0;
+ int n;
+ while (EOF != (n = input.read(buffer))) {
+ output.write(buffer, 0, n);
+ count += n;
+ }
+ return count;
+ } finally {
+ IOUtils.ScratchBufferHolder.releaseScratchCharArray(buffer);
}
- return count;
}
/**
diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java
index a1f9b0709a2..717be4b49bf 100644
--- a/src/main/java/org/apache/commons/io/IOUtils.java
+++ b/src/main/java/org/apache/commons/io/IOUtils.java
@@ -132,6 +132,94 @@ public class IOUtils {
// Writer. Each method should take at least one of these as a parameter,
// or return one of them.
+ /**
+ * Holder for per-thread internal scratch buffers.
+ *
+ *
Buffers are created lazily and reused within the same thread to reduce allocation overhead. In the rare case of reentrant access, a temporary buffer
+ * is allocated to avoid data corruption.