mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-24 05:40:39 +00:00
8146458: Improve exception reporting for Objects.checkIndex/checkFromToIndex/checkFromIndexSize
Reviewed-by: jrose, smarks
This commit is contained in:
parent
e10605459a
commit
87c6cee72e
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user