Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bb3226a
Backport `Objects.checkFromIndexSize` to `IOUtils`
ppkarwasz Sep 28, 2025
659b90b
Add changelog entry
ppkarwasz Sep 28, 2025
5552d9b
fix: apply `IOUtils.checkIndexFromSize` to all streams
ppkarwasz Sep 28, 2025
051a86a
fix: failing tests
ppkarwasz Sep 29, 2025
cbfb55c
fix: inline static import
ppkarwasz Sep 29, 2025
8b45ad9
fix: add usage examples to Javadoc
ppkarwasz Sep 29, 2025
2220753
fix: Javadoc for JDK 8 and 11
ppkarwasz Sep 29, 2025
41c2a98
fix: remove `@Override` from code snippet
ppkarwasz Sep 29, 2025
68c1ba4
fix: `ClosedReader` Javadoc and tests
ppkarwasz Oct 1, 2025
48fe1c3
fix: revert `BrokenReader` and add tests
ppkarwasz Oct 1, 2025
bbde627
fix: remove `BrokenReader.read(char[]/CharBuffer)` overrides
ppkarwasz Oct 1, 2025
de2d1e3
fix: revert `BrokenWriter`
ppkarwasz Oct 1, 2025
b0bea24
fix: Checkstyle failures
ppkarwasz Oct 1, 2025
06cb03b
fix: revert `ClosedWriter` and add tests
ppkarwasz Oct 1, 2025
3678d1e
fix: revert `FilterCollectionWriter` changes
ppkarwasz Oct 1, 2025
d00d5ef
fix: fix `NullAppendable` Javadoc
ppkarwasz Oct 1, 2025
1e558f5
fix: fix `NullOutputStream` Javadoc and enhance tests
ppkarwasz Oct 1, 2025
6585383
fix: fix `NullWriter` Javadoc and enhance tests
ppkarwasz Oct 1, 2025
14a2506
fix: `NullInputStream` Javadoc
ppkarwasz Oct 1, 2025
1632fb2
fix: `ClosedOutputStream` Javadoc
ppkarwasz Oct 1, 2025
9476028
fix: `StringBuilderWriter` behavior and Javadoc
ppkarwasz Oct 1, 2025
b809718
fix: restore `NullWriter` comment
ppkarwasz Oct 1, 2025
a70be15
fix: `checkFromToIndex` logical or
ppkarwasz Oct 1, 2025
8d520f7
fix: document `IndexOutOfBoundsException`
ppkarwasz Oct 1, 2025
c4e4f3b
fix: `ClosedInputStream` and `ThresholdingOutputStream` javadoc
ppkarwasz Oct 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ The <action> type attribute can be add,update,fix,remove.
<action dev="ggregory" type="add" due-to="Gary Gregory">Add IOIterable.asIterable().</action>
<action dev="pkarwasz" type="add" due-to="Piotr P. Karwasz">Add NIO channel support to `AbstractStreamBuilder`.</action>
<action dev="pkarwasz" type="add" due-to="Piotr P. Karwasz">Add CloseShieldChannel to close-shielded NIO Channels #786.</action>
<action dev="pkarwasz" type="add" due-to="Piotr P. Karwasz">Added IOUtils.checkFromIndexSize as a Java 8 backport of Objects.checkFromIndexSize #790.</action>
<!-- UPDATE -->
<action type="update" dev="ggregory" due-to="Gary Gregory, Dependabot">Bump org.apache.commons:commons-parent from 85 to 88 #774, #783.</action>
<action type="update" dev="ggregory" due-to="Gary Gregory">[test] Bump commons-codec:commons-codec from 1.18.0 to 1.19.0.</action>
Expand Down
177 changes: 177 additions & 0 deletions src/main/java/org/apache/commons/io/IOUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,183 @@ private static char[] charArray(final int size) {
return new char[size];
}

