Skip to content

Commit 9c934b5

Browse files
committed
Support varargs-only MethodHandle as SpEL function
Prior to this commit, if a MethodHandle was registered as a custom function in the Spring Expression Language (SpEL) for a static method that accepted only a variable argument list (for example, `static String func(String... args)`), attempting to invoke the registered function within a SpEL expression resulted in a ClassCastException because the varargs array was unnecessarily wrapped in an Object[]. This commit modifies the logic in FunctionReference's internal executeFunctionViaMethodHandle() method to address that. Closes gh-34109
1 parent 4b45338 commit 9c934b5

File tree

3 files changed

+32
-3
lines changed

3 files changed

+32
-3
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,9 @@ else if (spelParamCount != declaredParamCount) {
226226
ReflectionHelper.convertAllMethodHandleArguments(converter, functionArgs, methodHandle, varArgPosition);
227227

228228
if (isSuspectedVarargs) {
229-
if (declaredParamCount == 1) {
230-
// We only repackage the varargs if it is the ONLY argument -- for example,
229+
if (declaredParamCount == 1 && !methodHandle.isVarargsCollector()) {
230+
// We only repackage the arguments if the MethodHandle accepts a single
231+
// argument AND the MethodHandle is not a "varargs collector" -- for example,
231232
// when we are dealing with a bound MethodHandle.
232233
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
233234
methodHandle.type().parameterArray(), functionArgs);

spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,16 @@ private static void populateMethodHandles(StandardEvaluationContext testContext)
108108
"formatObjectVarargs", MethodType.methodType(String.class, String.class, Object[].class));
109109
testContext.registerFunction("formatObjectVarargs", formatObjectVarargs);
110110

111-
// #formatObjectVarargs(format, args...)
111+
// #formatPrimitiveVarargs(format, args...)
112112
MethodHandle formatPrimitiveVarargs = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
113113
"formatPrimitiveVarargs", MethodType.methodType(String.class, String.class, int[].class));
114114
testContext.registerFunction("formatPrimitiveVarargs", formatPrimitiveVarargs);
115115

116+
// #varargsFunctionHandle(args...)
117+
MethodHandle varargsFunctionHandle = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
118+
"varargsFunction", MethodType.methodType(String.class, String[].class));
119+
testContext.registerFunction("varargsFunctionHandle", varargsFunctionHandle);
120+
116121
// #add(int, int)
117122
MethodHandle add = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
118123
"add", MethodType.methodType(int.class, int.class, int.class));

spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java

+23
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ void functionInvocationWithStringArgument() {
7979

8080
@Test
8181
void functionWithVarargs() {
82+
// static String varargsFunction(String... strings) -> Arrays.toString(strings)
83+
8284
evaluate("#varargsFunction()", "[]", String.class);
8385
evaluate("#varargsFunction(new String[0])", "[]", String.class);
8486
evaluate("#varargsFunction('a')", "[a]", String.class);
@@ -241,6 +243,27 @@ void functionFromMethodHandleWithListConvertedToVarargsArray() {
241243
evaluate("#formatObjectVarargs('x -> %s %s %s', {'a', 'b', 'c'})", expected, String.class);
242244
}
243245

246+
@Test // gh-34109
247+
void functionViaMethodHandleForStaticMethodThatAcceptsOnlyVarargs() {
248+
// #varargsFunctionHandle: static String varargsFunction(String... strings) -> Arrays.toString(strings)
249+
250+
evaluate("#varargsFunctionHandle()", "[]", String.class);
251+
evaluate("#varargsFunctionHandle(new String[0])", "[]", String.class);
252+
evaluate("#varargsFunctionHandle('a')", "[a]", String.class);
253+
evaluate("#varargsFunctionHandle('a','b','c')", "[a, b, c]", String.class);
254+
evaluate("#varargsFunctionHandle(new String[]{'a','b','c'})", "[a, b, c]", String.class);
255+
// Conversion from int to String
256+
evaluate("#varargsFunctionHandle(25)", "[25]", String.class);
257+
evaluate("#varargsFunctionHandle('b',25)", "[b, 25]", String.class);
258+
evaluate("#varargsFunctionHandle(new int[]{1, 2, 3})", "[1, 2, 3]", String.class);
259+
// Strings that contain a comma
260+
evaluate("#varargsFunctionHandle('a,b')", "[a,b]", String.class);
261+
evaluate("#varargsFunctionHandle('a', 'x,y', 'd')", "[a, x,y, d]", String.class);
262+
// null values
263+
evaluate("#varargsFunctionHandle(null)", "[null]", String.class);
264+
evaluate("#varargsFunctionHandle('a',null,'b')", "[a, null, b]", String.class);
265+
}
266+
244267
@Test
245268
void functionMethodMustBeStatic() throws Exception {
246269
SpelExpressionParser parser = new SpelExpressionParser();

0 commit comments

Comments
 (0)