8263087: Add a MethodHandle combinator that switches over a set of MethodHandles

Reviewed-by: redestad
This commit is contained in:
Jorn Vernee 2021-05-27 12:28:10 +00:00
parent 85f616522b
commit 3623abb7f6
10 changed files with 974 additions and 26 deletions

View file

@ -7751,4 +7751,90 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
}
}
/**
* Creates a table switch method handle, which can be used to switch over a set of target
* method handles, based on a given target index, called selector.
* <p>
* For a selector value of {@code n}, where {@code n} falls in the range {@code [0, N)},
* and where {@code N} is the number of target method handles, the table switch method
* handle will invoke the n-th target method handle from the list of target method handles.
* <p>
* For a selector value that does not fall in the range {@code [0, N)}, the table switch
* method handle will invoke the given fallback method handle.
* <p>
* All method handles passed to this method must have the same type, with the additional
* requirement that the leading parameter be of type {@code int}. The leading parameter
* represents the selector.
* <p>
* Any trailing parameters present in the type will appear on the returned table switch
* method handle as well. Any arguments assigned to these parameters will be forwarded,
* together with the selector value, to the selected method handle when invoking it.
*
* @apiNote Example:
* The cases each drop the {@code selector} value they are given, and take an additional
* {@code String} argument, which is concatenated (using {@link String#concat(String)})
* to a specific constant label string for each case:
* <blockquote><pre>{@code
* MethodHandles.Lookup lookup = MethodHandles.lookup();
* MethodHandle caseMh = lookup.findVirtual(String.class, "concat",
* MethodType.methodType(String.class, String.class));
* caseMh = MethodHandles.dropArguments(caseMh, 0, int.class);
*
* MethodHandle caseDefault = MethodHandles.insertArguments(caseMh, 1, "default: ");
* MethodHandle case0 = MethodHandles.insertArguments(caseMh, 1, "case 0: ");
* MethodHandle case1 = MethodHandles.insertArguments(caseMh, 1, "case 1: ");
*
* MethodHandle mhSwitch = MethodHandles.tableSwitch(
* caseDefault,
* case0,
* case1
* );
*
* assertEquals("default: data", (String) mhSwitch.invokeExact(-1, "data"));
* assertEquals("case 0: data", (String) mhSwitch.invokeExact(0, "data"));
* assertEquals("case 1: data", (String) mhSwitch.invokeExact(1, "data"));
* assertEquals("default: data", (String) mhSwitch.invokeExact(2, "data"));
* }</pre></blockquote>
*
* @param fallback the fallback method handle that is called when the selector is not
* within the range {@code [0, N)}.
* @param targets array of target method handles.
* @return the table switch method handle.
* @throws NullPointerException if {@code fallback}, the {@code targets} array, or any
* any of the elements of the {@code targets} array are
* {@code null}.
* @throws IllegalArgumentException if the {@code targets} array is empty, if the leading
* parameter of the fallback handle or any of the target
* handles is not {@code int}, or if the types of
* the fallback handle and all of target handles are
* not the same.
*/
public static MethodHandle tableSwitch(MethodHandle fallback, MethodHandle... targets) {
Objects.requireNonNull(fallback);
Objects.requireNonNull(targets);
targets = targets.clone();
MethodType type = tableSwitchChecks(fallback, targets);
return MethodHandleImpl.makeTableSwitch(type, fallback, targets);
}
private static MethodType tableSwitchChecks(MethodHandle defaultCase, MethodHandle[] caseActions) {
if (caseActions.length == 0)
throw new IllegalArgumentException("Not enough cases: " + Arrays.toString(caseActions));
MethodType expectedType = defaultCase.type();
if (!(expectedType.parameterCount() >= 1) || expectedType.parameterType(0) != int.class)
throw new IllegalArgumentException(
"Case actions must have int as leading parameter: " + Arrays.toString(caseActions));
for (MethodHandle mh : caseActions) {
Objects.requireNonNull(mh);
if (mh.type() != expectedType)
throw new IllegalArgumentException(
"Case actions must have the same type: " + Arrays.toString(caseActions));
}
return expectedType;
}
}