Skip to content

Commit 18e31fc

Browse files
committed
Merge branch '6.2.x'
2 parents 722701a + b07217a commit 18e31fc

File tree

3 files changed

+96
-11
lines changed

3 files changed

+96
-11
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,8 @@ else if (!sourceType.isAssignableTo(componentTypeDesc) ||
319319
(sourceType.isArray() && !sourceType.isAssignableTo(targetType)) ||
320320
(argument instanceof List)) {
321321

322-
TypeDescriptor targetTypeToUse =
323-
(sourceType.isArray() || argument instanceof List ? targetType : componentTypeDesc);
322+
TypeDescriptor targetTypeToUse = (sourceType.isArray() || argument instanceof List ||
323+
converter.canConvert(sourceType, targetType) ? targetType : componentTypeDesc);
324324
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetTypeToUse);
325325
}
326326
// Possible outcomes of the above if-else block:
@@ -421,8 +421,8 @@ else if (!sourceType.isAssignableTo(varargsComponentType) ||
421421
(sourceType.isArray() && !sourceType.isAssignableTo(varargsArrayType)) ||
422422
(argument instanceof List)) {
423423

424-
TypeDescriptor targetTypeToUse =
425-
(sourceType.isArray() || argument instanceof List ? varargsArrayType : varargsComponentType);
424+
TypeDescriptor targetTypeToUse = (sourceType.isArray() || argument instanceof List ||
425+
converter.canConvert(sourceType, varargsArrayType) ? varargsArrayType : varargsComponentType);
426426
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetTypeToUse);
427427
}
428428
// Possible outcomes of the above if-else block:

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -118,6 +118,11 @@ private static void populateMethodHandles(StandardEvaluationContext testContext)
118118
"varargsFunction", MethodType.methodType(String.class, String[].class));
119119
testContext.registerFunction("varargsFunctionHandle", varargsFunctionHandle);
120120

121+
// #varargsObjectFunctionHandle(args...)
122+
MethodHandle varargsObjectFunctionHandle = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
123+
"varargsObjectFunction", MethodType.methodType(String.class, Object[].class));
124+
testContext.registerFunction("varargsObjectFunctionHandle", varargsObjectFunctionHandle);
125+
121126
// #add(int, int)
122127
MethodHandle add = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
123128
"add", MethodType.methodType(int.class, int.class, int.class));

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

+86-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,11 +16,21 @@
1616

1717
package org.springframework.expression.spel;
1818

19+
import java.util.Set;
20+
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.Nested;
1923
import org.junit.jupiter.api.Test;
2024

21-
import org.springframework.expression.spel.standard.SpelExpressionParser;
25+
import org.springframework.core.convert.TypeDescriptor;
26+
import org.springframework.core.convert.converter.GenericConverter;
27+
import org.springframework.core.convert.support.DefaultConversionService;
28+
import org.springframework.expression.Expression;
29+
import org.springframework.expression.spel.standard.SpelExpression;
2230
import org.springframework.expression.spel.support.StandardEvaluationContext;
31+
import org.springframework.expression.spel.support.StandardTypeConverter;
2332
import org.springframework.expression.spel.support.StandardTypeLocator;
33+
import org.springframework.lang.Nullable;
2434

2535
import static org.assertj.core.api.Assertions.assertThat;
2636
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -266,11 +276,10 @@ void functionViaMethodHandleForStaticMethodThatAcceptsOnlyVarargs() {
266276

267277
@Test
268278
void functionMethodMustBeStatic() throws Exception {
269-
SpelExpressionParser parser = new SpelExpressionParser();
270-
StandardEvaluationContext ctx = new StandardEvaluationContext();
271-
ctx.setVariable("notStatic", this.getClass().getMethod("nonStatic"));
279+
context.registerFunction("nonStatic", this.getClass().getMethod("nonStatic"));
280+
SpelExpression expression = parser.parseRaw("#nonStatic()");
272281
assertThatExceptionOfType(SpelEvaluationException.class)
273-
.isThrownBy(() -> parser.parseRaw("#notStatic()").getValue(ctx))
282+
.isThrownBy(() -> expression.getValue(context))
274283
.satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(FUNCTION_MUST_BE_STATIC));
275284
}
276285

@@ -279,4 +288,75 @@ void functionMethodMustBeStatic() throws Exception {
279288
public void nonStatic() {
280289
}
281290

291+
292+
@Nested // gh-34371
293+
class VarargsAndPojoToArrayConversionTests {
294+
295+
private final StandardEvaluationContext context = TestScenarioCreator.getTestEvaluationContext();
296+
297+
private final ArrayHolder arrayHolder = new ArrayHolder("a", "b", "c");
298+
299+
300+
@BeforeEach
301+
void setUp() {
302+
DefaultConversionService conversionService = new DefaultConversionService();
303+
conversionService.addConverter(new ArrayHolderConverter());
304+
context.setTypeConverter(new StandardTypeConverter(conversionService));
305+
context.setVariable("arrayHolder", arrayHolder);
306+
}
307+
308+
@Test
309+
void functionWithVarargsAndPojoToArrayConversion() {
310+
// #varargsFunction: static String varargsFunction(String... strings) -> Arrays.toString(strings)
311+
evaluate("#varargsFunction(#arrayHolder)", "[a, b, c]");
312+
313+
// #varargsObjectFunction: static String varargsObjectFunction(Object... args) -> Arrays.toString(args)
314+
//
315+
// Since ArrayHolder is an "instanceof Object" and Object is the varargs component type,
316+
// we expect the ArrayHolder not to be converted to an array but rather to be passed
317+
// "as is" as a single argument to the varargs method.
318+
evaluate("#varargsObjectFunction(#arrayHolder)", "[" + arrayHolder.toString() + "]");
319+
}
320+
321+
@Test
322+
void functionWithVarargsAndPojoToArrayConversionViaMethodHandle() {
323+
// #varargsFunctionHandle: static String varargsFunction(String... strings) -> Arrays.toString(strings)
324+
evaluate("#varargsFunctionHandle(#arrayHolder)", "[a, b, c]");
325+
326+
// #varargsObjectFunctionHandle: static String varargsObjectFunction(Object... args) -> Arrays.toString(args)
327+
//
328+
// Since ArrayHolder is an "instanceof Object" and Object is the varargs component type,
329+
// we expect the ArrayHolder not to be converted to an array but rather to be passed
330+
// "as is" as a single argument to the varargs method.
331+
evaluate("#varargsObjectFunctionHandle(#arrayHolder)", "[" + arrayHolder.toString() + "]");
332+
}
333+
334+
private void evaluate(String expression, Object expectedValue) {
335+
Expression expr = parser.parseExpression(expression);
336+
assertThat(expr).as("expression").isNotNull();
337+
Object value = expr.getValue(context);
338+
assertThat(value).as("expression '" + expression + "'").isEqualTo(expectedValue);
339+
}
340+
341+
342+
record ArrayHolder(String... array) {
343+
}
344+
345+
static class ArrayHolderConverter implements GenericConverter {
346+
347+
@Nullable
348+
@Override
349+
public Set<ConvertiblePair> getConvertibleTypes() {
350+
return Set.of(new ConvertiblePair(ArrayHolder.class, Object[].class));
351+
}
352+
353+
@Nullable
354+
@Override
355+
public String[] convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
356+
return ((ArrayHolder) source).array();
357+
}
358+
}
359+
360+
}
361+
282362
}

0 commit comments

Comments
 (0)