From bb3226a54ba0eb9d261589e989f0c1e422bf65dc Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sun, 28 Sep 2025 15:51:54 +0200 Subject: [PATCH 01/25] Backport `Objects.checkFromIndexSize` to `IOUtils` Java 9 introduced `Objects.checkFromIndexSize` for validating the offset/length ranges used in `read` and `write` operations. This PR adds an equivalent method, `IOUtils.checkFromIndexSize`, to provide the same validation in Commons IO. It simplifies range checks in stream implementations and makes the utility available to projects that still target Java 8. --- .../java/org/apache/commons/io/IOUtils.java | 30 +++++++++++ .../org/apache/commons/io/IOUtilsTest.java | 52 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index e37eddafb3f..eb611f80584 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -406,6 +406,36 @@ private static char[] charArray(final int size) { return new char[size]; } + /** + * Checks if the sub-range described by an offset and length is valid for an array of the given length. + * + *

This method is functionally equivalent to + * {@code java.util.Objects#checkFromIndexSize(int, int, int)} introduced in Java 9, + * but is provided here for use on Java 8.

+ * + *

The range is valid if all of the following hold:

+ * + * + *

If the range is invalid, this method throws an + * {@link IndexOutOfBoundsException} with a descriptive message.

+ * + * @param off the starting offset into the array + * @param len the number of elements in the range + * @param arrayLength the length of the array to be checked against + * @throws IndexOutOfBoundsException if the range {@code [off, off + len)} is out of bounds + * @since 2.21.0 + */ + public 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, % diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java index bae8f59e374..d062ae61ea8 100644 --- a/src/test/java/org/apache/commons/io/IOUtilsTest.java +++ b/src/test/java/org/apache/commons/io/IOUtilsTest.java @@ -50,6 +50,7 @@ import java.io.SequenceInputStream; import java.io.StringReader; import java.io.Writer; +import java.lang.reflect.InvocationTargetException; import java.net.ServerSocket; import java.net.Socket; import java.net.URI; @@ -65,6 +66,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Supplier; @@ -85,7 +87,9 @@ import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; import org.apache.commons.io.test.TestUtils; import org.apache.commons.io.test.ThrowOnCloseReader; +import org.apache.commons.lang3.JavaVersion; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -348,6 +352,54 @@ 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) { + final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.checkFromIndexSize(off, len, arrayLength)); + assertTrue(ex.getMessage().contains(String.valueOf(off))); + assertTrue(ex.getMessage().contains(String.valueOf(len))); + assertTrue(ex.getMessage().contains(String.valueOf(arrayLength))); + // Optional requirement: compare the exception message for Java 8 and Java 9+ + if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_9)) { + final IndexOutOfBoundsException jreEx = assertThrows(IndexOutOfBoundsException.class, () -> { + try { + Objects.class.getDeclaredMethod("checkFromIndexSize", int.class, int.class, int.class).invoke(null, off, len, arrayLength); + } catch (InvocationTargetException ite) { + throw ite.getTargetException(); + } + }); + assertEquals(jreEx.getMessage(), ex.getMessage()); + } + } + @Test void testClose() { assertDoesNotThrow(() -> IOUtils.close((Closeable) null)); From 659b90bdccb1e5a14ad59c87ab9141b7a70f894e Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sun, 28 Sep 2025 15:56:19 +0200 Subject: [PATCH 02/25] Add changelog entry --- src/changes/changes.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0190734a841..7c2b0971b29 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -68,6 +68,7 @@ The type attribute can be add,update,fix,remove. Add IOIterable.asIterable(). Add NIO channel support to `AbstractStreamBuilder`. 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. [test] Bump commons-codec:commons-codec from 1.18.0 to 1.19.0. From 5552d9b1495b9315210e3b7ab9320bc92f89cb5c Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sun, 28 Sep 2025 20:37:14 +0200 Subject: [PATCH 03/25] fix: apply `IOUtils.checkIndexFromSize` to all streams --- .../java/org/apache/commons/io/IOUtils.java | 125 ++++++++++++++---- .../commons/io/input/BOMInputStream.java | 4 + .../commons/io/input/BoundedReader.java | 3 + .../apache/commons/io/input/BrokenReader.java | 5 + .../input/BufferedFileChannelInputStream.java | 5 +- .../io/input/CharSequenceInputStream.java | 6 +- .../commons/io/input/CharSequenceReader.java | 12 +- .../commons/io/input/ClosedInputStream.java | 4 + .../apache/commons/io/input/ClosedReader.java | 4 + .../io/input/MemoryMappedFileInputStream.java | 5 + .../commons/io/input/NullInputStream.java | 5 +- .../apache/commons/io/input/NullReader.java | 6 + .../commons/io/input/QueueInputStream.java | 9 +- .../io/input/ReadAheadInputStream.java | 5 +- .../commons/io/input/ReaderInputStream.java | 8 +- .../commons/io/input/SequenceReader.java | 7 +- .../UnsynchronizedBufferedInputStream.java | 11 +- .../input/UnsynchronizedBufferedReader.java | 8 +- .../UnsynchronizedByteArrayInputStream.java | 7 +- .../buffer/CircularBufferInputStream.java | 9 +- .../commons/io/output/AppendableWriter.java | 7 +- .../commons/io/output/BrokenWriter.java | 2 + .../io/output/ByteArrayOutputStream.java | 10 +- .../io/output/ChunkedOutputStream.java | 1 + .../commons/io/output/ChunkedWriter.java | 1 + .../commons/io/output/ClosedOutputStream.java | 3 + .../commons/io/output/ClosedWriter.java | 3 + .../io/output/FilterCollectionWriter.java | 3 + .../commons/io/output/NullAppendable.java | 3 + .../commons/io/output/NullOutputStream.java | 4 +- .../apache/commons/io/output/NullWriter.java | 6 +- .../io/output/StringBuilderWriter.java | 7 +- .../io/output/ThresholdingOutputStream.java | 2 + .../UnsynchronizedByteArrayOutputStream.java | 5 +- .../commons/io/output/WriterOutputStream.java | 1 + .../commons/io/output/XmlStreamWriter.java | 1 + .../org/apache/commons/io/IOUtilsTest.java | 50 +++++++ 37 files changed, 262 insertions(+), 95 deletions(-) diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index eb611f80584..0d671eac9ee 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -17,6 +17,8 @@ package org.apache.commons.io; +import static java.util.Objects.requireNonNull; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -244,7 +246,7 @@ public class IOUtils { public static BufferedInputStream buffer(final InputStream inputStream) { // reject null early on rather than waiting for IO operation to fail // not checked by BufferedInputStream - Objects.requireNonNull(inputStream, "inputStream"); + requireNonNull(inputStream, "inputStream"); return inputStream instanceof BufferedInputStream ? (BufferedInputStream) inputStream : new BufferedInputStream(inputStream); } @@ -263,7 +265,7 @@ public static BufferedInputStream buffer(final InputStream inputStream) { public static BufferedInputStream buffer(final InputStream inputStream, final int size) { // reject null early on rather than waiting for IO operation to fail // not checked by BufferedInputStream - Objects.requireNonNull(inputStream, "inputStream"); + requireNonNull(inputStream, "inputStream"); return inputStream instanceof BufferedInputStream ? (BufferedInputStream) inputStream : new BufferedInputStream(inputStream, size); } @@ -281,7 +283,7 @@ public static BufferedInputStream buffer(final InputStream inputStream, final in public static BufferedOutputStream buffer(final OutputStream outputStream) { // reject null early on rather than waiting for IO operation to fail // not checked by BufferedInputStream - Objects.requireNonNull(outputStream, "outputStream"); + requireNonNull(outputStream, "outputStream"); return outputStream instanceof BufferedOutputStream ? (BufferedOutputStream) outputStream : new BufferedOutputStream(outputStream); } @@ -300,7 +302,7 @@ public static BufferedOutputStream buffer(final OutputStream outputStream) { public static BufferedOutputStream buffer(final OutputStream outputStream, final int size) { // reject null early on rather than waiting for IO operation to fail // not checked by BufferedInputStream - Objects.requireNonNull(outputStream, "outputStream"); + requireNonNull(outputStream, "outputStream"); return outputStream instanceof BufferedOutputStream ? (BufferedOutputStream) outputStream : new BufferedOutputStream(outputStream, size); } @@ -407,35 +409,112 @@ private static char[] charArray(final int size) { } /** - * Checks if the sub-range described by an offset and length is valid for an array of the given length. + * Checks if the sub-range described by an offset and length is valid for the given array. + * + *

The range is valid if all of the following hold:

+ *
    + *
  • {@code off >= 0}
  • + *
  • {@code len >= 0}
  • + *
  • {@code off + len <= array.length}
  • + *
+ * + *

If the range is invalid, this method throws an + * {@link IndexOutOfBoundsException} with a descriptive message.