/**
* Validates that the sub-range {@code [off, off + len)} is within the bounds of the given array.
*
* <p>The range is valid if all of the following hold:</p>
* <ul>
* <li>{@code off >= 0}</li>
* <li>{@code len >= 0}</li>
* <li>{@code off + len <= array.length}</li>
* </ul>
*
* <p>If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message.</p>
*
* <p>Typical usage in {@link InputStream#read(byte[], int, int)} and {@link OutputStream#write(byte[], int, int)} implementations:</p>
*
* <pre><code>
* public int read(byte[] b, int off, int len) throws IOException {
* IOUtils.checkFromIndexSize(b, off, len);
* if (len == 0) {
* return 0;
* }
* ensureOpen();
* // perform read...
* }
*
* public void write(byte[] b, int off, int len) throws IOException {
* IOUtils.checkFromIndexSize(b, off, len);
* if (len == 0) {
* return;
* }
* ensureOpen();
* // perform write...
* }
* </code></pre>
*
* @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) {
checkFromIndexSize(off, len, Objects.requireNonNull(array, "byte array").length);
}

/**
* Validates that the sub-range {@code [off, off + len)} is within the bounds of the given array.
*
* <p>The range is valid if all of the following hold:</p>
* <ul>
* <li>{@code off >= 0}</li>
* <li>{@code len >= 0}</li>
* <li>{@code off + len <= array.length}</li>
* </ul>
*
* <p>If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message.</p>
*
* <p>Typical usage in {@link Reader#read(char[], int, int)} and {@link Writer#write(char[], int, int)} implementations:</p>
*
* <pre><code>
* public int read(char[] cbuf, int off, int len) throws IOException {
* ensureOpen();
* IOUtils.checkFromIndexSize(cbuf, off, len);
* if (len == 0) {
* return 0;
* }
* // perform read...
* }
*
* public void write(char[] cbuf, int off, int len) throws IOException {
* ensureOpen();
* IOUtils.checkFromIndexSize(cbuf, off, len);
* if (len == 0) {
* return;
* }
* // perform write...
* }
* </code></pre>
*
* @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) {
checkFromIndexSize(off, len, Objects.requireNonNull(array, "char array").length);
}

/**
* Validates that the sub-range {@code [off, off + len)} is within the bounds of the given string.
*
* <p>The range is valid if all of the following hold:</p>
* <ul>
* <li>{@code off >= 0}</li>
* <li>{@code len >= 0}</li>
* <li>{@code off + len <= str.length()}</li>
* </ul>
*
* <p>If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message.</p>
*
* <p>Typical usage in {@link Writer#write(String, int, int)} implementations:</p>
*
* <pre><code>
* public void write(String str, int off, int len) throws IOException {
* IOUtils.checkFromIndexSize(str, off, len);
* if (len == 0) {
* return;
* }
* // perform write...
* }
* </code></pre>
*
* @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) {
checkFromIndexSize(off, len, Objects.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, %<s + %s) out of bounds for length %s", off, len, arrayLength));
}
}

/**
* Validates that the sub-sequence {@code [fromIndex, toIndex)} is within the bounds of the given {@link CharSequence}.
*
* <p>The sub-sequence is valid if all of the following hold:</p>
* <ul>
* <li>{@code fromIndex >= 0}</li>
* <li>{@code fromIndex <= toIndex}</li>
* <li>{@code toIndex <= seq.length()}</li>
* </ul>
*
* <p>If {@code seq} is {@code null}, it is treated as the literal string {@code "null"} (length {@code 4}).</p>
*
* <p>If the range is invalid, throws {@link IndexOutOfBoundsException} with a descriptive message.</p>
*
* <p>Typical usage in {@link Appendable#append(CharSequence, int, int)} implementations:</p>
*
* <pre><code>
* public Appendable append(CharSequence csq, int start, int end) throws IOException {
* IOUtils.checkFromToIndex(csq, start, end);
* // perform append...
* return this;
* }
* </code></pre>
*
* @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, final int fromIndex, final int toIndex) {
checkFromToIndex(fromIndex, toIndex, seq != null ? seq.length() : 4);
}

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.
* <ul>
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/org/apache/commons/io/input/BOMInputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -439,11 +441,19 @@ 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
*/
@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) {
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/apache/commons/io/input/BoundedReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -116,11 +118,14 @@ 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)
*/
@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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -204,19 +205,19 @@ 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) {
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);
Expand Down
14 changes: 10 additions & 4 deletions src/main/java/org/apache/commons/io/input/ClosedInputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,19 @@ 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 {
IOUtils.checkFromIndexSize(b, off, len);
if (len == 0) {
return 0;
}
return EOF;
}

Expand Down
21 changes: 16 additions & 5 deletions src/main/java/org/apache/commons/io/input/ClosedReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,26 @@ 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
* <p>Behavior:</p>
* <ul>
* <li>If {@code len == 0}, returns {@code 0} immediately (no characters are read).</li>
* <li>Otherwise, always returns {@value IOUtils#EOF} to signal that the stream is closed or at end-of-stream.</li>
* </ul>
*
* @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) {
IOUtils.checkFromIndexSize(cbuf, off, len);
if (len == 0) {
return 0;
}
return EOF;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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();
Expand Down
Loading
Loading