8146458: Improve exception reporting for Objects.checkIndex/checkFromToIndex/checkFromIndexSize
Reviewed-by: jrose, smarks
--- a/jdk/src/java.base/share/classes/java/lang/ArrayIndexOutOfBoundsException.java Wed Apr 13 15:05:49 2016 +0200
+++ b/jdk/src/java.base/share/classes/java/lang/ArrayIndexOutOfBoundsException.java Wed Apr 13 15:05:50 2016 +0200
@@ -64,20 +64,4 @@
public ArrayIndexOutOfBoundsException(int index) {
super("Array index out of range: " + index);
}
-
- /**
- * Constructs a new {@code ArrayIndexOutOfBoundsException} class with
- * arguments indicating two out of bound values.
- *
- * <p>The out of bound values are included in this exception's detail
- * message. The exact presentation format of the detail message is
- * unspecified.
- *
- * @param a the first out of bound value.
- * @param b the second out of bound value.
- * @since 9
- */
- public ArrayIndexOutOfBoundsException(int a, int b) {
- super("Array indexed access out of bounds: " + a + ", " + b);
- }
}
--- a/jdk/src/java.base/share/classes/java/lang/IndexOutOfBoundsException.java Wed Apr 13 15:05:49 2016 +0200
+++ b/jdk/src/java.base/share/classes/java/lang/IndexOutOfBoundsException.java Wed Apr 13 15:05:50 2016 +0200
@@ -67,21 +67,4 @@
public IndexOutOfBoundsException(int index) {
super("Index out of range: " + index);
}
-
- /**
- * Constructs an {@code IndexOutOfBoundsException} with arguments indicating
- * two out of bound values.
- *
- * <p>The out of bound values are included in this exception's detail
- * message. The exact presentation format of the detail message is
- * unspecified.
- *
- * @param a the first out of bound value
- * @param b the second out of bound value
- * @since 9
- */
- public IndexOutOfBoundsException(int a, int b) {
- super("Indexed access out of bounds: " + a + ", " + b);
- }
-
}
--- a/jdk/src/java.base/share/classes/java/lang/StringIndexOutOfBoundsException.java Wed Apr 13 15:05:49 2016 +0200
+++ b/jdk/src/java.base/share/classes/java/lang/StringIndexOutOfBoundsException.java Wed Apr 13 15:05:50 2016 +0200
@@ -67,20 +67,4 @@
public StringIndexOutOfBoundsException(int index) {
super("String index out of range: " + index);
}
-
- /**
- * Constructs a new {@code StringIndexOutOfBoundsException} class with
- * arguments indicating two out of bound values.
- *
- * <p>The out of bound values are included in this exception's detail
- * message. The exact presentation format of the detail message is
- * unspecified.
- *
- * @param a the first out of bound value.
- * @param b the second out of bound value.
- * @since 9
- */
- public StringIndexOutOfBoundsException(int a, int b) {
- super("String indexed access out of bounds: " + a + ", " + b);
- }
}
--- a/jdk/src/java.base/share/classes/java/lang/invoke/VarHandle.java Wed Apr 13 15:05:49 2016 +0200
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/VarHandle.java Wed Apr 13 15:05:50 2016 +0200
@@ -34,7 +34,9 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.function.BiFunction;
+import java.util.function.Function;
import static java.lang.invoke.MethodHandleStatics.UNSAFE;
import static java.lang.invoke.MethodHandleStatics.newInternalError;
@@ -1377,12 +1379,14 @@
UNSAFE.fullFence();
}
- static final BiFunction<Integer, Integer, ArrayIndexOutOfBoundsException> AIOOBE_SUPPLIER = new BiFunction<>() {
- @Override
- public ArrayIndexOutOfBoundsException apply(Integer a, Integer b) {
- return new ArrayIndexOutOfBoundsException(a, b);
- }
- };
+ static final BiFunction<String, List<Integer>, ArrayIndexOutOfBoundsException>
+ AIOOBE_SUPPLIER = Objects.outOfBoundsExceptionFormatter(
+ new Function<String, ArrayIndexOutOfBoundsException>() {
+ @Override
+ public ArrayIndexOutOfBoundsException apply(String s) {
+ return new ArrayIndexOutOfBoundsException(s);
+ }
+ });
private static final long VFORM_OFFSET;
--- a/jdk/src/java.base/share/classes/java/util/Objects.java Wed Apr 13 15:05:49 2016 +0200
+++ b/jdk/src/java.base/share/classes/java/util/Objects.java Wed Apr 13 15:05:50 2016 +0200
@@ -25,26 +25,28 @@
package java.util;
+import jdk.internal.HotSpotIntrinsicCandidate;
+
import java.util.function.BiFunction;
+import java.util.function.Function;
import java.util.function.Supplier;
-import jdk.internal.HotSpotIntrinsicCandidate;
/**
* This class consists of {@code static} utility methods for operating
* on objects, or checking certain conditions before operation. These utilities
* include {@code null}-safe or {@code null}-tolerant methods for computing the
* hash code of an object, returning a string for an object, comparing two
- * objects, and checking if indexes or sub-range values are out of bounds.
+ * objects, and checking if indexes or sub-range values are out-of-bounds.
*
* @apiNote
* Static methods such as {@link Objects#checkIndex},
* {@link Objects#checkFromToIndex}, and {@link Objects#checkFromIndexSize} are
* provided for the convenience of checking if values corresponding to indexes
- * and sub-ranges are out of bounds.
+ * and sub-ranges are out-of-bounds.
* Variations of these static methods support customization of the runtime
* exception, and corresponding exception detail message, that is thrown when
- * values are out of bounds. Such methods accept a functional interface
- * argument, instances of {@code BiFunction}, that maps out of bound values to a
+ * values are out-of-bounds. Such methods accept a functional interface
+ * argument, instances of {@code BiFunction}, that maps out-of-bound values to a
* runtime exception. Care should be taken when using such methods in
* combination with an argument that is a lambda expression, method reference or
* class that capture values. In such cases the cost of capture, related to
@@ -347,29 +349,176 @@
}
/**
- * Maps out of bounds values to a runtime exception.
+ * Maps out-of-bounds values to a runtime exception.
*
- * @param a the first out of bound value
- * @param b the second out of bound value
- * @param oobe the exception mapping function that when applied with out of
- * bounds arguments returns a runtime exception. If {@code null}
- * then, it is as if an exception mapping function was supplied that
- * returns {@link IndexOutOfBoundsException} for any given arguments.
+ * @param checkKind the kind of bounds check, whose name may correspond
+ * to the name of one of the range check methods, checkIndex,
+ * checkFromToIndex, checkFromIndexSize
+ * @param args the out-of-bounds arguments that failed the range check.
+ * If the checkKind corresponds a the name of a range check method
+ * then the bounds arguments are those that can be passed in order
+ * to the method.
+ * @param oobef the exception formatter that when applied with a checkKind
+ * and a list out-of-bounds arguments returns a runtime exception.
+ * If {@code null} then, it is as if an exception formatter was
+ * supplied that returns {@link IndexOutOfBoundsException} for any
+ * given arguments.
* @return the runtime exception
*/
private static RuntimeException outOfBounds(
- int a, int b, BiFunction<Integer, Integer, ? extends RuntimeException> oobe) {
- RuntimeException e = oobe == null
- ? null : oobe.apply(a, b);
+ BiFunction<String, List<Integer>, ? extends RuntimeException> oobef,
+ String checkKind,
+ Integer... args) {
+ List<Integer> largs = List.of(args);
+ RuntimeException e = oobef == null
+ ? null : oobef.apply(checkKind, largs);
return e == null
- ? new IndexOutOfBoundsException(a, b) : e;
+ ? new IndexOutOfBoundsException(outOfBoundsMessage(checkKind, largs)) : e;
+ }
+
+ // Specific out-of-bounds exception producing methods that avoid
+ // the varargs-based code in the critical methods there by reducing their
+ // the byte code size, and therefore less likely to peturb inlining
+
+ private static RuntimeException outOfBoundsCheckIndex(
+ BiFunction<String, List<Integer>, ? extends RuntimeException> oobe,
+ int index, int length) {
+ return outOfBounds(oobe, "checkIndex", index, length);
+ }
+
+ private static RuntimeException outOfBoundsCheckFromToIndex(
+ BiFunction<String, List<Integer>, ? extends RuntimeException> oobe,
+ int fromIndex, int toIndex, int length) {
+ return outOfBounds(oobe, "checkFromToIndex", fromIndex, toIndex, length);
+ }
+
+ private static RuntimeException outOfBoundsCheckFromIndexSize(
+ BiFunction<String, List<Integer>, ? extends RuntimeException> oobe,
+ int fromIndex, int size, int length) {
+ return outOfBounds(oobe, "checkFromIndexSize", fromIndex, size, length);
+ }
+
+ /**
+ * Returns an out-of-bounds exception formatter from an given exception
+ * factory. The exception formatter is a function that formats an
+ * out-of-bounds message from its arguments and applies that message to the
+ * given exception factory to produce and relay an exception.
+ *
+ * <p>The exception formatter accepts two arguments: a {@code String}
+ * describing the out-of-bounds range check that failed, referred to as the
+ * <em>check kind</em>; and a {@code List<Integer>} containing the
+ * out-of-bound integer values that failed the check. The list of
+ * out-of-bound values is not modified.
+ *
+ * <p>Three check kinds are supported {@code checkIndex},
+ * {@code checkFromToIndex} and {@code checkFromIndexSize} corresponding
+ * respectively to the specified application of an exception formatter as an
+ * argument to the out-of-bounds range check methods
+ * {@link #checkIndex(int, int, BiFunction) checkIndex},
+ * {@link #checkFromToIndex(int, int, int, BiFunction) checkFromToIndex}, and
+ * {@link #checkFromIndexSize(int, int, int, BiFunction) checkFromIndexSize}.
+ * Thus a supported check kind corresponds to a method name and the
+ * out-of-bound integer values correspond to method argument values, in
+ * order, preceding the exception formatter argument (similar in many
+ * respects to the form of arguments required for a reflective invocation of
+ * such a range check method).
+ *
+ * <p>Formatter arguments conforming to such supported check kinds will
+ * produce specific exception messages describing failed out-of-bounds
+ * checks. Otherwise, more generic exception messages will be produced in
+ * any of the following cases: the check kind is supported but fewer
+ * or more out-of-bounds values are supplied, the check kind is not
+ * supported, the check kind is {@code null}, or the list of out-of-bound
+ * values is {@code null}.
+ *
+ * @apiNote
+ * This method produces an out-of-bounds exception formatter that can be
+ * passed as an argument to any of the supported out-of-bounds range check
+ * methods declared by {@code Objects}. For example, a formatter producing
+ * an {@code ArrayIndexOutOfBoundsException} may be produced and stored on a
+ * {@code static final} field as follows:
+ * <pre>{@code
+ * static final
+ * BiFunction<String, List<Integer>, ArrayIndexOutOfBoundsException> AIOOBEF =
+ * outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new);
+ * }</pre>
+ * The formatter instance {@code AIOOBEF} may be passed as an argument to an
+ * out-of-bounds range check method, such as checking if an {@code index}
+ * is within the bounds of a {@code limit}:
+ * <pre>{@code
+ * checkIndex(index, limit, AIOOBEF);
+ * }</pre>
+ * If the bounds check fails then the range check method will throw an
+ * {@code ArrayIndexOutOfBoundsException} with an appropriate exception
+ * message that is a produced from {@code AIOOBEF} as follows:
+ * <pre>{@code
+ * AIOOBEF.apply("checkIndex", List.of(index, limit));
+ * }</pre>
+ *
+ * @param f the exception factory, that produces an exception from a message
+ * where the message is produced and formatted by the returned
+ * exception formatter. If this factory is stateless and side-effect
+ * free then so is the returned formatter.
+ * Exceptions thrown by the factory are relayed to the caller
+ * of the returned formatter.
+ * @param <X> the type of runtime exception to be returned by the given
+ * exception factory and relayed by the exception formatter
+ * @return the out-of-bounds exception formatter
+ */
+ public static <X extends RuntimeException>
+ BiFunction<String, List<Integer>, X> outOfBoundsExceptionFormatter(Function<String, X> f) {
+ // Use anonymous class to avoid bootstrap issues if this method is
+ // used early in startup
+ return new BiFunction<String, List<Integer>, X>() {
+ @Override
+ public X apply(String checkKind, List<Integer> args) {
+ return f.apply(outOfBoundsMessage(checkKind, args));
+ }
+ };
+ }
+
+ private static String outOfBoundsMessage(String checkKind, List<Integer> args) {
+ if (checkKind == null && args == null) {
+ return String.format("Range check failed");
+ } else if (checkKind == null) {
+ return String.format("Range check failed: %s", args);
+ } else if (args == null) {
+ return String.format("Range check failed: %s", checkKind);
+ }
+
+ int argSize = 0;
+ switch (checkKind) {
+ case "checkIndex":
+ argSize = 2;
+ break;
+ case "checkFromToIndex":
+ case "checkFromIndexSize":
+ argSize = 3;
+ break;
+ default:
+ }
+
+ // Switch to default if fewer or more arguments than required are supplied
+ switch ((args.size() != argSize) ? "" : checkKind) {
+ case "checkIndex":
+ return String.format("Index %d out-of-bounds for length %d",
+ args.get(0), args.get(1));
+ case "checkFromToIndex":
+ return String.format("Range [%d, %d) out-of-bounds for length %d",
+ args.get(0), args.get(1), args.get(2));
+ case "checkFromIndexSize":
+ return String.format("Range [%d, %<d + %d) out-of-bounds for length %d",
+ args.get(0), args.get(1), args.get(2));
+ default:
+ return String.format("Range check failed: %s %s", checkKind, args);
+ }
}
/**
* Checks if the {@code index} is within the bounds of the range from
* {@code 0} (inclusive) to {@code length} (exclusive).
*
- * <p>The {@code index} is defined to be out of bounds if any of the
+ * <p>The {@code index} is defined to be out-of-bounds if any of the
* following inequalities is true:
* <ul>
* <li>{@code index < 0}</li>
@@ -377,14 +526,20 @@
* <li>{@code length < 0}, which is implied from the former inequalities</li>
* </ul>
*
+ * <p>This method behaves as if {@link #checkIndex(int, int, BiFunction)}
+ * was called with same out-of-bounds arguments and an exception formatter
+ * argument produced from an invocation of
+ * {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} (though it may
+ * be more efficient).
+ *
* @param index the index
* @param length the upper-bound (exclusive) of the range
* @return {@code index} if it is within bounds of the range
- * @throws IndexOutOfBoundsException if the {@code index} is out of bounds
+ * @throws IndexOutOfBoundsException if the {@code index} is out-of-bounds
* @since 9
*/
public static
- int checkIndex(int index, int length) throws IndexOutOfBoundsException {
+ int checkIndex(int index, int length) {
return checkIndex(index, length, null);
}
@@ -392,7 +547,7 @@
* Checks if the {@code index} is within the bounds of the range from
* {@code 0} (inclusive) to {@code length} (exclusive).
*
- * <p>The {@code index} is defined to be out of bounds if any of the
+ * <p>The {@code index} is defined to be out-of-bounds if any of the
* following inequalities is true:
* <ul>
* <li>{@code index < 0}</li>
@@ -400,40 +555,42 @@
* <li>{@code length < 0}, which is implied from the former inequalities</li>
* </ul>
*
- * <p>If the {@code index} is out of bounds, then a runtime exception is
- * thrown that is the result of applying the arguments {@code index} and
- * {@code length} to the given exception mapping function.
+ * <p>If the {@code index} is out-of-bounds, then a runtime exception is
+ * thrown that is the result of applying the following arguments to the
+ * exception formatter: the name of this method, {@code checkIndex};
+ * and an unmodifiable list integers whose values are, in order, the
+ * out-of-bounds arguments {@code index} and {@code length}.
*
- * @param <T> the type of runtime exception to throw if the arguments are
- * out of bounds
+ * @param <X> the type of runtime exception to throw if the arguments are
+ * out-of-bounds
* @param index the index
* @param length the upper-bound (exclusive) of the range
- * @param oobe the exception mapping function that when applied with out
- * of bounds arguments returns a runtime exception. If {@code null}
- * or returns {@code null} then, it is as if an exception mapping
- * function was supplied that returns
- * {@link IndexOutOfBoundsException} for any given arguments.
- * Exceptions thrown by the function are relayed to the caller.
+ * @param oobef the exception formatter that when applied with this
+ * method name and out-of-bounds arguments returns a runtime
+ * exception. If {@code null} or returns {@code null} then, it is as
+ * if an exception formatter produced from an invocation of
+ * {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} is used
+ * instead (though it may be more efficient).
+ * Exceptions thrown by the formatter are relayed to the caller.
* @return {@code index} if it is within bounds of the range
- * @throws T if the {@code index} is out of bounds, then a runtime exception
- * is thrown that is the result of applying the out of bounds
- * arguments to the exception mapping function.
- * @throws IndexOutOfBoundsException if the {@code index} is out of bounds
- * and the exception mapping function is {@code null}
+ * @throws X if the {@code index} is out-of-bounds and the exception
+ * formatter is non-{@code null}
+ * @throws IndexOutOfBoundsException if the {@code index} is out-of-bounds
+ * and the exception formatter is {@code null}
* @since 9
*
* @implNote
- * This method is made intrinsic in optimizing compilers to guide
- * them to perform unsigned comparisons of the index and length
- * when it is known the length is a non-negative value (such as
- * that of an array length or from the upper bound of a loop)
+ * This method is made intrinsic in optimizing compilers to guide them to
+ * perform unsigned comparisons of the index and length when it is known the
+ * length is a non-negative value (such as that of an array length or from
+ * the upper bound of a loop)
*/
@HotSpotIntrinsicCandidate
- public static <T extends RuntimeException>
+ public static <X extends RuntimeException>
int checkIndex(int index, int length,
- BiFunction<Integer, Integer, T> oobe) throws T, IndexOutOfBoundsException {
+ BiFunction<String, List<Integer>, X> oobef) {
if (index < 0 || index >= length)
- throw outOfBounds(index, length, oobe);
+ throw outOfBoundsCheckIndex(oobef, index, length);
return index;
}
@@ -442,7 +599,7 @@
* {@code toIndex} (exclusive) is within the bounds of range from {@code 0}
* (inclusive) to {@code length} (exclusive).
*
- * <p>The sub-range is defined to be out of bounds if any of the following
+ * <p>The sub-range is defined to be out-of-bounds if any of the following
* inequalities is true:
* <ul>
* <li>{@code fromIndex < 0}</li>
@@ -451,15 +608,21 @@
* <li>{@code length < 0}, which is implied from the former inequalities</li>
* </ul>
*
+ * <p>This method behaves as if {@link #checkFromToIndex(int, int, int, BiFunction)}
+ * was called with same out-of-bounds arguments and an exception formatter
+ * argument produced from an invocation of
+ * {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} (though it may
+ * be more efficient).
+ *
* @param fromIndex the lower-bound (inclusive) of the sub-range
* @param toIndex the upper-bound (exclusive) of the sub-range
* @param length the upper-bound (exclusive) the range
* @return {@code fromIndex} if the sub-range within bounds of the range
- * @throws IndexOutOfBoundsException if the sub-range is out of bounds
+ * @throws IndexOutOfBoundsException if the sub-range is out-of-bounds
* @since 9
*/
public static
- int checkFromToIndex(int fromIndex, int toIndex, int length) throws IndexOutOfBoundsException {
+ int checkFromToIndex(int fromIndex, int toIndex, int length) {
return checkFromToIndex(fromIndex, toIndex, length, null);
}
@@ -468,7 +631,7 @@
* {@code toIndex} (exclusive) is within the bounds of range from {@code 0}
* (inclusive) to {@code length} (exclusive).
*
- * <p>The sub-range is defined to be out of bounds if any of the following
+ * <p>The sub-range is defined to be out-of-bounds if any of the following
* inequalities is true:
* <ul>
* <li>{@code fromIndex < 0}</li>
@@ -477,34 +640,36 @@
* <li>{@code length < 0}, which is implied from the former inequalities</li>
* </ul>
*
- * <p>If the sub-range is out of bounds, then a runtime exception is thrown
- * that is the result of applying the arguments {@code fromIndex} and
- * {@code toIndex} to the given exception mapping function.
+ * <p>If the sub-range is out-of-bounds, then a runtime exception is
+ * thrown that is the result of applying the following arguments to the
+ * exception formatter: the name of this method, {@code checkFromToIndex};
+ * and an unmodifiable list integers whose values are, in order, the
+ * out-of-bounds arguments {@code fromIndex}, {@code toIndex}, and {@code length}.
*
- * @param <T> the type of runtime exception to throw if the arguments are
- * out of bounds
+ * @param <X> the type of runtime exception to throw if the arguments are
+ * out-of-bounds
* @param fromIndex the lower-bound (inclusive) of the sub-range
* @param toIndex the upper-bound (exclusive) of the sub-range
* @param length the upper-bound (exclusive) the range
- * @param oobe the exception mapping function that when applied with out
- * of bounds arguments returns a runtime exception. If {@code null}
- * or returns {@code null} then, it is as if an exception mapping
- * function was supplied that returns
- * {@link IndexOutOfBoundsException} for any given arguments.
- * Exceptions thrown by the function are relayed to the caller.
+ * @param oobef the exception formatter that when applied with this
+ * method name and out-of-bounds arguments returns a runtime
+ * exception. If {@code null} or returns {@code null} then, it is as
+ * if an exception formatter produced from an invocation of
+ * {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} is used
+ * instead (though it may be more efficient).
+ * Exceptions thrown by the formatter are relayed to the caller.
* @return {@code fromIndex} if the sub-range within bounds of the range
- * @throws T if the sub-range is out of bounds, then a runtime exception is
- * thrown that is the result of applying the out of bounds arguments
- * to the exception mapping function.
- * @throws IndexOutOfBoundsException if the sub-range is out of bounds and
- * the exception mapping function is {@code null}
+ * @throws X if the sub-range is out-of-bounds and the exception factory
+ * function is non-{@code null}
+ * @throws IndexOutOfBoundsException if the sub-range is out-of-bounds and
+ * the exception factory function is {@code null}
* @since 9
*/
- public static <T extends RuntimeException>
+ public static <X extends RuntimeException>
int checkFromToIndex(int fromIndex, int toIndex, int length,
- BiFunction<Integer, Integer, T> oobe) throws T, IndexOutOfBoundsException {
+ BiFunction<String, List<Integer>, X> oobef) {
if (fromIndex < 0 || fromIndex > toIndex || toIndex > length)
- throw outOfBounds(fromIndex, toIndex, oobe);
+ throw outOfBoundsCheckFromToIndex(oobef, fromIndex, toIndex, length);
return fromIndex;
}
@@ -513,7 +678,7 @@
* {@code fromIndex + size} (exclusive) is within the bounds of range from
* {@code 0} (inclusive) to {@code length} (exclusive).
*
- * <p>The sub-range is defined to be out of bounds if any of the following
+ * <p>The sub-range is defined to be out-of-bounds if any of the following
* inequalities is true:
* <ul>
* <li>{@code fromIndex < 0}</li>
@@ -522,15 +687,21 @@
* <li>{@code length < 0}, which is implied from the former inequalities</li>
* </ul>
*
+ * <p>This method behaves as if {@link #checkFromIndexSize(int, int, int, BiFunction)}
+ * was called with same out-of-bounds arguments and an exception formatter
+ * argument produced from an invocation of
+ * {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} (though it may
+ * be more efficient).
+ *
* @param fromIndex the lower-bound (inclusive) of the sub-interval
* @param size the size of the sub-range
* @param length the upper-bound (exclusive) of the range
* @return {@code fromIndex} if the sub-range within bounds of the range
- * @throws IndexOutOfBoundsException if the sub-range is out of bounds
+ * @throws IndexOutOfBoundsException if the sub-range is out-of-bounds
* @since 9
*/
public static
- int checkFromIndexSize(int fromIndex, int size, int length) throws IndexOutOfBoundsException {
+ int checkFromIndexSize(int fromIndex, int size, int length) {
return checkFromIndexSize(fromIndex, size, length, null);
}
@@ -539,7 +710,7 @@
* {@code fromIndex + size} (exclusive) is within the bounds of range from
* {@code 0} (inclusive) to {@code length} (exclusive).
*
- * <p>The sub-range is defined to be out of bounds if any of the following
+ * <p>The sub-range is defined to be out-of-bounds if any of the following
* inequalities is true:
* <ul>
* <li>{@code fromIndex < 0}</li>
@@ -548,34 +719,37 @@
* <li>{@code length < 0}, which is implied from the former inequalities</li>
* </ul>
*
- * <p>If the sub-range is out of bounds then, a runtime exception is thrown
- * that is the result of applying the arguments {@code fromIndex} and
- * {@code size} to the given exception mapping function.
+ * <p>If the sub-range is out-of-bounds, then a runtime exception is
+ * thrown that is the result of applying the following arguments to the
+ * exception formatter: the name of this method, {@code checkFromIndexSize};
+ * and an unmodifiable list integers whose values are, in order, the
+ * out-of-bounds arguments {@code fromIndex}, {@code size}, and
+ * {@code length}.
*
- * @param <T> the type of runtime exception to throw if the arguments are
- * out of bounds
+ * @param <X> the type of runtime exception to throw if the arguments are
+ * out-of-bounds
* @param fromIndex the lower-bound (inclusive) of the sub-interval
* @param size the size of the sub-range
* @param length the upper-bound (exclusive) of the range
- * @param oobe the exception mapping function that when applied with out
- * of bounds arguments returns a runtime exception. If {@code null}
- * or returns {@code null} then, it is as if an exception mapping
- * function was supplied that returns
- * {@link IndexOutOfBoundsException} for any given arguments.
- * Exceptions thrown by the function are relayed to the caller.
+ * @param oobef the exception formatter that when applied with this
+ * method name and out-of-bounds arguments returns a runtime
+ * exception. If {@code null} or returns {@code null} then, it is as
+ * if an exception formatter produced from an invocation of
+ * {@code outOfBoundsExceptionFormatter(IndexOutOfBounds::new)} is used
+ * instead (though it may be more efficient).
+ * Exceptions thrown by the formatter are relayed to the caller.
* @return {@code fromIndex} if the sub-range within bounds of the range
- * @throws T if the sub-range is out of bounds, then a runtime exception is
- * thrown that is the result of applying the out of bounds arguments
- * to the exception mapping function.
- * @throws IndexOutOfBoundsException if the sub-range is out of bounds and
- * the exception mapping function is {@code null}
+ * @throws X if the sub-range is out-of-bounds and the exception factory
+ * function is non-{@code null}
+ * @throws IndexOutOfBoundsException if the sub-range is out-of-bounds and
+ * the exception factory function is {@code null}
* @since 9
*/
- public static <T extends RuntimeException>
+ public static <X extends RuntimeException>
int checkFromIndexSize(int fromIndex, int size, int length,
- BiFunction<Integer, Integer, T> oobe) throws T, IndexOutOfBoundsException {
+ BiFunction<String, List<Integer>, X> oobef) {
if ((length | fromIndex | size) < 0 || size > length - fromIndex)
- throw outOfBounds(fromIndex, size, oobe);
+ throw outOfBoundsCheckFromIndexSize(oobef, fromIndex, size, length);
return fromIndex;
}
}
--- a/jdk/test/java/util/Objects/CheckIndex.java Wed Apr 13 15:05:49 2016 +0200
+++ b/jdk/test/java/util/Objects/CheckIndex.java Wed Apr 13 15:05:50 2016 +0200
@@ -43,22 +43,30 @@
public class CheckIndex {
static class AssertingOutOfBoundsException extends RuntimeException {
+ public AssertingOutOfBoundsException(String message) {
+ super(message);
+ }
}
- static BiFunction<Integer, Integer, AssertingOutOfBoundsException> assertingOutOfBounds(
- int expFromIndex, int expToIndexOrSizeOrLength) {
- return (fromIndex, toIndexOrSizeorLength) -> {
- assertEquals(fromIndex, Integer.valueOf(expFromIndex));
- assertEquals(toIndexOrSizeorLength, Integer.valueOf(expToIndexOrSizeOrLength));
- return new AssertingOutOfBoundsException();
+ static BiFunction<String, List<Integer>, AssertingOutOfBoundsException> assertingOutOfBounds(
+ String message, String expCheckKind, Integer... expArgs) {
+ return (checkKind, args) -> {
+ assertEquals(checkKind, expCheckKind);
+ assertEquals(args, List.of(expArgs));
+ try {
+ args.clear();
+ fail("Out of bounds List<Integer> argument should be unmodifiable");
+ } catch (Exception e) {
+ }
+ return new AssertingOutOfBoundsException(message);
};
}
- static BiFunction<Integer, Integer, AssertingOutOfBoundsException> assertingOutOfBoundsReturnNull(
- int expFromIndex, int expToIndexOrSizeOrLength) {
- return (fromIndex, toIndexOrSizeorLength) -> {
- assertEquals(fromIndex, Integer.valueOf(expFromIndex));
- assertEquals(toIndexOrSizeorLength, Integer.valueOf(expToIndexOrSizeOrLength));
+ static BiFunction<String, List<Integer>, AssertingOutOfBoundsException> assertingOutOfBoundsReturnNull(
+ String expCheckKind, Integer... expArgs) {
+ return (checkKind, args) -> {
+ assertEquals(checkKind, expCheckKind);
+ assertEquals(args, List.of(expArgs));
return null;
};
}
@@ -85,7 +93,12 @@
@Test(dataProvider = "checkIndexProvider")
public void testCheckIndex(int index, int length, boolean withinBounds) {
- BiConsumer<Class<? extends RuntimeException>, IntSupplier> check = (ec, s) -> {
+ String expectedMessage = withinBounds
+ ? null
+ : Objects.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new).
+ apply("checkIndex", List.of(index, length)).getMessage();
+
+ BiConsumer<Class<? extends RuntimeException>, IntSupplier> checker = (ec, s) -> {
try {
int rIndex = s.getAsInt();
if (!withinBounds)
@@ -98,17 +111,27 @@
if (withinBounds)
fail(String.format(
"Index %d is within bounds of [0, %d), but was reported to be out of bounds", index, length));
+ else
+ assertEquals(e.getMessage(), expectedMessage);
}
};
- check.accept(AssertingOutOfBoundsException.class,
- () -> Objects.checkIndex(index, length, assertingOutOfBounds(index, length)));
- check.accept(IndexOutOfBoundsException.class,
- () -> Objects.checkIndex(index, length, assertingOutOfBoundsReturnNull(index, length)));
- check.accept(IndexOutOfBoundsException.class,
+ checker.accept(AssertingOutOfBoundsException.class,
+ () -> Objects.checkIndex(index, length,
+ assertingOutOfBounds(expectedMessage, "checkIndex", index, length)));
+ checker.accept(IndexOutOfBoundsException.class,
+ () -> Objects.checkIndex(index, length,
+ assertingOutOfBoundsReturnNull("checkIndex", index, length)));
+ checker.accept(IndexOutOfBoundsException.class,
() -> Objects.checkIndex(index, length, null));
- check.accept(IndexOutOfBoundsException.class,
+ checker.accept(IndexOutOfBoundsException.class,
() -> Objects.checkIndex(index, length));
+ checker.accept(ArrayIndexOutOfBoundsException.class,
+ () -> Objects.checkIndex(index, length,
+ Objects.outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new)));
+ checker.accept(StringIndexOutOfBoundsException.class,
+ () -> Objects.checkIndex(index, length,
+ Objects.outOfBoundsExceptionFormatter(StringIndexOutOfBoundsException::new)));
}
@@ -132,6 +155,11 @@
@Test(dataProvider = "checkFromToIndexProvider")
public void testCheckFromToIndex(int fromIndex, int toIndex, int length, boolean withinBounds) {
+ String expectedMessage = withinBounds
+ ? null
+ : Objects.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new).
+ apply("checkFromToIndex", List.of(fromIndex, toIndex, length)).getMessage();
+
BiConsumer<Class<? extends RuntimeException>, IntSupplier> check = (ec, s) -> {
try {
int rIndex = s.getAsInt();
@@ -145,17 +173,27 @@
if (withinBounds)
fail(String.format(
"Range [%d, %d) is within bounds of [0, %d), but was reported to be out of bounds", fromIndex, toIndex, length));
+ else
+ assertEquals(e.getMessage(), expectedMessage);
}
};
check.accept(AssertingOutOfBoundsException.class,
- () -> Objects.checkFromToIndex(fromIndex, toIndex, length, assertingOutOfBounds(fromIndex, toIndex)));
+ () -> Objects.checkFromToIndex(fromIndex, toIndex, length,
+ assertingOutOfBounds(expectedMessage, "checkFromToIndex", fromIndex, toIndex, length)));
check.accept(IndexOutOfBoundsException.class,
- () -> Objects.checkFromToIndex(fromIndex, toIndex, length, assertingOutOfBoundsReturnNull(fromIndex, toIndex)));
+ () -> Objects.checkFromToIndex(fromIndex, toIndex, length,
+ assertingOutOfBoundsReturnNull("checkFromToIndex", fromIndex, toIndex, length)));
check.accept(IndexOutOfBoundsException.class,
() -> Objects.checkFromToIndex(fromIndex, toIndex, length, null));
check.accept(IndexOutOfBoundsException.class,
() -> Objects.checkFromToIndex(fromIndex, toIndex, length));
+ check.accept(ArrayIndexOutOfBoundsException.class,
+ () -> Objects.checkFromToIndex(fromIndex, toIndex, length,
+ Objects.outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new)));
+ check.accept(StringIndexOutOfBoundsException.class,
+ () -> Objects.checkFromToIndex(fromIndex, toIndex, length,
+ Objects.outOfBoundsExceptionFormatter(StringIndexOutOfBoundsException::new)));
}
@@ -186,6 +224,11 @@
@Test(dataProvider = "checkFromIndexSizeProvider")
public void testCheckFromIndexSize(int fromIndex, int size, int length, boolean withinBounds) {
+ String expectedMessage = withinBounds
+ ? null
+ : Objects.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new).
+ apply("checkFromIndexSize", List.of(fromIndex, size, length)).getMessage();
+
BiConsumer<Class<? extends RuntimeException>, IntSupplier> check = (ec, s) -> {
try {
int rIndex = s.getAsInt();
@@ -199,36 +242,54 @@
if (withinBounds)
fail(String.format(
"Range [%d, %d + %d) is within bounds of [0, %d), but was reported to be out of bounds", fromIndex, fromIndex, size, length));
+ else
+ assertEquals(e.getMessage(), expectedMessage);
}
};
check.accept(AssertingOutOfBoundsException.class,
- () -> Objects.checkFromIndexSize(fromIndex, size, length, assertingOutOfBounds(fromIndex, size)));
+ () -> Objects.checkFromIndexSize(fromIndex, size, length,
+ assertingOutOfBounds(expectedMessage, "checkFromIndexSize", fromIndex, size, length)));
check.accept(IndexOutOfBoundsException.class,
- () -> Objects.checkFromIndexSize(fromIndex, size, length, assertingOutOfBoundsReturnNull(fromIndex, size)));
+ () -> Objects.checkFromIndexSize(fromIndex, size, length,
+ assertingOutOfBoundsReturnNull("checkFromIndexSize", fromIndex, size, length)));
check.accept(IndexOutOfBoundsException.class,
() -> Objects.checkFromIndexSize(fromIndex, size, length, null));
check.accept(IndexOutOfBoundsException.class,
() -> Objects.checkFromIndexSize(fromIndex, size, length));
+ check.accept(ArrayIndexOutOfBoundsException.class,
+ () -> Objects.checkFromIndexSize(fromIndex, size, length,
+ Objects.outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new)));
+ check.accept(StringIndexOutOfBoundsException.class,
+ () -> Objects.checkFromIndexSize(fromIndex, size, length,
+ Objects.outOfBoundsExceptionFormatter(StringIndexOutOfBoundsException::new)));
}
@Test
- public void checkIndexOutOfBoundsExceptionConstructors() {
- BiConsumer<Class<? extends RuntimeException>, IntSupplier> check = (ec, s) -> {
- try {
- s.getAsInt();
- fail("Runtime exception expected");
- }
- catch (RuntimeException e) {
- assertTrue(ec.isInstance(e));
- }
- };
+ public void uniqueMessagesForCheckKinds() {
+ BiFunction<String, List<Integer>, IndexOutOfBoundsException> f =
+ Objects.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new);
- check.accept(IndexOutOfBoundsException.class,
- () -> Objects.checkIndex(1, 0, IndexOutOfBoundsException::new));
- check.accept(StringIndexOutOfBoundsException.class,
- () -> Objects.checkIndex(1, 0, StringIndexOutOfBoundsException::new));
- check.accept(ArrayIndexOutOfBoundsException.class,
- () -> Objects.checkIndex(1, 0, ArrayIndexOutOfBoundsException::new));
+ List<String> messages = new ArrayList<>();
+ // Exact arguments
+ messages.add(f.apply("checkIndex", List.of(-1, 0)).getMessage());
+ messages.add(f.apply("checkFromToIndex", List.of(-1, 0, 0)).getMessage());
+ messages.add(f.apply("checkFromIndexSize", List.of(-1, 0, 0)).getMessage());
+ // Unknown check kind
+ messages.add(f.apply("checkUnknown", List.of(-1, 0, 0)).getMessage());
+ // Known check kind with more arguments
+ messages.add(f.apply("checkIndex", List.of(-1, 0, 0)).getMessage());
+ messages.add(f.apply("checkFromToIndex", List.of(-1, 0, 0, 0)).getMessage());
+ messages.add(f.apply("checkFromIndexSize", List.of(-1, 0, 0, 0)).getMessage());
+ // Known check kind with fewer arguments
+ messages.add(f.apply("checkIndex", List.of(-1)).getMessage());
+ messages.add(f.apply("checkFromToIndex", List.of(-1, 0)).getMessage());
+ messages.add(f.apply("checkFromIndexSize", List.of(-1, 0)).getMessage());
+ // Null arguments
+ messages.add(f.apply(null, null).getMessage());
+ messages.add(f.apply("checkNullArguments", null).getMessage());
+ messages.add(f.apply(null, List.of(-1)).getMessage());
+
+ assertEquals(messages.size(), messages.stream().distinct().count());
}
}