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. diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index e37eddafb3f..a882d4d87cb 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -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. + * + *

The range is valid if all of the following hold:

+ * + * + *

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

+ * + *

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

+ * + *

+     * 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...
+     * }
+     * 
+ * + * @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. + * + *

The range is valid if all of the following hold:

+ * + * + *

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

+ * + *

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

+ * + *

+     * 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...
+     * }
+     * 
+ * + * @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. + * + *

The range is valid if all of the following hold:

+ * + * + *

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

+ * + *

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

+ * + *

+     * 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) { + 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, %The sub-sequence is valid if all of the following hold:

+ * + * + *

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

+ * + *

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

+ * + *

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

+ * + *

+     * 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) + * @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. *