You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by pk...@apache.org on 2025/09/28 13:53:21 UTC
(commons-io) 01/01: Backport `Objects.checkFromIndexSize` to `IOUtils`
This is an automated email from the ASF dual-hosted git repository.
pkarwasz pushed a commit to branch fix/input-stream-read
in repository https://gitbox.apache.org/repos/asf/commons-io.git
commit bb3226a54ba0eb9d261589e989f0c1e422bf65dc
Author: Piotr P. Karwasz <pk...@apache.org>
AuthorDate: Sun Sep 28 15:51:54 2025 +0200
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.
---
src/main/java/org/apache/commons/io/IOUtils.java | 30 +++++++++++++
.../java/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 e37eddafb..eb611f805 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.
+ *
+ * <p>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.</p>
+ *
+ * <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 arrayLength >= 0}</li>
+ * <li>{@code off + len <= arrayLength}</li>
+ * </ul>
+ *
+ * <p>If the range is invalid, this method throws an
+ * {@link IndexOutOfBoundsException} with a descriptive message.</p>
+ *
+ * @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, %<s + %s) out of bounds for length %s", off, len, arrayLength));
+ }
+ }
+
/**
* Clears any state.
* <ul>
diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java
index bae8f59e3..d062ae61e 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<Arguments> 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<Arguments> 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));