# HG changeset patch # User psandoz # Date 1460552750 -7200 # Node ID 9cb6e1141bdbd11380e1d5b63bbc1a0e38ba486a # Parent 52d3d8517efcc111b44f74c2249cd5ecb7fae709 8146458: Improve exception reporting for Objects.checkIndex/checkFromToIndex/checkFromIndexSize Reviewed-by: jrose, smarks diff -r 52d3d8517efc -r 9cb6e1141bdb jdk/src/java.base/share/classes/java/lang/ArrayIndexOutOfBoundsException.java --- 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. - * - *

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); - } } diff -r 52d3d8517efc -r 9cb6e1141bdb jdk/src/java.base/share/classes/java/lang/IndexOutOfBoundsException.java --- 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. - * - *

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); - } - } diff -r 52d3d8517efc -r 9cb6e1141bdb jdk/src/java.base/share/classes/java/lang/StringIndexOutOfBoundsException.java --- 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. - * - *

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); - } } diff -r 52d3d8517efc -r 9cb6e1141bdb jdk/src/java.base/share/classes/java/lang/invoke/VarHandle.java --- 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 AIOOBE_SUPPLIER = new BiFunction<>() { - @Override - public ArrayIndexOutOfBoundsException apply(Integer a, Integer b) { - return new ArrayIndexOutOfBoundsException(a, b); - } - }; + static final BiFunction, ArrayIndexOutOfBoundsException> + AIOOBE_SUPPLIER = Objects.outOfBoundsExceptionFormatter( + new Function() { + @Override + public ArrayIndexOutOfBoundsException apply(String s) { + return new ArrayIndexOutOfBoundsException(s); + } + }); private static final long VFORM_OFFSET; diff -r 52d3d8517efc -r 9cb6e1141bdb jdk/src/java.base/share/classes/java/util/Objects.java --- 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 oobe) { - RuntimeException e = oobe == null - ? null : oobe.apply(a, b); + BiFunction, ? extends RuntimeException> oobef, + String checkKind, + Integer... args) { + List 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, ? extends RuntimeException> oobe, + int index, int length) { + return outOfBounds(oobe, "checkIndex", index, length); + } + + private static RuntimeException outOfBoundsCheckFromToIndex( + BiFunction, ? extends RuntimeException> oobe, + int fromIndex, int toIndex, int length) { + return outOfBounds(oobe, "checkFromToIndex", fromIndex, toIndex, length); + } + + private static RuntimeException outOfBoundsCheckFromIndexSize( + BiFunction, ? 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. + * + *

The exception formatter accepts two arguments: a {@code String} + * describing the out-of-bounds range check that failed, referred to as the + * check kind; and a {@code List} containing the + * out-of-bound integer values that failed the check. The list of + * out-of-bound values is not modified. + * + *

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

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: + *

{@code
+     * static final
+     * BiFunction, ArrayIndexOutOfBoundsException> AIOOBEF =
+     *     outOfBoundsExceptionFormatter(ArrayIndexOutOfBoundsException::new);
+     * }
+ * 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}: + *
{@code
+     * checkIndex(index, limit, AIOOBEF);
+     * }
+ * 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: + *
{@code
+     * AIOOBEF.apply("checkIndex", List.of(index, limit));
+     * }
+ * + * @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 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 + BiFunction, X> outOfBoundsExceptionFormatter(Function f) { + // Use anonymous class to avoid bootstrap issues if this method is + // used early in startup + return new BiFunction, X>() { + @Override + public X apply(String checkKind, List args) { + return f.apply(outOfBoundsMessage(checkKind, args)); + } + }; + } + + private static String outOfBoundsMessage(String checkKind, List 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, %The {@code index} is defined to be out of bounds if any of the + *

The {@code index} is defined to be out-of-bounds if any of the * following inequalities is true: *

    *
  • {@code index < 0}
  • @@ -377,14 +526,20 @@ *
  • {@code length < 0}, which is implied from the former inequalities
  • *
* + *

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

The {@code index} is defined to be out of bounds if any of the + *

The {@code index} is defined to be out-of-bounds if any of the * following inequalities is true: *

    *
  • {@code index < 0}
  • @@ -400,40 +555,42 @@ *
  • {@code length < 0}, which is implied from the former inequalities
  • *
* - *

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

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 the type of runtime exception to throw if the arguments are - * out of bounds + * @param 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 + public static int checkIndex(int index, int length, - BiFunction oobe) throws T, IndexOutOfBoundsException { + BiFunction, 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). * - *

The sub-range is defined to be out of bounds if any of the following + *

The sub-range is defined to be out-of-bounds if any of the following * inequalities is true: *

    *
  • {@code fromIndex < 0}
  • @@ -451,15 +608,21 @@ *
  • {@code length < 0}, which is implied from the former inequalities
  • *
* + *

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

The sub-range is defined to be out of bounds if any of the following + *

The sub-range is defined to be out-of-bounds if any of the following * inequalities is true: *

    *
  • {@code fromIndex < 0}
  • @@ -477,34 +640,36 @@ *
  • {@code length < 0}, which is implied from the former inequalities
  • *
* - *

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

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 the type of runtime exception to throw if the arguments are - * out of bounds + * @param 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 + public static int checkFromToIndex(int fromIndex, int toIndex, int length, - BiFunction oobe) throws T, IndexOutOfBoundsException { + BiFunction, 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). * - *

The sub-range is defined to be out of bounds if any of the following + *

The sub-range is defined to be out-of-bounds if any of the following * inequalities is true: *

    *
  • {@code fromIndex < 0}
  • @@ -522,15 +687,21 @@ *
  • {@code length < 0}, which is implied from the former inequalities
  • *
* + *

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

The sub-range is defined to be out of bounds if any of the following + *

The sub-range is defined to be out-of-bounds if any of the following * inequalities is true: *

    *
  • {@code fromIndex < 0}
  • @@ -548,34 +719,37 @@ *
  • {@code length < 0}, which is implied from the former inequalities
  • *
* - *

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

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 the type of runtime exception to throw if the arguments are - * out of bounds + * @param 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 + public static int checkFromIndexSize(int fromIndex, int size, int length, - BiFunction oobe) throws T, IndexOutOfBoundsException { + BiFunction, X> oobef) { if ((length | fromIndex | size) < 0 || size > length - fromIndex) - throw outOfBounds(fromIndex, size, oobe); + throw outOfBoundsCheckFromIndexSize(oobef, fromIndex, size, length); return fromIndex; } } diff -r 52d3d8517efc -r 9cb6e1141bdb jdk/test/java/util/Objects/CheckIndex.java --- 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 assertingOutOfBounds( - int expFromIndex, int expToIndexOrSizeOrLength) { - return (fromIndex, toIndexOrSizeorLength) -> { - assertEquals(fromIndex, Integer.valueOf(expFromIndex)); - assertEquals(toIndexOrSizeorLength, Integer.valueOf(expToIndexOrSizeOrLength)); - return new AssertingOutOfBoundsException(); + static BiFunction, 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 argument should be unmodifiable"); + } catch (Exception e) { + } + return new AssertingOutOfBoundsException(message); }; } - static BiFunction assertingOutOfBoundsReturnNull( - int expFromIndex, int expToIndexOrSizeOrLength) { - return (fromIndex, toIndexOrSizeorLength) -> { - assertEquals(fromIndex, Integer.valueOf(expFromIndex)); - assertEquals(toIndexOrSizeorLength, Integer.valueOf(expToIndexOrSizeOrLength)); + static BiFunction, 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, IntSupplier> check = (ec, s) -> { + String expectedMessage = withinBounds + ? null + : Objects.outOfBoundsExceptionFormatter(IndexOutOfBoundsException::new). + apply("checkIndex", List.of(index, length)).getMessage(); + + BiConsumer, 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, 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, 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, IntSupplier> check = (ec, s) -> { - try { - s.getAsInt(); - fail("Runtime exception expected"); - } - catch (RuntimeException e) { - assertTrue(ec.isInstance(e)); - } - }; + public void uniqueMessagesForCheckKinds() { + BiFunction, 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 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()); } }