+ * + * @param array The array to be checked against + * @param off The starting offset into the array + * @param len The number of elements in the range + * @throws NullPointerException If the array is null + * @throws IndexOutOfBoundsException If the range {@code [off, off + len)} is out of bounds + * @since 2.21.0 + */ + public static void checkFromIndexSize(final byte[] array, final int off, final int len) { + checkFromIndexSize(off, len, requireNonNull(array, "byte array").length); + } + + /** + * Checks if the sub-range described by an offset and length is valid for the given array. + * + *

The range is valid if all of the following hold:

+ *
    + *
  • {@code off >= 0}
  • + *
  • {@code len >= 0}
  • + *
  • {@code off + len <= array.length}
  • + *
+ * + *

If the range is invalid, this method throws an + * {@link IndexOutOfBoundsException} with a descriptive message.

* - *

This method is functionally equivalent to - * {@code java.util.Objects#checkFromIndexSize(int, int, int)} introduced in Java 9, - * but is provided here for use on Java 8.

+ * @param array The array to be checked against + * @param off The starting offset into the array + * @param len The number of elements in the range + * @throws NullPointerException If the array is null + * @throws IndexOutOfBoundsException If the range {@code [off, off + len)} is out of bounds + * @since 2.21.0 + */ + public static void checkFromIndexSize(final char[] array, final int off, final int len) { + checkFromIndexSize(off, len, requireNonNull(array, "char array").length); + } + + /** + * Checks if the sub-range described by an offset and length is valid for the given string. * *

The range is valid if all of the following hold:

*
    *
  • {@code off >= 0}
  • *
  • {@code len >= 0}
  • - *
  • {@code arrayLength >= 0}
  • - *
  • {@code off + len <= arrayLength}
  • + *
  • {@code off + len <= array.length}
  • *
* *

If the range is invalid, this method throws an * {@link IndexOutOfBoundsException} with a descriptive message.

* - * @param off the starting offset into the array - * @param len the number of elements in the range - * @param arrayLength the length of the array to be checked against - * @throws IndexOutOfBoundsException if the range {@code [off, off + len)} is out of bounds + * @param str The char sequence to be checked against + * @param off The starting offset into the array + * @param len The number of elements in the range + * @throws NullPointerException If the array is null + * @throws IndexOutOfBoundsException If the range {@code [off, off + len)} is out of bounds * @since 2.21.0 */ - public static void checkFromIndexSize(final int off, final int len, final int arrayLength) { + public static void checkFromIndexSize(final String str, final int off, final int len) { + checkFromIndexSize(off, len, requireNonNull(str, "str").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, %The range is valid if all of the following hold:

+ *
    + *
  • {@code fromIndex >= 0}
  • + *
  • {@code fromIndex <= toIndex}
  • + *
  • {@code toIndex <= seq.length()}
  • + *
+ * + *

If the range is invalid, this method throws an {@link IndexOutOfBoundsException} with a descriptive message.

+ * + * @param seq The char sequence to be checked against + * @param fromIndex The starting index into the char sequence (inclusive) + * @param toIndex The ending index into the char sequence (exclusive) + * @throws NullPointerException If the char sequence is null + * @throws IndexOutOfBoundsException If the range {@code [fromIndex, toIndex)} is out of bounds + * @since 2.21.0 + */ + public static void checkFromToIndex(final CharSequence seq, int fromIndex, final int toIndex) { + checkFromToIndex(fromIndex, toIndex, requireNonNull(seq, "char sequence").length()); + } + + static void checkFromToIndex(final int fromIndex, final int toIndex, final int length) { + if (fromIndex < 0 | toIndex < fromIndex | length < toIndex) { + throw new IndexOutOfBoundsException(String.format("Range [%s, %s) out of bounds for length %s", fromIndex, toIndex, length)); + } + } + /** * Clears any state. *
    @@ -1217,7 +1296,7 @@ public static void copy(final InputStream input, final Writer writer, final Stri */ @SuppressWarnings("resource") // streams are closed by the caller. public static QueueInputStream copy(final java.io.ByteArrayOutputStream outputStream) throws IOException { - Objects.requireNonNull(outputStream, "outputStream"); + requireNonNull(outputStream, "outputStream"); final QueueInputStream in = new QueueInputStream(); outputStream.writeTo(in.newQueueOutputStream()); return in; @@ -1398,7 +1477,7 @@ public static int copy(final Reader reader, final Writer writer) throws IOExcept * @since 2.9.0 */ public static long copy(final URL url, final File file) throws IOException { - try (OutputStream outputStream = Files.newOutputStream(Objects.requireNonNull(file, "file").toPath())) { + try (OutputStream outputStream = Files.newOutputStream(requireNonNull(file, "file").toPath())) { return copy(url, outputStream); } } @@ -1421,7 +1500,7 @@ public static long copy(final URL url, final File file) throws IOException { * @since 2.9.0 */ public static long copy(final URL url, final OutputStream outputStream) throws IOException { - try (InputStream inputStream = Objects.requireNonNull(url, "url").openStream()) { + try (InputStream inputStream = requireNonNull(url, "url").openStream()) { return copyLarge(inputStream, outputStream); } } @@ -1470,8 +1549,8 @@ public static long copyLarge(final InputStream inputStream, final OutputStream o @SuppressWarnings("resource") // streams are closed by the caller. public static long copyLarge(final InputStream inputStream, final OutputStream outputStream, final byte[] buffer) throws IOException { - Objects.requireNonNull(inputStream, "inputStream"); - Objects.requireNonNull(outputStream, "outputStream"); + requireNonNull(inputStream, "inputStream"); + requireNonNull(outputStream, "outputStream"); long count = 0; int n; while (EOF != (n = inputStream.read(buffer))) { @@ -2739,7 +2818,7 @@ public static byte[] toByteArray(final InputStream inputStream) throws IOExcepti * @since 2.1 */ public static byte[] toByteArray(final InputStream input, final int size) throws IOException { - return toByteArray(Objects.requireNonNull(input, "input")::read, size); + return toByteArray(requireNonNull(input, "input")::read, size); } /** @@ -2765,7 +2844,7 @@ public static byte[] toByteArray(final InputStream input, final int size) throws * @since 2.21.0 */ public static byte[] toByteArray(final InputStream input, final int size, final int chunkSize) throws IOException { - Objects.requireNonNull(input, "input"); + requireNonNull(input, "input"); if (chunkSize <= 0) { throw new IllegalArgumentException("Chunk size must be greater than zero: " + chunkSize); } @@ -3913,7 +3992,7 @@ public static void writeLines(final Collection lines, String lineEnding, fina * @since 2.7 */ public static Writer writer(final Appendable appendable) { - Objects.requireNonNull(appendable, "appendable"); + requireNonNull(appendable, "appendable"); if (appendable instanceof Writer) { return (Writer) appendable; } diff --git a/src/main/java/org/apache/commons/io/input/BOMInputStream.java b/src/main/java/org/apache/commons/io/input/BOMInputStream.java index c5872d7cbc9..4cb347f1dc5 100644 --- a/src/main/java/org/apache/commons/io/input/BOMInputStream.java +++ b/src/main/java/org/apache/commons/io/input/BOMInputStream.java @@ -444,6 +444,10 @@ public int read(final byte[] buf) throws IOException { */ @Override public int read(final byte[] buf, int off, int len) throws IOException { + IOUtils.checkFromIndexSize(buf, off, len); + if (len == 0) { + return 0; + } int firstCount = 0; int b = 0; while (len > 0 && b >= 0) { diff --git a/src/main/java/org/apache/commons/io/input/BoundedReader.java b/src/main/java/org/apache/commons/io/input/BoundedReader.java index 3cd0cee6259..c64759045f2 100644 --- a/src/main/java/org/apache/commons/io/input/BoundedReader.java +++ b/src/main/java/org/apache/commons/io/input/BoundedReader.java @@ -23,6 +23,8 @@ import java.io.IOException; import java.io.Reader; +import org.apache.commons.io.IOUtils; + /** * A reader that imposes a limit to the number of characters that can be read from an underlying reader, returning EOF * when this limit is reached, regardless of state of underlying reader. @@ -121,6 +123,7 @@ public int read() throws IOException { */ @Override public int read(final char[] cbuf, final int off, final int len) throws IOException { + IOUtils.checkFromIndexSize(cbuf, off, len); int c; for (int i = 0; i < len; i++) { c = read(); diff --git a/src/main/java/org/apache/commons/io/input/BrokenReader.java b/src/main/java/org/apache/commons/io/input/BrokenReader.java index 8fb510569cf..61e9bff47ba 100644 --- a/src/main/java/org/apache/commons/io/input/BrokenReader.java +++ b/src/main/java/org/apache/commons/io/input/BrokenReader.java @@ -20,6 +20,7 @@ import java.io.Reader; import java.util.function.Supplier; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.function.Erase; /** @@ -114,6 +115,10 @@ public void mark(final int readAheadLimit) throws IOException { */ @Override public int read(final char[] cbuf, final int off, final int len) throws IOException { + IOUtils.checkFromIndexSize(cbuf, off, len); + if (len == 0) { + return 0; + } throw rethrow(); } diff --git a/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java b/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java index bc5a9533038..8e81b34508a 100644 --- a/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java +++ b/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java @@ -254,8 +254,9 @@ public synchronized int read() throws IOException { @Override public synchronized int read(final byte[] b, final int offset, int len) throws IOException { - if (offset < 0 || len < 0 || offset + len < 0 || offset + len > b.length) { - throw new IndexOutOfBoundsException(); + IOUtils.checkFromIndexSize(b, offset, len); + if (len == 0) { + return 0; } if (!refill()) { return EOF; diff --git a/src/main/java/org/apache/commons/io/input/CharSequenceInputStream.java b/src/main/java/org/apache/commons/io/input/CharSequenceInputStream.java index 8666105491a..874dcf1cb39 100644 --- a/src/main/java/org/apache/commons/io/input/CharSequenceInputStream.java +++ b/src/main/java/org/apache/commons/io/input/CharSequenceInputStream.java @@ -28,7 +28,6 @@ import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; -import java.util.Objects; import org.apache.commons.io.Charsets; import org.apache.commons.io.IOUtils; @@ -319,10 +318,7 @@ public int read(final byte[] b) throws IOException { @Override public int read(final byte[] array, int off, int len) throws IOException { - Objects.requireNonNull(array, "array"); - if (len < 0 || off + len > array.length) { - throw new IndexOutOfBoundsException("Array Size=" + array.length + ", offset=" + off + ", length=" + len); - } + IOUtils.checkFromIndexSize(array, off, len); if (len == 0) { return 0; // must return 0 for zero length read } diff --git a/src/main/java/org/apache/commons/io/input/CharSequenceReader.java b/src/main/java/org/apache/commons/io/input/CharSequenceReader.java index 8fee366b2da..bc3defe7ddc 100644 --- a/src/main/java/org/apache/commons/io/input/CharSequenceReader.java +++ b/src/main/java/org/apache/commons/io/input/CharSequenceReader.java @@ -20,7 +20,8 @@ import java.io.Reader; import java.io.Serializable; -import java.util.Objects; + +import org.apache.commons.io.IOUtils; /** * {@link Reader} implementation that can read from String, StringBuffer, @@ -209,14 +210,13 @@ public int read() { */ @Override public int read(final char[] array, final int offset, final int length) { + IOUtils.checkFromIndexSize(array, offset, length); + if (length == 0) { + return 0; + } if (idx >= end()) { return EOF; } - Objects.requireNonNull(array, "array"); - if (length < 0 || offset < 0 || offset + length > array.length) { - throw new IndexOutOfBoundsException("Array Size=" + array.length + - ", offset=" + offset + ", length=" + length); - } if (charSequence instanceof String) { final int count = Math.min(length, end() - idx); diff --git a/src/main/java/org/apache/commons/io/input/ClosedInputStream.java b/src/main/java/org/apache/commons/io/input/ClosedInputStream.java index ceab0176090..5ddb9ea6c9d 100644 --- a/src/main/java/org/apache/commons/io/input/ClosedInputStream.java +++ b/src/main/java/org/apache/commons/io/input/ClosedInputStream.java @@ -86,6 +86,10 @@ public int read() { */ @Override public int read(final byte[] b, final int off, final int len) throws IOException { + IOUtils.checkFromIndexSize(b, off, len); + if (len == 0) { + return 0; + } return EOF; } diff --git a/src/main/java/org/apache/commons/io/input/ClosedReader.java b/src/main/java/org/apache/commons/io/input/ClosedReader.java index 93c99e3c550..72220c6978d 100644 --- a/src/main/java/org/apache/commons/io/input/ClosedReader.java +++ b/src/main/java/org/apache/commons/io/input/ClosedReader.java @@ -71,6 +71,10 @@ public void close() throws IOException { */ @Override public int read(final char[] cbuf, final int off, final int len) { + IOUtils.checkFromIndexSize(cbuf, off, len); + if (len == 0) { + return 0; + } return EOF; } diff --git a/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java b/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java index 8fdd7aec8b9..22fae70ca75 100644 --- a/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java +++ b/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java @@ -27,6 +27,7 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.build.AbstractStreamBuilder; /** @@ -216,6 +217,10 @@ public int read() throws IOException { @Override public int read(final byte[] b, final int off, final int len) throws IOException { + IOUtils.checkFromIndexSize(b, off, len); + if (len == 0) { + return 0; + } checkOpen(); if (!buffer.hasRemaining()) { nextBuffer(); diff --git a/src/main/java/org/apache/commons/io/input/NullInputStream.java b/src/main/java/org/apache/commons/io/input/NullInputStream.java index 5f3497863b0..32b1206ff2f 100644 --- a/src/main/java/org/apache/commons/io/input/NullInputStream.java +++ b/src/main/java/org/apache/commons/io/input/NullInputStream.java @@ -22,6 +22,8 @@ import java.io.IOException; import java.io.InputStream; +import org.apache.commons.io.IOUtils; + /** * A lightweight {@link InputStream} that emulates a stream of a specified size. *

    @@ -292,7 +294,8 @@ public int read(final byte[] bytes) throws IOException { */ @Override public int read(final byte[] bytes, final int offset, final int length) throws IOException { - if (bytes.length == 0 || length == 0) { + IOUtils.checkFromIndexSize(bytes, offset, length); + if (length == 0) { return 0; } checkOpen(); diff --git a/src/main/java/org/apache/commons/io/input/NullReader.java b/src/main/java/org/apache/commons/io/input/NullReader.java index c5ae530a170..2b7fdb47175 100644 --- a/src/main/java/org/apache/commons/io/input/NullReader.java +++ b/src/main/java/org/apache/commons/io/input/NullReader.java @@ -22,6 +22,8 @@ import java.io.IOException; import java.io.Reader; +import org.apache.commons.io.IOUtils; + /** * A functional, lightweight {@link Reader} that emulates * a reader of a specified size. @@ -276,6 +278,10 @@ public int read(final char[] chars) throws IOException { */ @Override public int read(final char[] chars, final int offset, final int length) throws IOException { + IOUtils.checkFromIndexSize(chars, offset, length); + if (length == 0) { + return 0; + } if (eof) { throw new IOException("Read after end of file"); } diff --git a/src/main/java/org/apache/commons/io/input/QueueInputStream.java b/src/main/java/org/apache/commons/io/input/QueueInputStream.java index 1515eb94036..5f701945cb3 100644 --- a/src/main/java/org/apache/commons/io/input/QueueInputStream.java +++ b/src/main/java/org/apache/commons/io/input/QueueInputStream.java @@ -30,6 +30,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.build.AbstractStreamBuilder; import org.apache.commons.io.output.QueueOutputStream; @@ -244,13 +245,7 @@ public int read() { */ @Override public int read(final byte[] b, final int offset, final int length) { - if (b == null) { - throw new NullPointerException(); - } - if (offset < 0 || length < 0 || length > b.length - offset) { - throw new IndexOutOfBoundsException( - String.format("Range [%d, % b.length - offset) { - throw new IndexOutOfBoundsException(); - } + IOUtils.checkFromIndexSize(b, offset, len); if (len == 0) { return 0; } 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 bcd03abd565..b2b6a82befb 100644 --- a/src/main/java/org/apache/commons/io/input/ReaderInputStream.java +++ b/src/main/java/org/apache/commons/io/input/ReaderInputStream.java @@ -30,7 +30,6 @@ import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; -import java.util.Objects; import org.apache.commons.io.Charsets; import org.apache.commons.io.IOUtils; @@ -456,14 +455,11 @@ public int read(final byte[] b) throws IOException { */ @Override public int read(final byte[] array, int off, int len) throws IOException { - Objects.requireNonNull(array, "array"); - if (len < 0 || off < 0 || off + len > array.length) { - throw new IndexOutOfBoundsException("Array size=" + array.length + ", offset=" + off + ", length=" + len); - } - int read = 0; + IOUtils.checkFromIndexSize(array, off, len); if (len == 0) { return 0; // Always return 0 if len == 0 } + int read = 0; while (len > 0) { if (encoderOut.hasRemaining()) { // Data from the last read not fully copied final int c = Math.min(encoderOut.remaining(), len); diff --git a/src/main/java/org/apache/commons/io/input/SequenceReader.java b/src/main/java/org/apache/commons/io/input/SequenceReader.java index 6632917030b..b10bfcefcc9 100644 --- a/src/main/java/org/apache/commons/io/input/SequenceReader.java +++ b/src/main/java/org/apache/commons/io/input/SequenceReader.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.Objects; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.function.Uncheck; /** @@ -114,9 +115,9 @@ public int read() throws IOException { */ @Override public int read(final char[] cbuf, int off, int len) throws IOException { - Objects.requireNonNull(cbuf, "cbuf"); - if (len < 0 || off < 0 || off + len > cbuf.length) { - throw new IndexOutOfBoundsException("Array Size=" + cbuf.length + ", offset=" + off + ", length=" + len); + IOUtils.checkFromIndexSize(cbuf, off, len); + if (len == 0) { + return 0; } int count = 0; while (reader != null) { diff --git a/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStream.java b/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStream.java index 8106a065631..8d60f6cedb8 100644 --- a/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStream.java +++ b/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStream.java @@ -306,19 +306,16 @@ public int read() throws IOException { */ @Override public int read(final byte[] dest, int offset, final int length) throws IOException { + IOUtils.checkFromIndexSize(dest, offset, length); + if (length == 0) { + return 0; + } // Use local ref since buf may be invalidated by an unsynchronized // close() byte[] localBuf = buffer; if (localBuf == null) { throw new IOException("Stream is closed"); } - // avoid int overflow - if (offset > dest.length - length || offset < 0 || length < 0) { - throw new IndexOutOfBoundsException(); - } - if (length == 0) { - return 0; - } final InputStream localIn = inputStream; if (localIn == null) { throw new IOException("Stream is closed"); diff --git a/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java b/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java index 51b48c07af3..e8bf3363f05 100644 --- a/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java +++ b/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java @@ -271,10 +271,12 @@ public int read() throws IOException { */ @Override public int read(final char[] buffer, int offset, final int length) throws IOException { - checkOpen(); - if (offset < 0 || offset > buffer.length - length || length < 0) { - throw new IndexOutOfBoundsException(); + IOUtils.checkFromIndexSize(buffer, offset, length); + if (length == 0) { + return 0; } + checkOpen(); + int outstanding = length; while (outstanding > 0) { diff --git a/src/main/java/org/apache/commons/io/input/UnsynchronizedByteArrayInputStream.java b/src/main/java/org/apache/commons/io/input/UnsynchronizedByteArrayInputStream.java index cb2f5d3ddc5..a56dec7206c 100644 --- a/src/main/java/org/apache/commons/io/input/UnsynchronizedByteArrayInputStream.java +++ b/src/main/java/org/apache/commons/io/input/UnsynchronizedByteArrayInputStream.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.util.Objects; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.build.AbstractOrigin; import org.apache.commons.io.build.AbstractStreamBuilder; @@ -288,9 +289,9 @@ public int read(final byte[] dest) { @Override public int read(final byte[] dest, final int off, final int len) { - Objects.requireNonNull(dest, "dest"); - if (off < 0 || len < 0 || off + len > dest.length) { - throw new IndexOutOfBoundsException(); + IOUtils.checkFromIndexSize(dest, off, len); + if (len == 0) { + return 0; } if (offset >= eod) { diff --git a/src/main/java/org/apache/commons/io/input/buffer/CircularBufferInputStream.java b/src/main/java/org/apache/commons/io/input/buffer/CircularBufferInputStream.java index 8be305ddd04..b92163478d0 100644 --- a/src/main/java/org/apache/commons/io/input/buffer/CircularBufferInputStream.java +++ b/src/main/java/org/apache/commons/io/input/buffer/CircularBufferInputStream.java @@ -125,12 +125,9 @@ public int read() throws IOException { @Override public int read(final byte[] targetBuffer, final int offset, final int length) throws IOException { - Objects.requireNonNull(targetBuffer, "targetBuffer"); - if (offset < 0) { - throw new IllegalArgumentException("Offset must not be negative"); - } - if (length < 0) { - throw new IllegalArgumentException("Length must not be negative"); + IOUtils.checkFromIndexSize(targetBuffer, offset, length); + if (length == 0) { + return 0; } if (!haveBytes(length)) { return EOF; diff --git a/src/main/java/org/apache/commons/io/output/AppendableWriter.java b/src/main/java/org/apache/commons/io/output/AppendableWriter.java index 2130e448dea..a6f4ff53fa9 100644 --- a/src/main/java/org/apache/commons/io/output/AppendableWriter.java +++ b/src/main/java/org/apache/commons/io/output/AppendableWriter.java @@ -21,6 +21,8 @@ import java.io.Writer; import java.util.Objects; +import org.apache.commons.io.IOUtils; + /** * Writer implementation that writes the data to an {@link Appendable} Object. *

    @@ -124,10 +126,7 @@ public T getAppendable() { */ @Override public void write(final char[] cbuf, final int off, final int len) throws IOException { - Objects.requireNonNull(cbuf, "cbuf"); - if (len < 0 || off + len > cbuf.length) { - throw new IndexOutOfBoundsException("Array Size=" + cbuf.length + ", offset=" + off + ", length=" + len); - } + IOUtils.checkFromIndexSize(cbuf, off, len); for (int i = 0; i < len; i++) { appendable.append(cbuf[off + i]); } diff --git a/src/main/java/org/apache/commons/io/output/BrokenWriter.java b/src/main/java/org/apache/commons/io/output/BrokenWriter.java index 7fe244d2392..1feac6bb84a 100644 --- a/src/main/java/org/apache/commons/io/output/BrokenWriter.java +++ b/src/main/java/org/apache/commons/io/output/BrokenWriter.java @@ -20,6 +20,7 @@ import java.io.Writer; import java.util.function.Supplier; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.function.Erase; /** @@ -121,6 +122,7 @@ private RuntimeException rethrow() { */ @Override public void write(final char[] cbuf, final int off, final int len) throws IOException { + IOUtils.checkFromIndexSize(cbuf, off, len); throw rethrow(); } diff --git a/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java b/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java index b2a9afbfc58..48fb793669b 100644 --- a/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java @@ -21,6 +21,8 @@ import java.io.InputStream; import java.io.OutputStream; +import org.apache.commons.io.IOUtils; + /** * Implements a ThreadSafe version of {@link AbstractByteArrayOutputStream} using instance synchronization. */ @@ -130,13 +132,7 @@ public synchronized InputStream toInputStream() { @Override public void write(final byte[] b, final int off, final int len) { - if (off < 0 - || off > b.length - || len < 0 - || off + len > b.length - || off + len < 0) { - throw new IndexOutOfBoundsException(); - } + IOUtils.checkFromIndexSize(b, off, len); if (len == 0) { return; } diff --git a/src/main/java/org/apache/commons/io/output/ChunkedOutputStream.java b/src/main/java/org/apache/commons/io/output/ChunkedOutputStream.java index 4612a7de2f7..adecd946a3e 100644 --- a/src/main/java/org/apache/commons/io/output/ChunkedOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ChunkedOutputStream.java @@ -172,6 +172,7 @@ int getChunkSize() { */ @Override public void write(final byte[] data, final int srcOffset, final int length) throws IOException { + IOUtils.checkFromIndexSize(data, srcOffset, length); int bytes = length; int dstOffset = srcOffset; while (bytes > 0) { diff --git a/src/main/java/org/apache/commons/io/output/ChunkedWriter.java b/src/main/java/org/apache/commons/io/output/ChunkedWriter.java index 6a6492882b1..f18553fb776 100644 --- a/src/main/java/org/apache/commons/io/output/ChunkedWriter.java +++ b/src/main/java/org/apache/commons/io/output/ChunkedWriter.java @@ -74,6 +74,7 @@ public ChunkedWriter(final Writer writer, final int chunkSize) { */ @Override public void write(final char[] data, final int srcOffset, final int length) throws IOException { + IOUtils.checkFromIndexSize(data, srcOffset, length); int bytes = length; int dstOffset = srcOffset; while (bytes > 0) { diff --git a/src/main/java/org/apache/commons/io/output/ClosedOutputStream.java b/src/main/java/org/apache/commons/io/output/ClosedOutputStream.java index 18783481d44..faa342f40f6 100644 --- a/src/main/java/org/apache/commons/io/output/ClosedOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ClosedOutputStream.java @@ -20,6 +20,8 @@ import java.io.IOException; import java.io.OutputStream; +import org.apache.commons.io.IOUtils; + /** * Throws an IOException on all attempts to write to the stream. *

    @@ -73,6 +75,7 @@ public void flush() throws IOException { */ @Override public void write(final byte b[], final int off, final int len) throws IOException { + IOUtils.checkFromIndexSize(b, off, len); throw new IOException("write(byte[], int, int) failed: stream is closed"); } diff --git a/src/main/java/org/apache/commons/io/output/ClosedWriter.java b/src/main/java/org/apache/commons/io/output/ClosedWriter.java index 58c3993acd3..bc1add898ad 100644 --- a/src/main/java/org/apache/commons/io/output/ClosedWriter.java +++ b/src/main/java/org/apache/commons/io/output/ClosedWriter.java @@ -20,6 +20,8 @@ import java.io.IOException; import java.io.Writer; +import org.apache.commons.io.IOUtils; + /** * Throws an IOException on all attempts to write with {@link #close()} implemented as a noop. *

    @@ -77,6 +79,7 @@ public void flush() throws IOException { */ @Override public void write(final char[] cbuf, final int off, final int len) throws IOException { + IOUtils.checkFromIndexSize(cbuf, off, len); throw new IOException("write(" + new String(cbuf) + ", " + off + ", " + len + ") failed: stream is closed"); } } diff --git a/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java b/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java index d78f049e635..1a13d90eff3 100644 --- a/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java +++ b/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java @@ -28,6 +28,7 @@ import org.apache.commons.io.IOExceptionList; import org.apache.commons.io.IOIndexedException; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.function.IOConsumer; /** @@ -128,6 +129,7 @@ public void write(final char[] cbuf) throws IOException { @SuppressWarnings("resource") // no allocation @Override public void write(final char[] cbuf, final int off, final int len) throws IOException { + IOUtils.checkFromIndexSize(cbuf, off, len); forAllWriters(w -> w.write(cbuf, off, len)); } @@ -159,6 +161,7 @@ public void write(final String str) throws IOException { @SuppressWarnings("resource") // no allocation @Override public void write(final String str, final int off, final int len) throws IOException { + IOUtils.checkFromIndexSize(str, off, len); forAllWriters(w -> w.write(str, off, len)); } diff --git a/src/main/java/org/apache/commons/io/output/NullAppendable.java b/src/main/java/org/apache/commons/io/output/NullAppendable.java index 520daa3a64b..fee5dace602 100644 --- a/src/main/java/org/apache/commons/io/output/NullAppendable.java +++ b/src/main/java/org/apache/commons/io/output/NullAppendable.java @@ -19,6 +19,8 @@ import java.io.IOException; +import org.apache.commons.io.IOUtils; + /** * Appends all data to the famous /dev/null. *

    @@ -51,6 +53,7 @@ public Appendable append(final CharSequence csq) throws IOException { @Override public Appendable append(final CharSequence csq, final int start, final int end) throws IOException { + IOUtils.checkFromToIndex(csq, start, end); return this; } diff --git a/src/main/java/org/apache/commons/io/output/NullOutputStream.java b/src/main/java/org/apache/commons/io/output/NullOutputStream.java index 5275aea9c75..682d0f8e768 100644 --- a/src/main/java/org/apache/commons/io/output/NullOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/NullOutputStream.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.io.OutputStream; +import org.apache.commons.io.IOUtils; + /** * Never writes data. Calls never go beyond this class. *

    @@ -75,7 +77,7 @@ public void write(final byte[] b) throws IOException { */ @Override public void write(final byte[] b, final int off, final int len) { - // noop + IOUtils.checkFromIndexSize(b, off, len); } /** diff --git a/src/main/java/org/apache/commons/io/output/NullWriter.java b/src/main/java/org/apache/commons/io/output/NullWriter.java index ab7f4ddd6f8..b209056d194 100644 --- a/src/main/java/org/apache/commons/io/output/NullWriter.java +++ b/src/main/java/org/apache/commons/io/output/NullWriter.java @@ -18,6 +18,8 @@ import java.io.Writer; +import org.apache.commons.io.IOUtils; + /** * Never writes data. Calls never go beyond this class. *

    @@ -87,7 +89,7 @@ public Writer append(final CharSequence csq) { */ @Override public Writer append(final CharSequence csq, final int start, final int end) { - //to /dev/null + IOUtils.checkFromToIndex(csq, start, end); return this; } @@ -154,7 +156,7 @@ public void write(final String str) { */ @Override public void write(final String str, final int st, final int end) { - //to /dev/null + IOUtils.checkFromIndexSize(str, st, end); } } diff --git a/src/main/java/org/apache/commons/io/output/StringBuilderWriter.java b/src/main/java/org/apache/commons/io/output/StringBuilderWriter.java index 106525e88f9..836e8bc3753 100644 --- a/src/main/java/org/apache/commons/io/output/StringBuilderWriter.java +++ b/src/main/java/org/apache/commons/io/output/StringBuilderWriter.java @@ -20,6 +20,8 @@ import java.io.StringWriter; import java.io.Writer; +import org.apache.commons.io.IOUtils; + /** * {@link Writer} implementation that outputs to a {@link StringBuilder}. *

    @@ -149,9 +151,8 @@ public String toString() { */ @Override public void write(final char[] value, final int offset, final int length) { - if (value != null) { - builder.append(value, offset, length); - } + IOUtils.checkFromIndexSize(value, offset, length); + builder.append(value, offset, length); } /** diff --git a/src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java b/src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java index e5892dbd885..2efe401910a 100644 --- a/src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.OutputStream; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.function.IOConsumer; import org.apache.commons.io.function.IOFunction; @@ -249,6 +250,7 @@ public void write(final byte[] b) throws IOException { @SuppressWarnings("resource") // the underlying stream is managed by a subclass. @Override public void write(final byte[] b, final int off, final int len) throws IOException { + IOUtils.checkFromIndexSize(b, off, len); // TODO we could write the sub-array up the threshold, fire the event, // and then write the rest so the event is always fired at the precise point. checkThreshold(len); diff --git a/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java b/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java index f20ccefad74..a47a3b2aa2a 100644 --- a/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.io.OutputStream; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.build.AbstractOrigin; import org.apache.commons.io.build.AbstractStreamBuilder; import org.apache.commons.io.function.Uncheck; @@ -214,9 +215,7 @@ public InputStream toInputStream() { @Override public void write(final byte[] b, final int off, final int len) { - if (off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0) { - throw new IndexOutOfBoundsException(String.format("offset=%,d, length=%,d", off, len)); - } + IOUtils.checkFromIndexSize(b, off, len); if (len == 0) { return; } 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 dcb6e226e72..c753ccdce87 100644 --- a/src/main/java/org/apache/commons/io/output/WriterOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/WriterOutputStream.java @@ -448,6 +448,7 @@ public void write(final byte[] b) throws IOException { */ @Override public void write(final byte[] b, int off, int len) throws IOException { + IOUtils.checkFromIndexSize(b, off, len); while (len > 0) { final int c = Math.min(len, decoderIn.remaining()); decoderIn.put(b, off, c); diff --git a/src/main/java/org/apache/commons/io/output/XmlStreamWriter.java b/src/main/java/org/apache/commons/io/output/XmlStreamWriter.java index 40fdcc8f730..73630a4bca2 100644 --- a/src/main/java/org/apache/commons/io/output/XmlStreamWriter.java +++ b/src/main/java/org/apache/commons/io/output/XmlStreamWriter.java @@ -306,6 +306,7 @@ public String getEncoding() { */ @Override public void write(final char[] cbuf, final int off, final int len) throws IOException { + IOUtils.checkFromIndexSize(cbuf, off, len); if (prologWriter != null) { detectEncoding(cbuf, off, len); } else { diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java index d062ae61ea8..02574946bf8 100644 --- a/src/test/java/org/apache/commons/io/IOUtilsTest.java +++ b/src/test/java/org/apache/commons/io/IOUtilsTest.java @@ -400,6 +400,56 @@ 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) + ); + } + + @ParameterizedTest + @MethodSource + void testCheckFromToIndexInvalidCases(int from, int to, int arrayLength) { + final IndexOutOfBoundsException ex = assertThrows(IndexOutOfBoundsException.class, () -> IOUtils.checkFromToIndex(from, to, arrayLength)); + assertTrue(ex.getMessage().contains(String.valueOf(from))); + assertTrue(ex.getMessage().contains(String.valueOf(to))); + assertTrue(ex.getMessage().contains(String.valueOf(arrayLength))); + // Optional requirement: compare the exception message for Java 8 and Java 9+ + if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_9)) { + final IndexOutOfBoundsException jreEx = assertThrows(IndexOutOfBoundsException.class, () -> { + try { + Objects.class.getDeclaredMethod("checkFromToIndex", int.class, int.class, int.class).invoke(null, from, to, arrayLength); + } catch (InvocationTargetException ite) { + throw ite.getTargetException(); + } + }); + assertEquals(jreEx.getMessage(), ex.getMessage()); + } + } + @Test void testClose() { assertDoesNotThrow(() -> IOUtils.close((Closeable) null)); From 051a86a3c28eece62e8008c31435e274d7ef342e Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Mon, 29 Sep 2025 10:26:44 +0200 Subject: [PATCH 04/25] fix: failing tests --- .../java/org/apache/commons/io/IOUtils.java | 6 ++- .../input/UnsynchronizedBufferedReader.java | 7 ++- .../apache/commons/io/output/NullWriter.java | 12 ++++-- .../io/input/ClosedInputStreamTest.java | 5 ++- .../commons/io/input/ClosedReaderTest.java | 6 +-- .../io/input/MarkShieldInputStreamTest.java | 2 +- .../commons/io/input/NullInputStreamTest.java | 2 +- .../commons/io/input/ProxyReaderTest.java | 5 +++ .../UnsynchronizedBufferedReaderTest.java | 43 +++++++++++++------ .../io/output/ByteArrayOutputStreamTest.java | 2 +- .../commons/io/output/NullAppendableTest.java | 7 ++- .../commons/io/output/ProxyWriterTest.java | 9 ++-- 12 files changed, 73 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index 0d671eac9ee..11d1585309d 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -496,9 +496,11 @@ static void checkFromIndexSize(final int off, final int len, final int arrayLeng *

  • {@code toIndex <= seq.length()}
  • *
* + *

If {@code seq} is null, it is treated as the string "null".

+ * *

If the range is invalid, this method throws an {@link IndexOutOfBoundsException} with a descriptive message.

* - * @param seq The char sequence to be checked against + * @param seq The char sequence to be checked against, may be {@code null} * @param fromIndex The starting index into the char sequence (inclusive) * @param toIndex The ending index into the char sequence (exclusive) * @throws NullPointerException If the char sequence is null @@ -506,7 +508,7 @@ static void checkFromIndexSize(final int off, final int len, final int arrayLeng * @since 2.21.0 */ public static void checkFromToIndex(final CharSequence seq, int fromIndex, final int toIndex) { - checkFromToIndex(fromIndex, toIndex, requireNonNull(seq, "char sequence").length()); + checkFromToIndex(fromIndex, toIndex, seq != null ? seq.length() : 4); } static void checkFromToIndex(final int fromIndex, final int toIndex, final int length) { diff --git a/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java b/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java index e8bf3363f05..bde55873f24 100644 --- a/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java +++ b/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java @@ -271,11 +271,16 @@ public int read() throws IOException { */ @Override public int read(final char[] buffer, int offset, final int length) throws IOException { + /* + * First throw on a closed reader, then check the parameters. + * + * This behavior is not specified in the Javadoc, but is followed by most readers in java.io. + */ + checkOpen(); IOUtils.checkFromIndexSize(buffer, offset, length); if (length == 0) { return 0; } - checkOpen(); int outstanding = length; while (outstanding > 0) { diff --git a/src/main/java/org/apache/commons/io/output/NullWriter.java b/src/main/java/org/apache/commons/io/output/NullWriter.java index b209056d194..9741b819766 100644 --- a/src/main/java/org/apache/commons/io/output/NullWriter.java +++ b/src/main/java/org/apache/commons/io/output/NullWriter.java @@ -112,6 +112,7 @@ public void flush() { */ @Override public void write(final char[] chr) { + write(chr, 0, chr.length); //to /dev/null } @@ -124,6 +125,7 @@ public void write(final char[] chr) { */ @Override public void write(final char[] chr, final int st, final int end) { + IOUtils.checkFromIndexSize(chr, st, end); //to /dev/null } @@ -144,6 +146,7 @@ public void write(final int b) { */ @Override public void write(final String str) { + write(str, 0, str.length()); //to /dev/null } @@ -151,12 +154,13 @@ public void write(final String str) { * Does nothing, like writing to {@code /dev/null}. * * @param str The string to write. - * @param st The start offset. - * @param end The number of characters to write. + * @param off The start offset. + * @param len The number of characters to write. */ @Override - public void write(final String str, final int st, final int end) { - IOUtils.checkFromIndexSize(str, st, end); + public void write(final String str, final int off, final int len) { + IOUtils.checkFromIndexSize(str, off, len); + //to /dev/null } } diff --git a/src/test/java/org/apache/commons/io/input/ClosedInputStreamTest.java b/src/test/java/org/apache/commons/io/input/ClosedInputStreamTest.java index b19364d8a17..2586806b3a5 100644 --- a/src/test/java/org/apache/commons/io/input/ClosedInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/ClosedInputStreamTest.java @@ -22,6 +22,7 @@ import java.io.InputStream; +import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; /** @@ -86,7 +87,7 @@ void testReadArray() throws Exception { try (ClosedInputStream cis = new ClosedInputStream()) { assertEquals(EOF, cis.read(new byte[4096])); assertEquals(EOF, cis.read(new byte[1])); - assertEquals(EOF, cis.read(new byte[0])); + assertEquals(0, cis.read(IOUtils.EMPTY_BYTE_ARRAY)); } } @@ -95,7 +96,7 @@ void testReadArrayIndex() throws Exception { try (ClosedInputStream cis = new ClosedInputStream()) { assertEquals(EOF, cis.read(new byte[4096], 0, 1)); assertEquals(EOF, cis.read(new byte[1], 0, 1)); - assertEquals(EOF, cis.read(new byte[0], 0, 0)); + assertEquals(0, cis.read(IOUtils.EMPTY_BYTE_ARRAY, 0, 0)); } } diff --git a/src/test/java/org/apache/commons/io/input/ClosedReaderTest.java b/src/test/java/org/apache/commons/io/input/ClosedReaderTest.java index cb57f9e6f91..891049f9ef5 100644 --- a/src/test/java/org/apache/commons/io/input/ClosedReaderTest.java +++ b/src/test/java/org/apache/commons/io/input/ClosedReaderTest.java @@ -46,7 +46,7 @@ void testReadArray() throws Exception { try (Reader reader = new ClosedReader()) { assertEquals(EOF, reader.read(new char[4096])); assertEquals(EOF, reader.read(new char[1])); - assertEquals(EOF, reader.read(new char[0])); + assertEquals(0, reader.read(new char[0])); } } @@ -55,7 +55,7 @@ void testReadArrayIndex() throws Exception { try (Reader reader = new ClosedReader()) { assertEquals(EOF, reader.read(CharBuffer.wrap(new char[4096]))); assertEquals(EOF, reader.read(CharBuffer.wrap(new char[1]))); - assertEquals(EOF, reader.read(CharBuffer.wrap(new char[0]))); + assertEquals(0, reader.read(CharBuffer.wrap(new char[0]))); } } @@ -64,7 +64,7 @@ void testReadCharBuffer() throws Exception { try (Reader reader = new ClosedReader()) { assertEquals(EOF, reader.read(new char[4096])); assertEquals(EOF, reader.read(new char[1])); - assertEquals(EOF, reader.read(new char[0])); + assertEquals(0, reader.read(new char[0])); } } diff --git a/src/test/java/org/apache/commons/io/input/MarkShieldInputStreamTest.java b/src/test/java/org/apache/commons/io/input/MarkShieldInputStreamTest.java index a1e4d17f457..2566f8b51e6 100644 --- a/src/test/java/org/apache/commons/io/input/MarkShieldInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/MarkShieldInputStreamTest.java @@ -152,7 +152,7 @@ void testReadByteArrayIntIntAfterClose(final int len) throws Exception { MarkShieldInputStream msis = new MarkShieldInputStream(in)) { assertEquals(len, in.available()); in.close(); - assertEquals(0, in.read(new byte[0], 0, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> in.read(new byte[0], 0, 1)); assertEquals(0, in.read(new byte[1], 0, 0)); assertThrows(IOException.class, () -> in.read(new byte[2], 0, 1)); } diff --git a/src/test/java/org/apache/commons/io/input/NullInputStreamTest.java b/src/test/java/org/apache/commons/io/input/NullInputStreamTest.java index b79555d8c02..9b59ac90c0e 100644 --- a/src/test/java/org/apache/commons/io/input/NullInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/NullInputStreamTest.java @@ -249,7 +249,7 @@ void testReadByteArrayIntIntAfterClose() throws Exception { try (InputStream in = new NullInputStream()) { assertEquals(0, in.available()); in.close(); - assertEquals(0, in.read(new byte[0], 0, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> in.read(new byte[0], 0, 1)); assertEquals(0, in.read(new byte[1], 0, 0)); assertThrows(IOException.class, () -> in.read(new byte[2], 0, 1)); } diff --git a/src/test/java/org/apache/commons/io/input/ProxyReaderTest.java b/src/test/java/org/apache/commons/io/input/ProxyReaderTest.java index f33979ce1b8..681ab90eae0 100644 --- a/src/test/java/org/apache/commons/io/input/ProxyReaderTest.java +++ b/src/test/java/org/apache/commons/io/input/ProxyReaderTest.java @@ -38,6 +38,11 @@ public int read(final char[] chars) throws IOException { return chars == null ? 0 : super.read(chars); } + @Override + public int read(final char[] chars, final int offset, final int length) throws IOException { + return chars == null ? 0 : super.read(chars, offset, length); + } + @Override public int read(final CharBuffer target) throws IOException { return target == null ? 0 : super.read(target); 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 9c7dbf6fd81..a144bc418d0 100644 --- a/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java +++ b/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java @@ -353,35 +353,46 @@ void testRead() throws IOException { * @throws IOException test failure. */ @Test - void testReadArray() throws IOException { + void testReadArray1() throws IOException { final char[] ca = new char[2]; try (UnsynchronizedBufferedReader toRet = new UnsynchronizedBufferedReader(new InputStreamReader(new ByteArrayInputStream(new byte[0])))) { - /* Null buffer should throw NPE even when len == 0 */ + /* Validate parameters, before returning 0 */ assertThrows(NullPointerException.class, () -> toRet.read(null, 1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> toRet.read(ca, 1, 5)); + /* Read zero bytes should return 0 */ + assertEquals(0, toRet.read(ca, 0, 0)); toRet.close(); - assertThrows(IOException.class, () -> toRet.read(null, 1, 0)); - /* Closed reader should throw IOException reading zero bytes */ - assertThrows(IOException.class, () -> toRet.read(ca, 0, 0)); /* - * Closed reader should throw IOException in preference to index out of bounds + * After close, readers in java.io consistently throw IOException before checking parameters or returning 0. */ - // Read should throw IOException before - // ArrayIndexOutOfBoundException + assertThrows(IOException.class, () -> toRet.read(null, 1, 0)); assertThrows(IOException.class, () -> toRet.read(ca, 1, 5)); + assertThrows(IOException.class, () -> toRet.read(ca, 0, 0)); } + } + + @Test + void testReadArray2() throws IOException { + final char[] ca = new char[2]; // Test to ensure that a drained stream returns 0 at EOF try (UnsynchronizedBufferedReader toRet2 = new UnsynchronizedBufferedReader(new InputStreamReader(new ByteArrayInputStream(new byte[2])))) { assertEquals(2, toRet2.read(ca, 0, 2)); assertEquals(-1, toRet2.read(ca, 0, 2)); assertEquals(0, toRet2.read(ca, 0, 0)); } + } + @Test + void testReadArray3() throws IOException { // Test for method int UnsynchronizedBufferedReader.read(char [], int, int) final char[] buf = new char[testString.length()]; br = new UnsynchronizedBufferedReader(new StringReader(testString)); br.read(buf, 50, 500); assertTrue(new String(buf, 50, 500).equals(testString.substring(0, 500))); + } + @Test + void testReadArray4() throws IOException { try (UnsynchronizedBufferedReader bufin = new UnsynchronizedBufferedReader(new Reader() { int size = 2; int pos; @@ -424,11 +435,18 @@ public boolean ready() throws IOException { final int result = bufin.read(new char[2], 0, 2); assertEquals(result, 1); } + } + + @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") @@ -456,13 +474,12 @@ void testReadArrayException() throws IOException { br = new UnsynchronizedBufferedReader(new StringReader(testString)); final char[] nullCharArray = null; final char[] charArray = testString.toCharArray(); - assertThrows(IndexOutOfBoundsException.class, () -> br.read(nullCharArray, -1, -1)); - assertThrows(IndexOutOfBoundsException.class, () -> br.read(nullCharArray, -1, 0)); + assertThrows(NullPointerException.class, () -> br.read(nullCharArray, -1, 0)); assertThrows(NullPointerException.class, () -> br.read(nullCharArray, 0, -1)); - assertThrows(NullPointerException.class, () -> br.read(nullCharArray, 0, 0)); - assertThrows(NullPointerException.class, () -> br.read(nullCharArray, 0, 1)); - assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, -1, -1)); + assertThrows(NullPointerException.class, () -> br.read(nullCharArray, 1, 1)); assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, -1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, 0, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, charArray.length, 1)); br.read(charArray, 0, 0); br.read(charArray, 0, charArray.length); diff --git a/src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTest.java index fa19ba2a41a..9bae1cb5f62 100644 --- a/src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTest.java +++ b/src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTest.java @@ -177,7 +177,7 @@ void testInvalidWriteOffsetOver(final String baosName, final BAOSFactory baos @MethodSource("baosFactories") void testInvalidWriteOffsetUnder(final String baosName, final BAOSFactory baosFactory) throws IOException { try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { - assertThrows(IndexOutOfBoundsException.class, () -> baout.write(null, -1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> baout.write(IOUtils.EMPTY_BYTE_ARRAY, -1, 0)); } } diff --git a/src/test/java/org/apache/commons/io/output/NullAppendableTest.java b/src/test/java/org/apache/commons/io/output/NullAppendableTest.java index 6caa15023d9..09b1d1bf2ba 100644 --- a/src/test/java/org/apache/commons/io/output/NullAppendableTest.java +++ b/src/test/java/org/apache/commons/io/output/NullAppendableTest.java @@ -16,6 +16,8 @@ */ package org.apache.commons.io.output; +import static org.junit.jupiter.api.Assertions.assertThrows; + import java.io.IOException; import org.junit.jupiter.api.Test; @@ -32,7 +34,10 @@ void testNull() throws IOException { appendable.append("A"); appendable.append("A", 0, 1); appendable.append(null, 0, 1); - appendable.append(null, -1, -1); + // `null` is converted to "null" + assertThrows(IndexOutOfBoundsException.class, () -> appendable.append(null, -1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> appendable.append(null, 1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> appendable.append(null, 0, 5)); } } diff --git a/src/test/java/org/apache/commons/io/output/ProxyWriterTest.java b/src/test/java/org/apache/commons/io/output/ProxyWriterTest.java index 9954ca06803..e810d39d726 100644 --- a/src/test/java/org/apache/commons/io/output/ProxyWriterTest.java +++ b/src/test/java/org/apache/commons/io/output/ProxyWriterTest.java @@ -205,8 +205,8 @@ public void write(final String str, final int off, final int len) throws IOExcep @Test void testNullCharArray() throws Exception { try (ProxyWriter proxy = new ProxyWriter(NullWriter.INSTANCE)) { - proxy.write((char[]) null); - proxy.write((char[]) null, 0, 0); + assertThrows(NullPointerException.class, () -> proxy.write((char[]) null)); + assertThrows(NullPointerException.class, () -> proxy.write((char[]) null, 0, 0)); } } @@ -220,8 +220,9 @@ void testNullCharSequence() throws Exception { @Test void testNullString() throws Exception { try (ProxyWriter proxy = new ProxyWriter(NullWriter.INSTANCE)) { - proxy.write((String) null); - proxy.write((String) null, 0, 0); + // Default implementation delegates to write(char[], int, int) + assertThrows(NullPointerException.class, () -> proxy.write((String) null)); + assertThrows(NullPointerException.class, () -> proxy.write((String) null, 0, 0)); } } From cbfb55cc15c3f4344bbe7079026b0214024b11e9 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Mon, 29 Sep 2025 10:27:20 +0200 Subject: [PATCH 05/25] fix: inline static import --- .../java/org/apache/commons/io/IOUtils.java | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index 11d1585309d..d1d01573698 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -17,8 +17,6 @@ package org.apache.commons.io; -import static java.util.Objects.requireNonNull; - import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -246,7 +244,7 @@ public class IOUtils { public static BufferedInputStream buffer(final InputStream inputStream) { // reject null early on rather than waiting for IO operation to fail // not checked by BufferedInputStream - requireNonNull(inputStream, "inputStream"); + Objects.requireNonNull(inputStream, "inputStream"); return inputStream instanceof BufferedInputStream ? (BufferedInputStream) inputStream : new BufferedInputStream(inputStream); } @@ -265,7 +263,7 @@ public static BufferedInputStream buffer(final InputStream inputStream) { public static BufferedInputStream buffer(final InputStream inputStream, final int size) { // reject null early on rather than waiting for IO operation to fail // not checked by BufferedInputStream - requireNonNull(inputStream, "inputStream"); + Objects.requireNonNull(inputStream, "inputStream"); return inputStream instanceof BufferedInputStream ? (BufferedInputStream) inputStream : new BufferedInputStream(inputStream, size); } @@ -283,7 +281,7 @@ public static BufferedInputStream buffer(final InputStream inputStream, final in public static BufferedOutputStream buffer(final OutputStream outputStream) { // reject null early on rather than waiting for IO operation to fail // not checked by BufferedInputStream - requireNonNull(outputStream, "outputStream"); + Objects.requireNonNull(outputStream, "outputStream"); return outputStream instanceof BufferedOutputStream ? (BufferedOutputStream) outputStream : new BufferedOutputStream(outputStream); } @@ -302,7 +300,7 @@ public static BufferedOutputStream buffer(final OutputStream outputStream) { public static BufferedOutputStream buffer(final OutputStream outputStream, final int size) { // reject null early on rather than waiting for IO operation to fail // not checked by BufferedInputStream - requireNonNull(outputStream, "outputStream"); + Objects.requireNonNull(outputStream, "outputStream"); return outputStream instanceof BufferedOutputStream ? (BufferedOutputStream) outputStream : new BufferedOutputStream(outputStream, size); } @@ -429,7 +427,7 @@ private static char[] charArray(final int size) { * @since 2.21.0 */ public static void checkFromIndexSize(final byte[] array, final int off, final int len) { - checkFromIndexSize(off, len, requireNonNull(array, "byte array").length); + checkFromIndexSize(off, len, Objects.requireNonNull(array, "byte array").length); } /** @@ -453,7 +451,7 @@ public static void checkFromIndexSize(final byte[] array, final int off, final i * @since 2.21.0 */ public static void checkFromIndexSize(final char[] array, final int off, final int len) { - checkFromIndexSize(off, len, requireNonNull(array, "char array").length); + checkFromIndexSize(off, len, Objects.requireNonNull(array, "char array").length); } /** @@ -477,7 +475,7 @@ public static void checkFromIndexSize(final char[] array, final int off, final i * @since 2.21.0 */ public static void checkFromIndexSize(final String str, final int off, final int len) { - checkFromIndexSize(off, len, requireNonNull(str, "str").length()); + checkFromIndexSize(off, len, Objects.requireNonNull(str, "str").length()); } static void checkFromIndexSize(final int off, final int len, final int arrayLength) { @@ -1298,7 +1296,7 @@ public static void copy(final InputStream input, final Writer writer, final Stri */ @SuppressWarnings("resource") // streams are closed by the caller. public static QueueInputStream copy(final java.io.ByteArrayOutputStream outputStream) throws IOException { - requireNonNull(outputStream, "outputStream"); + Objects.requireNonNull(outputStream, "outputStream"); final QueueInputStream in = new QueueInputStream(); outputStream.writeTo(in.newQueueOutputStream()); return in; @@ -1479,7 +1477,7 @@ public static int copy(final Reader reader, final Writer writer) throws IOExcept * @since 2.9.0 */ public static long copy(final URL url, final File file) throws IOException { - try (OutputStream outputStream = Files.newOutputStream(requireNonNull(file, "file").toPath())) { + try (OutputStream outputStream = Files.newOutputStream(Objects.requireNonNull(file, "file").toPath())) { return copy(url, outputStream); } } @@ -1502,7 +1500,7 @@ public static long copy(final URL url, final File file) throws IOException { * @since 2.9.0 */ public static long copy(final URL url, final OutputStream outputStream) throws IOException { - try (InputStream inputStream = requireNonNull(url, "url").openStream()) { + try (InputStream inputStream = Objects.requireNonNull(url, "url").openStream()) { return copyLarge(inputStream, outputStream); } } @@ -1551,8 +1549,8 @@ public static long copyLarge(final InputStream inputStream, final OutputStream o @SuppressWarnings("resource") // streams are closed by the caller. public static long copyLarge(final InputStream inputStream, final OutputStream outputStream, final byte[] buffer) throws IOException { - requireNonNull(inputStream, "inputStream"); - requireNonNull(outputStream, "outputStream"); + Objects.requireNonNull(inputStream, "inputStream"); + Objects.requireNonNull(outputStream, "outputStream"); long count = 0; int n; while (EOF != (n = inputStream.read(buffer))) { @@ -2820,7 +2818,7 @@ public static byte[] toByteArray(final InputStream inputStream) throws IOExcepti * @since 2.1 */ public static byte[] toByteArray(final InputStream input, final int size) throws IOException { - return toByteArray(requireNonNull(input, "input")::read, size); + return toByteArray(Objects.requireNonNull(input, "input")::read, size); } /** @@ -2846,7 +2844,7 @@ public static byte[] toByteArray(final InputStream input, final int size) throws * @since 2.21.0 */ public static byte[] toByteArray(final InputStream input, final int size, final int chunkSize) throws IOException { - requireNonNull(input, "input"); + Objects.requireNonNull(input, "input"); if (chunkSize <= 0) { throw new IllegalArgumentException("Chunk size must be greater than zero: " + chunkSize); } @@ -3994,7 +3992,7 @@ public static void writeLines(final Collection lines, String lineEnding, fina * @since 2.7 */ public static Writer writer(final Appendable appendable) { - requireNonNull(appendable, "appendable"); + Objects.requireNonNull(appendable, "appendable"); if (appendable instanceof Writer) { return (Writer) appendable; } From 8b45ad9176a664df45fb939269e42edd58ac3b3d Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Mon, 29 Sep 2025 10:54:20 +0200 Subject: [PATCH 06/25] fix: add usage examples to Javadoc --- .../java/org/apache/commons/io/IOUtils.java | 150 +++++++++++++----- 1 file changed, 112 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index d1d01573698..bf685c435f2 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -407,7 +407,7 @@ private static char[] charArray(final int size) { } /** - * Checks if the sub-range described by an offset and length is valid for the given array. + * Validates that the sub-range {@code [off, off + len)} is within the bounds of the given array. * *

The range is valid if all of the following hold:

*
    @@ -416,14 +416,39 @@ private static char[] charArray(final int size) { *
  • {@code off + len <= array.length}
  • *
* - *

If the range is invalid, this method throws an - * {@link IndexOutOfBoundsException} with a descriptive message.

+ *

If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message.

* - * @param array The array to be checked against - * @param off The starting offset into the array - * @param len The number of elements in the range - * @throws NullPointerException If the array is null - * @throws IndexOutOfBoundsException If the range {@code [off, off + len)} is out of bounds + *

Typical usage in {@link InputStream#read(byte[], int, int)} and {@link OutputStream#write(byte[], int, int)} implementations:

+ * + *
{@code
+     * @Override
+     * public int read(byte[] b, int off, int len) throws IOException {
+     *     IOUtils.checkFromIndexSize(b, off, len);
+     *     if (len == 0) {
+     *         return 0;
+     *     }
+     *     ensureOpen();
+     *     // perform read...
+     * }
+     *
+     * @Override
+     * public void write(byte[] b, int off, int len) throws IOException {
+     *     IOUtils.checkFromIndexSize(b, off, len);
+     *     if (len == 0) {
+     *         return;
+     *     }
+     *     ensureOpen();
+     *     // perform write...
+     * }
+     * }
+ * + * @param array the array against which the range is validated + * @param off the starting offset into the array (inclusive) + * @param len the number of elements to access + * @throws NullPointerException if {@code array} is {@code null} + * @throws IndexOutOfBoundsException if the range {@code [off, off + len)} is out of bounds for {@code array} + * @see InputStream#read(byte[], int, int) + * @see OutputStream#write(byte[], int, int) * @since 2.21.0 */ public static void checkFromIndexSize(final byte[] array, final int off, final int len) { @@ -431,7 +456,7 @@ public static void checkFromIndexSize(final byte[] array, final int off, final i } /** - * Checks if the sub-range described by an offset and length is valid for the given array. + * Validates that the sub-range {@code [off, off + len)} is within the bounds of the given array. * *

The range is valid if all of the following hold:

*
    @@ -440,14 +465,39 @@ public static void checkFromIndexSize(final byte[] array, final int off, final i *
  • {@code off + len <= array.length}
  • *
* - *

If the range is invalid, this method throws an - * {@link IndexOutOfBoundsException} with a descriptive message.

+ *

If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message.

* - * @param array The array to be checked against - * @param off The starting offset into the array - * @param len The number of elements in the range - * @throws NullPointerException If the array is null - * @throws IndexOutOfBoundsException If the range {@code [off, off + len)} is out of bounds + *

Typical usage in {@link Reader#read(char[], int, int)} and {@link Writer#write(char[], int, int)} implementations:

+ * + *
{@code
+     * @Override
+     * public int read(char[] cbuf, int off, int len) throws IOException {
+     *     ensureOpen();
+     *     IOUtils.checkFromIndexSize(cbuf, off, len);
+     *     if (len == 0) {
+     *         return 0;
+     *     }
+     *     // perform read...
+     * }
+     *
+     * @Override
+     * public void write(char[] cbuf, int off, int len) throws IOException {
+     *     ensureOpen();
+     *     IOUtils.checkFromIndexSize(cbuf, off, len);
+     *     if (len == 0) {
+     *         return;
+     *     }
+     *     // perform write...
+     * }
+     * }
+ * + * @param array the array against which the range is validated + * @param off the starting offset into the array (inclusive) + * @param len the number of characters to access + * @throws NullPointerException if {@code array} is {@code null} + * @throws IndexOutOfBoundsException if the range {@code [off, off + len)} is out of bounds for {@code array} + * @see Reader#read(char[], int, int) + * @see Writer#write(char[], int, int) * @since 2.21.0 */ public static void checkFromIndexSize(final char[] array, final int off, final int len) { @@ -455,23 +505,36 @@ public static void checkFromIndexSize(final char[] array, final int off, final i } /** - * Checks if the sub-range described by an offset and length is valid for the given string. + * Validates that the sub-range {@code [off, off + len)} is within the bounds of the given string. * *

The range is valid if all of the following hold:

*
    *
  • {@code off >= 0}
  • *
  • {@code len >= 0}
  • - *
  • {@code off + len <= array.length}
  • + *
  • {@code off + len <= str.length()}
  • *
* - *

If the range is invalid, this method throws an - * {@link IndexOutOfBoundsException} with a descriptive message.

+ *

If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message.

+ * + *

Typical usage in {@link Writer#write(String, int, int)} implementations:

* - * @param str The char sequence to be checked against - * @param off The starting offset into the array - * @param len The number of elements in the range - * @throws NullPointerException If the array is null - * @throws IndexOutOfBoundsException If the range {@code [off, off + len)} is out of bounds + *
{@code
+     * @Override
+     * public void write(String str, int off, int len) throws IOException {
+     *     IOUtils.checkFromIndexSize(str, off, len);
+     *     if (len == 0) {
+     *         return;
+     *     }
+     *     // perform write...
+     * }
+     * }
+ * + * @param str the string against which the range is validated + * @param off the starting offset into the string (inclusive) + * @param len the number of characters to write + * @throws NullPointerException if {@code str} is {@code null} + * @throws IndexOutOfBoundsException if the range {@code [off, off + len)} is out of bounds for {@code str} + * @see Writer#write(String, int, int) * @since 2.21.0 */ public static void checkFromIndexSize(final String str, final int off, final int len) { @@ -485,27 +548,38 @@ static void checkFromIndexSize(final int off, final int len, final int arrayLeng } /** - * Checks if the sub-sequence described by fromIndex (inclusive) and toIndex (exclusive) is valid for the given {@link CharSequence}. + * Validates that the sub-sequence {@code [fromIndex, toIndex)} is within the bounds of the given {@link CharSequence}. * - *

The range is valid if all of the following hold:

+ *

The sub-sequence is valid if all of the following hold:

*
    - *
  • {@code fromIndex >= 0}
  • - *
  • {@code fromIndex <= toIndex}
  • - *
  • {@code toIndex <= seq.length()}
  • + *
  • {@code fromIndex >= 0}
  • + *
  • {@code fromIndex <= toIndex}
  • + *
  • {@code toIndex <= seq.length()}
  • *
* - *

If {@code seq} is null, it is treated as the string "null".

+ *

If {@code seq} is {@code null}, it is treated as the literal string {@code "null"} (length {@code 4}).

* - *

If the range is invalid, this method throws an {@link IndexOutOfBoundsException} with a descriptive message.

+ *

If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message.

+ * + *

Typical usage in {@link Appendable#append(CharSequence, int, int)} implementations:

+ * + *
{@code
+     * @Override
+     * public Appendable append(CharSequence csq, int start, int end) throws IOException {
+     *     IOUtils.checkFromToIndex(csq, start, end);
+     *     // perform append...
+     *     return this;
+     * }
+     * }
* - * @param seq The char sequence to be checked against, may be {@code null} - * @param fromIndex The starting index into the char sequence (inclusive) - * @param toIndex The ending index into the char sequence (exclusive) - * @throws NullPointerException If the char sequence is null - * @throws IndexOutOfBoundsException If the range {@code [fromIndex, toIndex)} is out of bounds + * @param seq the character sequence to validate (may be {@code null}, treated as {@code "null"}) + * @param fromIndex the starting index (inclusive) + * @param toIndex the ending index (exclusive) + * @throws IndexOutOfBoundsException if the range {@code [fromIndex, toIndex)} is out of bounds for {@code seq} + * @see Appendable#append(CharSequence, int, int) * @since 2.21.0 */ - public static void checkFromToIndex(final CharSequence seq, int fromIndex, final int toIndex) { + public static void checkFromToIndex(final CharSequence seq, final int fromIndex, final int toIndex) { checkFromToIndex(fromIndex, toIndex, seq != null ? seq.length() : 4); } From 2220753dc30034d8bbc8ecadb12c92757264d71f Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Mon, 29 Sep 2025 11:13:31 +0200 Subject: [PATCH 07/25] fix: Javadoc for JDK 8 and 11 --- .../java/org/apache/commons/io/IOUtils.java | 28 +++++++++---------- 1 file changed, 14 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 bf685c435f2..09c48f139d0 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -420,8 +420,8 @@ private static char[] charArray(final int size) { * *

Typical usage in {@link InputStream#read(byte[], int, int)} and {@link OutputStream#write(byte[], int, int)} implementations:

* - *
{@code
-     * @Override
+     * 

+     * {@code @Override}
      * public int read(byte[] b, int off, int len) throws IOException {
      *     IOUtils.checkFromIndexSize(b, off, len);
      *     if (len == 0) {
@@ -431,7 +431,7 @@ private static char[] charArray(final int size) {
      *     // perform read...
      * }
      *
-     * @Override
+     * {@code @Override}
      * public void write(byte[] b, int off, int len) throws IOException {
      *     IOUtils.checkFromIndexSize(b, off, len);
      *     if (len == 0) {
@@ -440,7 +440,7 @@ private static char[] charArray(final int size) {
      *     ensureOpen();
      *     // perform write...
      * }
-     * }
+ *
* * @param array the array against which the range is validated * @param off the starting offset into the array (inclusive) @@ -469,8 +469,8 @@ public static void checkFromIndexSize(final byte[] array, final int off, final i * *

Typical usage in {@link Reader#read(char[], int, int)} and {@link Writer#write(char[], int, int)} implementations:

* - *
{@code
-     * @Override
+     * 

+     * {@code @Override}
      * public int read(char[] cbuf, int off, int len) throws IOException {
      *     ensureOpen();
      *     IOUtils.checkFromIndexSize(cbuf, off, len);
@@ -480,7 +480,7 @@ public static void checkFromIndexSize(final byte[] array, final int off, final i
      *     // perform read...
      * }
      *
-     * @Override
+     * {@code @Override}
      * public void write(char[] cbuf, int off, int len) throws IOException {
      *     ensureOpen();
      *     IOUtils.checkFromIndexSize(cbuf, off, len);
@@ -489,7 +489,7 @@ public static void checkFromIndexSize(final byte[] array, final int off, final i
      *     }
      *     // perform write...
      * }
-     * }
+ *
* * @param array the array against which the range is validated * @param off the starting offset into the array (inclusive) @@ -518,8 +518,8 @@ public static void checkFromIndexSize(final char[] array, final int off, final i * *

Typical usage in {@link Writer#write(String, int, int)} implementations:

* - *
{@code
-     * @Override
+     * 

+     * {@code @Override}
      * public void write(String str, int off, int len) throws IOException {
      *     IOUtils.checkFromIndexSize(str, off, len);
      *     if (len == 0) {
@@ -527,7 +527,7 @@ public static void checkFromIndexSize(final char[] array, final int off, final i
      *     }
      *     // perform write...
      * }
-     * }
+ *
* * @param str the string against which the range is validated * @param off the starting offset into the string (inclusive) @@ -563,14 +563,14 @@ static void checkFromIndexSize(final int off, final int len, final int arrayLeng * *

Typical usage in {@link Appendable#append(CharSequence, int, int)} implementations:

* - *
{@code
-     * @Override
+     * 

+     * {@code @Override}
      * public Appendable append(CharSequence csq, int start, int end) throws IOException {
      *     IOUtils.checkFromToIndex(csq, start, end);
      *     // perform append...
      *     return this;
      * }
-     * }
+ *
* * @param seq the character sequence to validate (may be {@code null}, treated as {@code "null"}) * @param fromIndex the starting index (inclusive) From 41c2a98b4f8d2214051cc35bacd31664cd1ce2a4 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Mon, 29 Sep 2025 11:25:02 +0200 Subject: [PATCH 08/25] fix: remove `@Override` from code snippet There is no way to insert `@Override` in a Javadoc snippet without triggering a warning in either Java 8 or 21. --- src/main/java/org/apache/commons/io/IOUtils.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index 09c48f139d0..3f257692606 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -421,7 +421,6 @@ private static char[] charArray(final int size) { *

Typical usage in {@link InputStream#read(byte[], int, int)} and {@link OutputStream#write(byte[], int, int)} implementations:

* *

-     * {@code @Override}
      * public int read(byte[] b, int off, int len) throws IOException {
      *     IOUtils.checkFromIndexSize(b, off, len);
      *     if (len == 0) {
@@ -431,7 +430,6 @@ private static char[] charArray(final int size) {
      *     // perform read...
      * }
      *
-     * {@code @Override}
      * public void write(byte[] b, int off, int len) throws IOException {
      *     IOUtils.checkFromIndexSize(b, off, len);
      *     if (len == 0) {
@@ -470,7 +468,6 @@ public static void checkFromIndexSize(final byte[] array, final int off, final i
      * 

Typical usage in {@link Reader#read(char[], int, int)} and {@link Writer#write(char[], int, int)} implementations:

* *

-     * {@code @Override}
      * public int read(char[] cbuf, int off, int len) throws IOException {
      *     ensureOpen();
      *     IOUtils.checkFromIndexSize(cbuf, off, len);
@@ -480,7 +477,6 @@ public static void checkFromIndexSize(final byte[] array, final int off, final i
      *     // perform read...
      * }
      *
-     * {@code @Override}
      * public void write(char[] cbuf, int off, int len) throws IOException {
      *     ensureOpen();
      *     IOUtils.checkFromIndexSize(cbuf, off, len);
@@ -519,7 +515,6 @@ public static void checkFromIndexSize(final char[] array, final int off, final i
      * 

Typical usage in {@link Writer#write(String, int, int)} implementations:

* *

-     * {@code @Override}
      * public void write(String str, int off, int len) throws IOException {
      *     IOUtils.checkFromIndexSize(str, off, len);
      *     if (len == 0) {
@@ -564,7 +559,6 @@ static void checkFromIndexSize(final int off, final int len, final int arrayLeng
      * 

Typical usage in {@link Appendable#append(CharSequence, int, int)} implementations:

* *

-     * {@code @Override}
      * public Appendable append(CharSequence csq, int start, int end) throws IOException {
      *     IOUtils.checkFromToIndex(csq, start, end);
      *     // perform append...

From 68c1ba463d3d0d48ddf2e0ea0a06c5153d5052e1 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz" 
Date: Wed, 1 Oct 2025 08:51:01 +0200
Subject: [PATCH 09/25] fix: `ClosedReader` Javadoc and tests

---
 .../apache/commons/io/input/ClosedReader.java | 17 ++++++++----
 .../commons/io/input/ClosedReaderTest.java    | 27 ++++++++++++++-----
 2 files changed, 33 insertions(+), 11 deletions(-)

diff --git a/src/main/java/org/apache/commons/io/input/ClosedReader.java b/src/main/java/org/apache/commons/io/input/ClosedReader.java
index 72220c6978d..b50721c120d 100644
--- a/src/main/java/org/apache/commons/io/input/ClosedReader.java
+++ b/src/main/java/org/apache/commons/io/input/ClosedReader.java
@@ -62,12 +62,19 @@ public void close() throws IOException {
     }
 
     /**
-     * Returns -1 to indicate that the stream is closed.
+     * A no-op read method that always indicates end-of-stream.
      *
-     * @param cbuf ignored
-     * @param off ignored
-     * @param len ignored
-     * @return always -1
+     * 

Behavior:

+ *
    + *
  • If {@code len == 0}, returns {@code 0} immediately (no characters are read).
  • + *
  • Otherwise, always returns {@value IOUtils#EOF} to signal that the stream is closed or at end-of-stream.
  • + *
+ * + * @param cbuf The destination buffer. + * @param off The offset at which to start storing characters. + * @param len The maximum number of characters to read. + * @return {@code 0} if {@code len == 0}; otherwise always {@value IOUtils#EOF}. + * @throws IndexOutOfBoundsException If {@code off < 0}, {@code len < 0}, or {@code off + len > cbuf.length}. */ @Override public int read(final char[] cbuf, final int off, final int len) { diff --git a/src/test/java/org/apache/commons/io/input/ClosedReaderTest.java b/src/test/java/org/apache/commons/io/input/ClosedReaderTest.java index 891049f9ef5..57b87ddfb09 100644 --- a/src/test/java/org/apache/commons/io/input/ClosedReaderTest.java +++ b/src/test/java/org/apache/commons/io/input/ClosedReaderTest.java @@ -18,6 +18,7 @@ import static org.apache.commons.io.IOUtils.EOF; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; import java.io.Reader; @@ -47,24 +48,38 @@ void testReadArray() throws Exception { assertEquals(EOF, reader.read(new char[4096])); assertEquals(EOF, reader.read(new char[1])); assertEquals(0, reader.read(new char[0])); + assertThrows(NullPointerException.class, () -> reader.read((char[]) null)); } } @Test void testReadArrayIndex() throws Exception { try (Reader reader = new ClosedReader()) { - assertEquals(EOF, reader.read(CharBuffer.wrap(new char[4096]))); - assertEquals(EOF, reader.read(CharBuffer.wrap(new char[1]))); - assertEquals(0, reader.read(CharBuffer.wrap(new char[0]))); + final char[] cbuf = new char[4096]; + assertEquals(EOF, reader.read(cbuf, 0, 2048)); + assertEquals(EOF, reader.read(cbuf, 2048, 2048)); + assertEquals(0, reader.read(cbuf, 4096, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(cbuf, -1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(cbuf, 0, 4097)); + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(cbuf, 1, -1)); + + assertEquals(EOF, reader.read(new char[1])); + assertEquals(0, reader.read(new char[0])); + assertThrows(NullPointerException.class, () -> reader.read(null, 0, 0)); } } @Test void testReadCharBuffer() throws Exception { try (Reader reader = new ClosedReader()) { - assertEquals(EOF, reader.read(new char[4096])); - assertEquals(EOF, reader.read(new char[1])); - assertEquals(0, reader.read(new char[0])); + final CharBuffer charBuffer = CharBuffer.wrap(new char[4096]); + assertEquals(EOF, reader.read(charBuffer)); + charBuffer.position(4096); + assertEquals(0, reader.read(charBuffer)); + + assertEquals(EOF, reader.read(CharBuffer.wrap(new char[1]))); + assertEquals(0, reader.read(CharBuffer.wrap(new char[0]))); + assertThrows(NullPointerException.class, () -> reader.read((CharBuffer) null)); } } From 48fe1c34398470fb139ed306c48885c91da9f857 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 09:25:54 +0200 Subject: [PATCH 10/25] fix: revert `BrokenReader` and add tests The tests are improved to ensure that the given exception is thrown **before** parameter validation. --- .../apache/commons/io/input/BrokenReader.java | 30 ++++++++++++++--- .../commons/io/input/BrokenReaderTest.java | 32 ++++++++++++++++--- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/apache/commons/io/input/BrokenReader.java b/src/main/java/org/apache/commons/io/input/BrokenReader.java index 61e9bff47ba..f5d5b3b4e13 100644 --- a/src/main/java/org/apache/commons/io/input/BrokenReader.java +++ b/src/main/java/org/apache/commons/io/input/BrokenReader.java @@ -18,9 +18,9 @@ import java.io.IOException; import java.io.Reader; +import java.nio.CharBuffer; import java.util.function.Supplier; -import org.apache.commons.io.IOUtils; import org.apache.commons.io.function.Erase; /** @@ -115,10 +115,30 @@ public void mark(final int readAheadLimit) throws IOException { */ @Override public int read(final char[] cbuf, final int off, final int len) throws IOException { - IOUtils.checkFromIndexSize(cbuf, off, len); - if (len == 0) { - return 0; - } + throw rethrow(); + } + + /** + * Throws the configured exception. + * + * @param target ignored. + * @return nothing. + * @throws IOException always throws the exception configured in a constructor. + */ + @Override + public int read(CharBuffer target) throws IOException { + throw rethrow(); + } + + /** + * Throws the configured exception. + * + * @param cbuf ignored. + * @return nothing. + * @throws IOException always throws the exception configured in a constructor. + */ + @Override + public int read(char[] cbuf) throws IOException { throw rethrow(); } diff --git a/src/test/java/org/apache/commons/io/input/BrokenReaderTest.java b/src/test/java/org/apache/commons/io/input/BrokenReaderTest.java index aca91077e6b..02c535a9ec3 100644 --- a/src/test/java/org/apache/commons/io/input/BrokenReaderTest.java +++ b/src/test/java/org/apache/commons/io/input/BrokenReaderTest.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.Reader; +import java.nio.CharBuffer; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -45,7 +46,7 @@ void testClose(final Class clazz) throws Exception { final Throwable exception = clazz.newInstance(); @SuppressWarnings("resource") final BrokenReader brokenReader = createBrokenReader(exception); - assertEquals(exception, assertThrows(clazz, () -> brokenReader.close())); + assertEquals(exception, assertThrows(clazz, brokenReader::close)); } @Test @@ -68,7 +69,7 @@ void testRead(final Class clazz) throws Exception { final Throwable exception = clazz.newInstance(); @SuppressWarnings("resource") final BrokenReader brokenReader = createBrokenReader(exception); - assertEquals(exception, assertThrows(clazz, () -> brokenReader.read())); + assertEquals(exception, assertThrows(clazz, brokenReader::read)); } @ParameterizedTest @@ -78,6 +79,8 @@ void testReadCharArray(final Class clazz) throws Exception { @SuppressWarnings("resource") final BrokenReader brokenReader = createBrokenReader(exception); assertEquals(exception, assertThrows(clazz, () -> brokenReader.read(new char[1]))); + // Also throws the exception before checking arguments + assertEquals(exception, assertThrows(clazz, () -> brokenReader.read((char[]) null))); } @ParameterizedTest @@ -86,7 +89,26 @@ void testReadCharArrayIndexed(final Class clazz) throws Exception { final Throwable exception = clazz.newInstance(); @SuppressWarnings("resource") final BrokenReader brokenReader = createBrokenReader(exception); - assertEquals(exception, assertThrows(clazz, () -> brokenReader.read(new char[1], 0, 1))); + final char[] cbuf = new char[1]; + assertEquals(exception, assertThrows(clazz, () -> brokenReader.read(cbuf, 0, 1))); + // Also throws the exception before checking arguments + assertEquals(exception, assertThrows(clazz, () -> brokenReader.read(cbuf, -1, 1))); + assertEquals(exception, assertThrows(clazz, () -> brokenReader.read(cbuf, 0, -1))); + assertEquals(exception, assertThrows(clazz, () -> brokenReader.read(cbuf, 1, 1))); + assertEquals(exception, assertThrows(clazz, () -> brokenReader.read(null, 0, 0))); + + } + + @ParameterizedTest + @MethodSource("org.apache.commons.io.BrokenTestFactories#parameters") + void testReadCharBuffer(final Class clazz) throws Exception { + final Throwable exception = clazz.newInstance(); + @SuppressWarnings("resource") + final BrokenReader brokenReader = createBrokenReader(exception); + final CharBuffer charBuffer = CharBuffer.allocate(1); + assertEquals(exception, assertThrows(clazz, () -> brokenReader.read(charBuffer))); + // Also throws the exception before checking arguments + assertEquals(exception, assertThrows(clazz, () -> brokenReader.read((CharBuffer) null))); } @ParameterizedTest @@ -95,7 +117,7 @@ void testReady(final Class clazz) throws Exception { final Throwable exception = clazz.newInstance(); @SuppressWarnings("resource") final BrokenReader brokenReader = createBrokenReader(exception); - assertEquals(exception, assertThrows(clazz, () -> brokenReader.ready())); + assertEquals(exception, assertThrows(clazz, brokenReader::ready)); } @ParameterizedTest @@ -104,7 +126,7 @@ void testReset(final Class clazz) throws Exception { final Throwable exception = clazz.newInstance(); @SuppressWarnings("resource") final BrokenReader brokenReader = createBrokenReader(exception); - assertEquals(exception, assertThrows(clazz, () -> brokenReader.reset())); + assertEquals(exception, assertThrows(clazz, brokenReader::reset)); } @ParameterizedTest From bbde62705159668acddaaab29163346e06d05119 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 10:05:32 +0200 Subject: [PATCH 11/25] fix: remove `BrokenReader.read(char[]/CharBuffer)` overrides --- .../apache/commons/io/input/BrokenReader.java | 24 ------------------- .../commons/io/input/BrokenReaderTest.java | 4 ---- 2 files changed, 28 deletions(-) diff --git a/src/main/java/org/apache/commons/io/input/BrokenReader.java b/src/main/java/org/apache/commons/io/input/BrokenReader.java index f5d5b3b4e13..2467228b9a9 100644 --- a/src/main/java/org/apache/commons/io/input/BrokenReader.java +++ b/src/main/java/org/apache/commons/io/input/BrokenReader.java @@ -118,30 +118,6 @@ public int read(final char[] cbuf, final int off, final int len) throws IOExcept throw rethrow(); } - /** - * Throws the configured exception. - * - * @param target ignored. - * @return nothing. - * @throws IOException always throws the exception configured in a constructor. - */ - @Override - public int read(CharBuffer target) throws IOException { - throw rethrow(); - } - - /** - * Throws the configured exception. - * - * @param cbuf ignored. - * @return nothing. - * @throws IOException always throws the exception configured in a constructor. - */ - @Override - public int read(char[] cbuf) throws IOException { - throw rethrow(); - } - /** * Throws the configured exception. * diff --git a/src/test/java/org/apache/commons/io/input/BrokenReaderTest.java b/src/test/java/org/apache/commons/io/input/BrokenReaderTest.java index 02c535a9ec3..a47687abf3d 100644 --- a/src/test/java/org/apache/commons/io/input/BrokenReaderTest.java +++ b/src/test/java/org/apache/commons/io/input/BrokenReaderTest.java @@ -79,8 +79,6 @@ void testReadCharArray(final Class clazz) throws Exception { @SuppressWarnings("resource") final BrokenReader brokenReader = createBrokenReader(exception); assertEquals(exception, assertThrows(clazz, () -> brokenReader.read(new char[1]))); - // Also throws the exception before checking arguments - assertEquals(exception, assertThrows(clazz, () -> brokenReader.read((char[]) null))); } @ParameterizedTest @@ -107,8 +105,6 @@ void testReadCharBuffer(final Class clazz) throws Exception { final BrokenReader brokenReader = createBrokenReader(exception); final CharBuffer charBuffer = CharBuffer.allocate(1); assertEquals(exception, assertThrows(clazz, () -> brokenReader.read(charBuffer))); - // Also throws the exception before checking arguments - assertEquals(exception, assertThrows(clazz, () -> brokenReader.read((CharBuffer) null))); } @ParameterizedTest From de2d1e376421622ea3f700e6c59ff90a8e2c143d Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 10:13:51 +0200 Subject: [PATCH 12/25] fix: revert `BrokenWriter` Revert `BrokenWriter` and add test cases to ensure the exception is thrown **before** parameter validation. --- .../org/apache/commons/io/output/BrokenWriter.java | 2 -- .../apache/commons/io/output/BrokenWriterTest.java | 14 +++++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/apache/commons/io/output/BrokenWriter.java b/src/main/java/org/apache/commons/io/output/BrokenWriter.java index 1feac6bb84a..7fe244d2392 100644 --- a/src/main/java/org/apache/commons/io/output/BrokenWriter.java +++ b/src/main/java/org/apache/commons/io/output/BrokenWriter.java @@ -20,7 +20,6 @@ import java.io.Writer; import java.util.function.Supplier; -import org.apache.commons.io.IOUtils; import org.apache.commons.io.function.Erase; /** @@ -122,7 +121,6 @@ private RuntimeException rethrow() { */ @Override public void write(final char[] cbuf, final int off, final int len) throws IOException { - IOUtils.checkFromIndexSize(cbuf, off, len); throw rethrow(); } diff --git a/src/test/java/org/apache/commons/io/output/BrokenWriterTest.java b/src/test/java/org/apache/commons/io/output/BrokenWriterTest.java index f8db50a6729..dffeb7907ae 100644 --- a/src/test/java/org/apache/commons/io/output/BrokenWriterTest.java +++ b/src/test/java/org/apache/commons/io/output/BrokenWriterTest.java @@ -55,6 +55,7 @@ void testAppendCharSequence(final Class clazz) throws Exception { @SuppressWarnings("resource") final BrokenWriter brokenWriter = createBrokenWriter(exception); assertEquals(exception, assertThrows(clazz, () -> brokenWriter.append("01"))); + assertEquals(exception, assertThrows(clazz, () -> brokenWriter.append(null))); } @ParameterizedTest @@ -64,6 +65,7 @@ void testAppendCharSequenceIndexed(final Class clazz) throws Exceptio @SuppressWarnings("resource") final BrokenWriter brokenWriter = createBrokenWriter(exception); assertEquals(exception, assertThrows(clazz, () -> brokenWriter.append("01", 0, 1))); + assertEquals(exception, assertThrows(clazz, () -> brokenWriter.append(null, 0, 4))); } @ParameterizedTest @@ -72,7 +74,7 @@ void testClose(final Class clazz) throws Exception { final Throwable exception = clazz.newInstance(); @SuppressWarnings("resource") final BrokenWriter brokenWriter = createBrokenWriter(exception); - assertEquals(exception, assertThrows(clazz, () -> brokenWriter.close())); + assertEquals(exception, assertThrows(clazz, brokenWriter::close)); } @ParameterizedTest @@ -81,7 +83,7 @@ void testFlush(final Class clazz) throws Exception { final Throwable exception = clazz.newInstance(); @SuppressWarnings("resource") final BrokenWriter brokenWriter = createBrokenWriter(exception); - assertEquals(exception, assertThrows(clazz, () -> brokenWriter.flush())); + assertEquals(exception, assertThrows(clazz, brokenWriter::flush)); } @Test @@ -119,7 +121,13 @@ void testWriteCharArrayIndexed(final Class clazz) throws Exception { final Throwable exception = clazz.newInstance(); @SuppressWarnings("resource") final BrokenWriter brokenWriter = createBrokenWriter(exception); - assertEquals(exception, assertThrows(clazz, () -> brokenWriter.write(new char[1], 0, 1))); + final char[] cbuf = new char[1]; + assertEquals(exception, assertThrows(clazz, () -> brokenWriter.write(cbuf, 0, 1))); + // Verify that the exception is thrown before checking the parameters. + assertEquals(exception, assertThrows(clazz, () -> brokenWriter.write(cbuf, -1, 0))); + assertEquals(exception, assertThrows(clazz, () -> brokenWriter.write(cbuf, 0, -1))); + assertEquals(exception, assertThrows(clazz, () -> brokenWriter.write(cbuf, 0, 2))); + assertEquals(exception, assertThrows(clazz, () -> brokenWriter.write((char[]) null, 0, 0))); } @ParameterizedTest From b0bea24988abd922d640ffacc20abe3b5201cc42 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 10:14:59 +0200 Subject: [PATCH 13/25] fix: Checkstyle failures --- src/main/java/org/apache/commons/io/input/BrokenReader.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/apache/commons/io/input/BrokenReader.java b/src/main/java/org/apache/commons/io/input/BrokenReader.java index 2467228b9a9..8fb510569cf 100644 --- a/src/main/java/org/apache/commons/io/input/BrokenReader.java +++ b/src/main/java/org/apache/commons/io/input/BrokenReader.java @@ -18,7 +18,6 @@ import java.io.IOException; import java.io.Reader; -import java.nio.CharBuffer; import java.util.function.Supplier; import org.apache.commons.io.function.Erase; From 06cb03b158c105427f79c1c39b7a50a5bb57ba8d Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 10:33:13 +0200 Subject: [PATCH 14/25] fix: revert `ClosedWriter` and add tests --- .../java/org/apache/commons/io/output/ClosedWriter.java | 6 ++---- .../org/apache/commons/io/output/ClosedWriterTest.java | 8 +++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/apache/commons/io/output/ClosedWriter.java b/src/main/java/org/apache/commons/io/output/ClosedWriter.java index bc1add898ad..c4e1d93b93c 100644 --- a/src/main/java/org/apache/commons/io/output/ClosedWriter.java +++ b/src/main/java/org/apache/commons/io/output/ClosedWriter.java @@ -19,8 +19,7 @@ import java.io.IOException; import java.io.Writer; - -import org.apache.commons.io.IOUtils; +import java.util.Arrays; /** * Throws an IOException on all attempts to write with {@link #close()} implemented as a noop. @@ -79,7 +78,6 @@ public void flush() throws IOException { */ @Override public void write(final char[] cbuf, final int off, final int len) throws IOException { - IOUtils.checkFromIndexSize(cbuf, off, len); - throw new IOException("write(" + new String(cbuf) + ", " + off + ", " + len + ") failed: stream is closed"); + throw new IOException(String.format("write(%s, %d, %d) failed: stream is closed", Arrays.toString(cbuf), off, len)); } } diff --git a/src/test/java/org/apache/commons/io/output/ClosedWriterTest.java b/src/test/java/org/apache/commons/io/output/ClosedWriterTest.java index 4f57957a885..b9758729f1b 100644 --- a/src/test/java/org/apache/commons/io/output/ClosedWriterTest.java +++ b/src/test/java/org/apache/commons/io/output/ClosedWriterTest.java @@ -43,7 +43,13 @@ void testFlush() throws IOException { @Test void testWrite() throws IOException { try (ClosedWriter cw = new ClosedWriter()) { - assertThrows(IOException.class, () -> cw.write(new char[0], 0, 0)); + final char[] cbuf = new char[1]; + assertThrows(IOException.class, () -> cw.write(new char[0], 0, 1)); + // In writers, testing for closed always comes before argument validation + assertThrows(IOException.class, () -> cw.write(cbuf, -1, 0)); + assertThrows(IOException.class, () -> cw.write(cbuf, 0, -1)); + assertThrows(IOException.class, () -> cw.write(cbuf, 0, 2)); + assertThrows(IOException.class, () -> cw.write((char[]) null, 0, 0)); } } From 3678d1e22600febb067c606171ab77ff26c77241 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 10:50:19 +0200 Subject: [PATCH 15/25] fix: revert `FilterCollectionWriter` changes --- .../org/apache/commons/io/output/FilterCollectionWriter.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java b/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java index 1a13d90eff3..d78f049e635 100644 --- a/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java +++ b/src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java @@ -28,7 +28,6 @@ import org.apache.commons.io.IOExceptionList; import org.apache.commons.io.IOIndexedException; -import org.apache.commons.io.IOUtils; import org.apache.commons.io.function.IOConsumer; /** @@ -129,7 +128,6 @@ public void write(final char[] cbuf) throws IOException { @SuppressWarnings("resource") // no allocation @Override public void write(final char[] cbuf, final int off, final int len) throws IOException { - IOUtils.checkFromIndexSize(cbuf, off, len); forAllWriters(w -> w.write(cbuf, off, len)); } @@ -161,7 +159,6 @@ public void write(final String str) throws IOException { @SuppressWarnings("resource") // no allocation @Override public void write(final String str, final int off, final int len) throws IOException { - IOUtils.checkFromIndexSize(str, off, len); forAllWriters(w -> w.write(str, off, len)); } From d00d5effe0da276f7c0d0caa1b1cef77f6c2103d Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 11:03:24 +0200 Subject: [PATCH 16/25] fix: fix `NullAppendable` Javadoc --- .../apache/commons/io/output/NullAppendable.java | 15 +++++++++++++++ .../commons/io/output/NullAppendableTest.java | 10 ++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/apache/commons/io/output/NullAppendable.java b/src/main/java/org/apache/commons/io/output/NullAppendable.java index fee5dace602..7adbc2d3749 100644 --- a/src/main/java/org/apache/commons/io/output/NullAppendable.java +++ b/src/main/java/org/apache/commons/io/output/NullAppendable.java @@ -51,6 +51,21 @@ public Appendable append(final CharSequence csq) throws IOException { return this; } + /** + * Appends a subsequence of the specified character sequence to this Appendable. + * + * @param csq The character sequence from which a subsequence will be + * appended. + * If {@code csq} is {@code null}, it is treated as if it were + * {@code "null"}. + * @param start The index of the first character in the subsequence. + * @param end The index of the character following the last character in the + * subsequence. + * @return {@code this} instance. + * @throws IndexOutOfBoundsException if {@code start} or {@code end} are negative, {@code end} is + * greater than {@code csq.length()}, or {@code start} is greater + * than {@code end}. + */ @Override public Appendable append(final CharSequence csq, final int start, final int end) throws IOException { IOUtils.checkFromToIndex(csq, start, end); diff --git a/src/test/java/org/apache/commons/io/output/NullAppendableTest.java b/src/test/java/org/apache/commons/io/output/NullAppendableTest.java index 09b1d1bf2ba..aa5ef7137ed 100644 --- a/src/test/java/org/apache/commons/io/output/NullAppendableTest.java +++ b/src/test/java/org/apache/commons/io/output/NullAppendableTest.java @@ -34,10 +34,12 @@ void testNull() throws IOException { appendable.append("A"); appendable.append("A", 0, 1); appendable.append(null, 0, 1); - // `null` is converted to "null" - assertThrows(IndexOutOfBoundsException.class, () -> appendable.append(null, -1, 0)); - assertThrows(IndexOutOfBoundsException.class, () -> appendable.append(null, 1, 0)); - assertThrows(IndexOutOfBoundsException.class, () -> appendable.append(null, 0, 5)); + // Check argument validation + final CharSequence csq = "ABCDE"; + assertThrows(IndexOutOfBoundsException.class, () -> appendable.append(csq, -1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> appendable.append(csq, 0, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> appendable.append(csq, 1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> appendable.append(csq, 0, 6)); } } From 1e558f5e05cb2c67fc3a4f7404a73ddae02c6a81 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 11:09:22 +0200 Subject: [PATCH 17/25] fix: fix `NullOutputStream` Javadoc and enhance tests --- .../apache/commons/io/output/NullOutputStream.java | 13 +++++++++---- .../commons/io/output/NullOutputStreamTest.java | 11 ++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/apache/commons/io/output/NullOutputStream.java b/src/main/java/org/apache/commons/io/output/NullOutputStream.java index 682d0f8e768..0334a4ea4b8 100644 --- a/src/main/java/org/apache/commons/io/output/NullOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/NullOutputStream.java @@ -69,11 +69,16 @@ public void write(final byte[] b) throws IOException { } /** - * Does nothing. + * No-op operation. * - * @param b This method ignores this parameter. - * @param off This method ignores this parameter. - * @param len This method ignores this parameter. + *

Validates the arguments but does not write the data.

+ * + * @param b The byte array to write from, not {@code null}. + * @param off The offset to start at. + * @param len The number of bytes to write. + * @throws NullPointerException If {@code b} is {@code null}. + * @throws IndexOutOfBoundsException If {@code off} or {@code len} are negative, {@code off + len} is greater than + * {@code b.length}. */ @Override public void write(final byte[] b, final int off, final int len) { diff --git a/src/test/java/org/apache/commons/io/output/NullOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/NullOutputStreamTest.java index bdecc0e0a68..097566fc6e2 100644 --- a/src/test/java/org/apache/commons/io/output/NullOutputStreamTest.java +++ b/src/test/java/org/apache/commons/io/output/NullOutputStreamTest.java @@ -16,6 +16,8 @@ */ package org.apache.commons.io.output; +import static org.junit.jupiter.api.Assertions.assertThrows; + import java.io.IOException; import org.junit.jupiter.api.Test; @@ -34,11 +36,18 @@ private void process(final NullOutputStream nos) throws IOException { nos.close(); nos.write("allowed".getBytes()); nos.write(255); + // Test arguments validation + final byte[] b = new byte[1]; + assertThrows(IndexOutOfBoundsException.class, () -> nos.write(b, -1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> nos.write(b, 0, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> nos.write(b, 0, 2)); + assertThrows(NullPointerException.class, () -> nos.write(null, 0, 0)); } @Test + @SuppressWarnings("deprecation") void testNewInstance() throws IOException { - try (NullOutputStream nos = NullOutputStream.INSTANCE) { + try (NullOutputStream nos = new NullOutputStream()) { process(nos); } } From 6585383d23a7cc02acfb977125b16e00965d5458 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 11:37:35 +0200 Subject: [PATCH 18/25] fix: fix `NullWriter` Javadoc and enhance tests --- .../commons/io/output/NullAppendable.java | 4 +- .../apache/commons/io/output/NullWriter.java | 47 +++++---- .../commons/io/output/NullWriterTest.java | 96 +++++++++++++++++-- 3 files changed, 121 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/apache/commons/io/output/NullAppendable.java b/src/main/java/org/apache/commons/io/output/NullAppendable.java index 7adbc2d3749..c5ea2cfcbeb 100644 --- a/src/main/java/org/apache/commons/io/output/NullAppendable.java +++ b/src/main/java/org/apache/commons/io/output/NullAppendable.java @@ -52,7 +52,7 @@ public Appendable append(final CharSequence csq) throws IOException { } /** - * Appends a subsequence of the specified character sequence to this Appendable. + * Does nothing except argument validation, like writing to {@code /dev/null}. * * @param csq The character sequence from which a subsequence will be * appended. @@ -62,7 +62,7 @@ public Appendable append(final CharSequence csq) throws IOException { * @param end The index of the character following the last character in the * subsequence. * @return {@code this} instance. - * @throws IndexOutOfBoundsException if {@code start} or {@code end} are negative, {@code end} is + * @throws IndexOutOfBoundsException If {@code start} or {@code end} are negative, {@code end} is * greater than {@code csq.length()}, or {@code start} is greater * than {@code end}. */ diff --git a/src/main/java/org/apache/commons/io/output/NullWriter.java b/src/main/java/org/apache/commons/io/output/NullWriter.java index 9741b819766..0ef9a09cdbf 100644 --- a/src/main/java/org/apache/commons/io/output/NullWriter.java +++ b/src/main/java/org/apache/commons/io/output/NullWriter.java @@ -79,12 +79,19 @@ public Writer append(final CharSequence csq) { } /** - * Does nothing, like writing to {@code /dev/null}. + * Does nothing except argument validation, like writing to {@code /dev/null}. * - * @param csq The character sequence to write. - * @param start The index of the first character to write. - * @param end The index of the first character to write (exclusive). - * @return this writer. + * @param csq The character sequence from which a subsequence will be + * appended. + * If {@code csq} is {@code null}, it is treated as if it were + * {@code "null"}. + * @param start The index of the first character in the subsequence. + * @param end The index of the character following the last character in the + * subsequence. + * @return {@code this} instance. + * @throws IndexOutOfBoundsException If {@code start} or {@code end} are negative, {@code end} is + * greater than {@code csq.length()}, or {@code start} is greater + * than {@code end}. * @since 2.0 */ @Override @@ -106,9 +113,10 @@ public void flush() { } /** - * Does nothing, like writing to {@code /dev/null}. + * Does nothing except argument validation, like writing to {@code /dev/null}. * - * @param chr The characters to write + * @param chr The characters to write, not {@code null}. + * @throws NullPointerException if {@code chr} is {@code null}. */ @Override public void write(final char[] chr) { @@ -117,15 +125,17 @@ public void write(final char[] chr) { } /** - * Does nothing, like writing to {@code /dev/null}. + * Does nothing except argument validation, like writing to {@code /dev/null}. * - * @param chr The characters to write. - * @param st The start offset. - * @param end The number of characters to write. + * @param cbuf The characters to write, not {@code null}. + * @param off The start offset. + * @param len The number of characters to write. + * @throws NullPointerException if {@code chr} is {@code null}. + * @throws IndexOutOfBoundsException If ({@code off} or {@code len} are negative, or {@code off + len} is greater than {@code cbuf.length}. */ @Override - public void write(final char[] chr, final int st, final int end) { - IOUtils.checkFromIndexSize(chr, st, end); + public void write(final char[] cbuf, final int off, final int len) { + IOUtils.checkFromIndexSize(cbuf, off, len); //to /dev/null } @@ -140,9 +150,10 @@ public void write(final int b) { } /** - * Does nothing, like writing to {@code /dev/null}. + * Does nothing except argument validation, like writing to {@code /dev/null}. * - * @param str The string to write. + * @param str The string to write, not {@code null}. + * @throws NullPointerException if {@code str} is {@code null}. */ @Override public void write(final String str) { @@ -151,11 +162,13 @@ public void write(final String str) { } /** - * Does nothing, like writing to {@code /dev/null}. + * Does nothing except argument validation, like writing to {@code /dev/null}. * - * @param str The string to write. + * @param str The string to write, not {@code null}. * @param off The start offset. * @param len The number of characters to write. + * @throws NullPointerException If {@code str} is {@code null}. + * @throws IndexOutOfBoundsException If ({@code off} or {@code len} are negative, or {@code off + len} is greater than {@code str.length()}. */ @Override public void write(final String str, final int off, final int len) { diff --git a/src/test/java/org/apache/commons/io/output/NullWriterTest.java b/src/test/java/org/apache/commons/io/output/NullWriterTest.java index f1b603a3776..424d076ee21 100644 --- a/src/test/java/org/apache/commons/io/output/NullWriterTest.java +++ b/src/test/java/org/apache/commons/io/output/NullWriterTest.java @@ -16,6 +16,9 @@ */ package org.apache.commons.io.output; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.junit.jupiter.api.Test; /** @@ -24,17 +27,96 @@ */ class NullWriterTest { + private static final String TEST_STRING = "ABC"; + private static final char[] TEST_CHARS = TEST_STRING.toCharArray(); + + @Test + void testAppendChar() { + try (NullWriter writer = NullWriter.INSTANCE) { + assertSame(writer, writer.append('X')); + } + } + + @Test + void testAppendCharSequence() { + try (NullWriter writer = NullWriter.INSTANCE) { + assertSame(writer, writer.append(TEST_STRING)); + assertSame(writer, writer.append(null)); + } + } + @Test - void testNull() { - final char[] chars = { 'A', 'B', 'C' }; + void testAppendCharSequenceWithRange() { + try (NullWriter writer = NullWriter.INSTANCE) { + assertSame(writer, writer.append(TEST_STRING, 1, 2)); + assertSame(writer, writer.append(null, 0, 4)); + // Test argument validation + assertThrows(IndexOutOfBoundsException.class, () -> writer.append(TEST_STRING, -1, 2)); + assertThrows(IndexOutOfBoundsException.class, () -> writer.append(TEST_STRING, 1, 5)); + assertThrows(IndexOutOfBoundsException.class, () -> writer.append(TEST_STRING, 2, 1)); + } + } + + @Test + void testCloseNoOp() { + final NullWriter writer = NullWriter.INSTANCE; + writer.close(); + writer.write(TEST_CHARS); + } + + @Test + void testFlush() { try (NullWriter writer = NullWriter.INSTANCE) { - writer.write(1); - writer.write(chars); - writer.write(chars, 1, 1); - writer.write("some string"); - writer.write("some string", 2, 2); writer.flush(); } } + @Test + void testWriteCharArray() { + try (NullWriter writer = NullWriter.INSTANCE) { + writer.write(TEST_CHARS); + // Test argument validation + assertThrows(NullPointerException.class, () -> writer.write((char[]) null)); + } + } + + @Test + void testWriteCharArrayWithOffset() { + try (NullWriter writer = NullWriter.INSTANCE) { + writer.write(TEST_CHARS, 1, 2); + // Test argument validation + assertThrows(IndexOutOfBoundsException.class, () -> writer.write(TEST_CHARS, -1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> writer.write(TEST_CHARS, 0, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> writer.write(TEST_CHARS, 0, 4)); + assertThrows(NullPointerException.class, () -> writer.write((char[]) null, 0, 0)); + } + } + + @Test + void testWriteInt() { + try (NullWriter writer = NullWriter.INSTANCE) { + writer.write(42); + } + } + + @Test + void testWriteString() { + try (NullWriter writer = NullWriter.INSTANCE) { + writer.write(TEST_STRING); + // Test argument validation + assertThrows(NullPointerException.class, () -> writer.write((String) null)); + } + } + + @Test + void testWriteStringWithOffset() { + try (NullWriter writer = NullWriter.INSTANCE) { + writer.write(TEST_STRING, 1, 1); + // Test argument validation + assertThrows(IndexOutOfBoundsException.class, () -> writer.write(TEST_STRING, -1, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> writer.write(TEST_STRING, 0, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> writer.write(TEST_STRING, 0, 4)); + assertThrows(NullPointerException.class, () -> writer.write((String) null, 0, 0)); + } + } } From 14a250629ba13693c802a5a47592b1c634e1fb2e Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 22:19:11 +0200 Subject: [PATCH 19/25] fix: `NullInputStream` Javadoc --- .../org/apache/commons/io/input/NullInputStream.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/apache/commons/io/input/NullInputStream.java b/src/main/java/org/apache/commons/io/input/NullInputStream.java index 32b1206ff2f..5fa9b817b5c 100644 --- a/src/main/java/org/apache/commons/io/input/NullInputStream.java +++ b/src/main/java/org/apache/commons/io/input/NullInputStream.java @@ -243,9 +243,9 @@ protected int processByte() { * This implementation leaves the byte array unchanged. *

* - * @param bytes The byte array - * @param offset The offset to start at. - * @param length The number of bytes. + * @param bytes The byte array, never {@code null}. + * @param offset The offset to start at, always non-negative. + * @param length The number of bytes to process, always non-negative and at most {@code bytes.length - offset}. */ protected void processBytes(final byte[] bytes, final int offset, final int length) { // do nothing - overridable by subclass @@ -274,6 +274,7 @@ public int read() throws IOException { * * @param bytes The byte array to read into * @return The number of bytes read or {@code -1} if the end of file has been reached and {@code throwEofException} is set to {@code false}. + * @throws NullPointerException if the byte array is {@code null}. * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}. * @throws IOException if trying to read past the end of file. */ @@ -289,6 +290,8 @@ public int read(final byte[] bytes) throws IOException { * @param offset The offset to start reading bytes into. * @param length The number of bytes to read. * @return The number of bytes read or {@code -1} if the end of file has been reached and {@code throwEofException} is set to {@code false}. + * @throws NullPointerException if the byte array is {@code null}. + * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if {@code offset + length} is greater than {@code bytes.length}. * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}. * @throws IOException if trying to read past the end of file. */ From 1632fb2edcd4dd3f425b0e84e1111f0cfa251fe9 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 22:22:42 +0200 Subject: [PATCH 20/25] fix: `ClosedOutputStream` Javadoc --- .../org/apache/commons/io/output/ClosedOutputStream.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/apache/commons/io/output/ClosedOutputStream.java b/src/main/java/org/apache/commons/io/output/ClosedOutputStream.java index faa342f40f6..c7216ce6f90 100644 --- a/src/main/java/org/apache/commons/io/output/ClosedOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ClosedOutputStream.java @@ -68,9 +68,11 @@ public void flush() throws IOException { /** * Throws an {@link IOException} to indicate that the stream is closed. * - * @param b ignored. - * @param off ignored. - * @param len ignored. + * @param b Byte array, never {@code null}. + * @param off The start offset in the byte array. + * @param len The number of bytes to write. + * @throws NullPointerException if the byte array is {@code null}. + * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code b.length}. * @throws IOException always thrown. */ @Override From 94760281b40b4a442c7d784713525e94bb0ed694 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 22:26:12 +0200 Subject: [PATCH 21/25] fix: `StringBuilderWriter` behavior and Javadoc --- .../org/apache/commons/io/output/StringBuilderWriter.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apache/commons/io/output/StringBuilderWriter.java b/src/main/java/org/apache/commons/io/output/StringBuilderWriter.java index 836e8bc3753..bdb757cf684 100644 --- a/src/main/java/org/apache/commons/io/output/StringBuilderWriter.java +++ b/src/main/java/org/apache/commons/io/output/StringBuilderWriter.java @@ -148,11 +148,14 @@ public String toString() { * @param value The value to write. * @param offset The index of the first character. * @param length The number of characters to write. + * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if {@code offset + length} is greater than {@code value.length}. */ @Override public void write(final char[] value, final int offset, final int length) { - IOUtils.checkFromIndexSize(value, offset, length); - builder.append(value, offset, length); + if (value != null) { + IOUtils.checkFromIndexSize(value, offset, length); + builder.append(value, offset, length); + } } /** From b80971821c227b09de4e2aec5a2adcee7b4b4a3b Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 22:27:41 +0200 Subject: [PATCH 22/25] fix: restore `NullWriter` comment --- src/main/java/org/apache/commons/io/output/NullWriter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/apache/commons/io/output/NullWriter.java b/src/main/java/org/apache/commons/io/output/NullWriter.java index 0ef9a09cdbf..635ffb044a6 100644 --- a/src/main/java/org/apache/commons/io/output/NullWriter.java +++ b/src/main/java/org/apache/commons/io/output/NullWriter.java @@ -97,6 +97,7 @@ public Writer append(final CharSequence csq) { @Override public Writer append(final CharSequence csq, final int start, final int end) { IOUtils.checkFromToIndex(csq, start, end); + //to /dev/null return this; } From a70be15bbb8c493a95144542f5a8fd8788e5b8cd Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 22:32:30 +0200 Subject: [PATCH 23/25] fix: `checkFromToIndex` logical or --- src/main/java/org/apache/commons/io/IOUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index 3f257692606..a882d4d87cb 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -578,7 +578,7 @@ public static void checkFromToIndex(final CharSequence seq, final int fromIndex, } static void checkFromToIndex(final int fromIndex, final int toIndex, final int length) { - if (fromIndex < 0 | toIndex < fromIndex | length < toIndex) { + if (fromIndex < 0 || toIndex < fromIndex || length < toIndex) { throw new IndexOutOfBoundsException(String.format("Range [%s, %s) out of bounds for length %s", fromIndex, toIndex, length)); } } From 8d520f72c3698440e2275b860a991a41757ad2fb Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 22:50:40 +0200 Subject: [PATCH 24/25] fix: document `IndexOutOfBoundsException` --- .../java/org/apache/commons/io/input/BOMInputStream.java | 8 +++++++- .../java/org/apache/commons/io/input/BoundedReader.java | 2 ++ .../org/apache/commons/io/input/CharSequenceReader.java | 5 +++-- src/main/java/org/apache/commons/io/input/NullReader.java | 2 ++ .../org/apache/commons/io/input/ReaderInputStream.java | 5 ++++- .../java/org/apache/commons/io/input/SequenceReader.java | 5 ----- .../org/apache/commons/io/output/AppendableWriter.java | 6 ++++++ .../org/apache/commons/io/output/ChunkedOutputStream.java | 3 +++ .../java/org/apache/commons/io/output/ChunkedWriter.java | 3 +++ .../org/apache/commons/io/output/WriterOutputStream.java | 3 +++ .../org/apache/commons/io/output/XmlStreamWriter.java | 3 +++ 11 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/apache/commons/io/input/BOMInputStream.java b/src/main/java/org/apache/commons/io/input/BOMInputStream.java index 4cb347f1dc5..acfe8d9ddbb 100644 --- a/src/main/java/org/apache/commons/io/input/BOMInputStream.java +++ b/src/main/java/org/apache/commons/io/input/BOMInputStream.java @@ -419,8 +419,10 @@ public int read() throws IOException { * Invokes the delegate's {@code read(byte[])} method, detecting and optionally skipping BOM. * * @param buf - * the buffer to read the bytes into + * the buffer to read the bytes into, never {@code null} * @return the number of bytes read (excluding BOM) or -1 if the end of stream + * @throws NullPointerException + * if the buffer is {@code null} * @throws IOException * if an I/O error occurs */ @@ -439,6 +441,10 @@ public int read(final byte[] buf) throws IOException { * @param len * The number of bytes to read (excluding BOM) * @return the number of bytes read or -1 if the end of stream + * @throws NullPointerException + * if the buffer is {@code null} + * @throws IndexOutOfBoundsException + * if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code buf.length} * @throws IOException * if an I/O error occurs */ diff --git a/src/main/java/org/apache/commons/io/input/BoundedReader.java b/src/main/java/org/apache/commons/io/input/BoundedReader.java index c64759045f2..5c1c255c0fe 100644 --- a/src/main/java/org/apache/commons/io/input/BoundedReader.java +++ b/src/main/java/org/apache/commons/io/input/BoundedReader.java @@ -118,6 +118,8 @@ public int read() throws IOException { * @param off The offset * @param len The number of chars to read * @return the number of chars read + * @throws NullPointerException if the buffer is {@code null}. + * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code cbuf.length}. * @throws IOException If an I/O error occurs while calling the underlying reader's read method * @see Reader#read(char[], int, int) */ diff --git a/src/main/java/org/apache/commons/io/input/CharSequenceReader.java b/src/main/java/org/apache/commons/io/input/CharSequenceReader.java index bc3defe7ddc..bb2be3692c9 100644 --- a/src/main/java/org/apache/commons/io/input/CharSequenceReader.java +++ b/src/main/java/org/apache/commons/io/input/CharSequenceReader.java @@ -205,8 +205,9 @@ public int read() { * @param array The array to store the characters in * @param offset The starting position in the array to store * @param length The maximum number of characters to read - * @return The number of characters read or -1 if there are - * no more + * @return The number of characters read or -1 if there are no more + * @throws NullPointerException if the array is {@code null}. + * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if {@code offset + length} is greater than {@code array.length}. */ @Override public int read(final char[] array, final int offset, final int length) { diff --git a/src/main/java/org/apache/commons/io/input/NullReader.java b/src/main/java/org/apache/commons/io/input/NullReader.java index 2b7fdb47175..28f3d37eb21 100644 --- a/src/main/java/org/apache/commons/io/input/NullReader.java +++ b/src/main/java/org/apache/commons/io/input/NullReader.java @@ -272,6 +272,8 @@ public int read(final char[] chars) throws IOException { * @return The number of characters read or {@code -1} * if the end of file has been reached and * {@code throwEofException} is set to {@code false}. + * @throws NullPointerException if the array is {@code null}. + * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if {@code offset + length} is greater than {@code chars.length}. * @throws EOFException if the end of file is reached and * {@code throwEofException} is set to {@code true}. * @throws IOException if trying to read past the end of 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 b2b6a82befb..4d22d28f891 100644 --- a/src/main/java/org/apache/commons/io/input/ReaderInputStream.java +++ b/src/main/java/org/apache/commons/io/input/ReaderInputStream.java @@ -435,8 +435,9 @@ public int read() throws IOException { /** * Reads the specified number of bytes into an array. * - * @param b the byte array to read into + * @param b the byte array to read into, must not be {@code null} * @return the number of bytes read or {@code -1} if the end of the stream has been reached + * @throws NullPointerException if the byte array is {@code null}. * @throws IOException if an I/O error occurs. */ @Override @@ -451,6 +452,8 @@ public int read(final byte[] b) throws IOException { * @param off the offset to start reading bytes into * @param len the number of bytes to read * @return the number of bytes read or {@code -1} if the end of the stream has been reached + * @throws NullPointerException if the byte array is {@code null}. + * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code array.length}. * @throws IOException if an I/O error occurs. */ @Override diff --git a/src/main/java/org/apache/commons/io/input/SequenceReader.java b/src/main/java/org/apache/commons/io/input/SequenceReader.java index b10bfcefcc9..daeb71faa92 100644 --- a/src/main/java/org/apache/commons/io/input/SequenceReader.java +++ b/src/main/java/org/apache/commons/io/input/SequenceReader.java @@ -108,11 +108,6 @@ public int read() throws IOException { return c; } - /* - * (non-Javadoc) - * - * @see Reader#read() - */ @Override public int read(final char[] cbuf, int off, int len) throws IOException { IOUtils.checkFromIndexSize(cbuf, off, len); diff --git a/src/main/java/org/apache/commons/io/output/AppendableWriter.java b/src/main/java/org/apache/commons/io/output/AppendableWriter.java index a6f4ff53fa9..b1ace0b908c 100644 --- a/src/main/java/org/apache/commons/io/output/AppendableWriter.java +++ b/src/main/java/org/apache/commons/io/output/AppendableWriter.java @@ -79,6 +79,8 @@ public Writer append(final CharSequence csq) throws IOException { * @param start the index of the first character in the subsequence * @param end the index of the character following the last character in the subsequence * @return this writer + * @throws IndexOutOfBoundsException If {@code start} or {@code end} are negative, {@code start} is greater than + * {@code end}, or {@code end} is greater than {@code csq.length()}. * @throws IOException If an I/O error occurs. */ @Override @@ -122,6 +124,8 @@ public T getAppendable() { * @param cbuf an array with the characters to write. * @param off offset from which to start writing characters. * @param len number of characters to write. + * @throws NullPointerException if the array is {@code null}. + * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code cbuf.length}. * @throws IOException If an I/O error occurs. */ @Override @@ -149,6 +153,8 @@ public void write(final int c) throws IOException { * @param str a string. * @param off offset from which to start writing characters. * @param len number of characters to write. + * @throws NullPointerException if the string is {@code null}. + * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code str.length()}. * @throws IOException If an I/O error occurs. */ @Override diff --git a/src/main/java/org/apache/commons/io/output/ChunkedOutputStream.java b/src/main/java/org/apache/commons/io/output/ChunkedOutputStream.java index adecd946a3e..2588264e172 100644 --- a/src/main/java/org/apache/commons/io/output/ChunkedOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ChunkedOutputStream.java @@ -168,6 +168,9 @@ int getChunkSize() { * @param data the data to write. * @param srcOffset the offset. * @param length the length of data to write. + * @throws NullPointerException if the data is {@code null}. + * @throws IndexOutOfBoundsException if {@code srcOffset} or {@code length} are negative, + * or if {@code srcOffset + length} is greater than {@code data.length}. * @throws IOException if an I/O error occurs. */ @Override diff --git a/src/main/java/org/apache/commons/io/output/ChunkedWriter.java b/src/main/java/org/apache/commons/io/output/ChunkedWriter.java index f18553fb776..ed22625b667 100644 --- a/src/main/java/org/apache/commons/io/output/ChunkedWriter.java +++ b/src/main/java/org/apache/commons/io/output/ChunkedWriter.java @@ -70,6 +70,9 @@ public ChunkedWriter(final Writer writer, final int chunkSize) { * @param data The data. * @param srcOffset the offset. * @param length the number of bytes to write. + * @throws NullPointerException if the data is {@code null}. + * @throws IndexOutOfBoundsException if {@code srcOffset} or {@code length} are negative, + * or if {@code srcOffset + length} is greater than {@code data.length}. * @throws IOException If an I/O error occurs. */ @Override 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 c753ccdce87..d17318f79b6 100644 --- a/src/main/java/org/apache/commons/io/output/WriterOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/WriterOutputStream.java @@ -431,6 +431,7 @@ private void processInput(final boolean endOfInput) throws IOException { * Writes bytes from the specified byte array to the stream. * * @param b the byte array containing the bytes to write. + * @throws NullPointerException if the byte array is {@code null}. * @throws IOException if an I/O error occurs. */ @Override @@ -444,6 +445,8 @@ public void write(final byte[] b) throws IOException { * @param b the byte array containing the bytes to write. * @param off the start offset in the byte array. * @param len the number of bytes to write. + * @throws NullPointerException if the byte array is {@code null}. + * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code b.length}. * @throws IOException if an I/O error occurs. */ @Override diff --git a/src/main/java/org/apache/commons/io/output/XmlStreamWriter.java b/src/main/java/org/apache/commons/io/output/XmlStreamWriter.java index 73630a4bca2..873df0238d8 100644 --- a/src/main/java/org/apache/commons/io/output/XmlStreamWriter.java +++ b/src/main/java/org/apache/commons/io/output/XmlStreamWriter.java @@ -302,6 +302,9 @@ public String getEncoding() { * @param cbuf the buffer to write the characters from. * @param off The start offset. * @param len The number of characters to write. + * @throws NullPointerException if the buffer is {@code null}. + * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, + * or if {@code off + len} is greater than {@code cbuf.length}. * @throws IOException if an error occurs detecting the encoding. */ @Override From c4e4f3b5b09d59d0078a7d8b3f3772dea728e06f Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 1 Oct 2025 22:59:46 +0200 Subject: [PATCH 25/25] fix: `ClosedInputStream` and `ThresholdingOutputStream` javadoc --- .../org/apache/commons/io/input/ClosedInputStream.java | 10 ++++++---- .../commons/io/output/ThresholdingOutputStream.java | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/apache/commons/io/input/ClosedInputStream.java b/src/main/java/org/apache/commons/io/input/ClosedInputStream.java index 5ddb9ea6c9d..11bc7be6e19 100644 --- a/src/main/java/org/apache/commons/io/input/ClosedInputStream.java +++ b/src/main/java/org/apache/commons/io/input/ClosedInputStream.java @@ -79,10 +79,12 @@ public int read() { /** * Returns {@code -1} to indicate that the stream is closed. * - * @param b ignored. - * @param off ignored. - * @param len ignored. - * @return always -1. + * @param b The buffer to read bytes into. + * @param off The start offset. + * @param len The number of bytes to read. + * @return If len is zero, then {@code 0}; otherwise {@code -1}. + * @throws NullPointerException if the byte array is {@code null}. + * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code b.length}. */ @Override public int read(final byte[] b, final int off, final int len) throws IOException { diff --git a/src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java b/src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java index 2efe401910a..742572b2ebc 100644 --- a/src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ThresholdingOutputStream.java @@ -228,6 +228,7 @@ protected void thresholdReached() throws IOException { * Writes {@code b.length} bytes from the specified byte array to this output stream. * * @param b The array of bytes to be written. + * @throws NullPointerException if the byte array is {@code null}. * @throws IOException if an error occurs. */ @SuppressWarnings("resource") // the underlying stream is managed by a subclass. @@ -245,6 +246,8 @@ public void write(final byte[] b) throws IOException { * @param b The byte array from which the data will be written. * @param off The start offset in the byte array. * @param len The number of bytes to write. + * @throws NullPointerException if the byte array is {@code null}. + * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code b.length}. * @throws IOException if an error occurs. */ @SuppressWarnings("resource") // the underlying stream is managed by a subclass.