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));