8146458: Improve exception reporting for Objects.checkIndex/checkFromToIndex/checkFromIndexSize

Reviewed-by: jrose, smarks
This commit is contained in:
Paul Sandoz 2016-04-13 15:05:50 +02:00
parent e10605459a
commit 87c6cee72e
6 changed files with 373 additions and 183 deletions

View File

@ -64,20 +64,4 @@ public class ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException {
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);
}
}

View File

@ -67,21 +67,4 @@ public class IndexOutOfBoundsException extends RuntimeException {
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);
}
}

View File

@ -67,20 +67,4 @@ public class StringIndexOutOfBoundsException extends IndexOutOfBoundsException {
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);
}
}

View File

@ -34,7 +34,9 @@ import java.util.Arrays;
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 @@ public abstract class VarHandle {
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;

View File

@ -25,26 +25,28 @@
package java.util;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import jdk.internal.HotSpotIntrinsicCandidate;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* 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 @@ public final class Objects {
}
/**
* 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 @@ public final class Objects {
* <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 @@ public final class Objects {
* 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 @@ public final class Objects {
* <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 @@ public final class Objects {
* {@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 @@ public final class Objects {
* <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 @@ public final class Objects {
* {@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 @@ public final class Objects {
* <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 @@ public final class Objects {
* {@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 @@ public final class Objects {
* <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 @@ public final class Objects {
* {@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 @@ public final class Objects {
* <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;
}
}

View File

@ -43,22 +43,30 @@ import static org.testng.Assert.*;
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 @@ public class CheckIndex {
@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 @@ public class CheckIndex {
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 @@ public class CheckIndex {
@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 @@ public class CheckIndex {
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 @@ public class CheckIndex {
@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 @@ public class CheckIndex {
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());
}
}