Skip to content

Commit 1e804d8

Browse files
committed
Merge branch '6.1.x'
2 parents 622c1b9 + fcc99a6 commit 1e804d8

File tree

4 files changed

+98
-50
lines changed

4 files changed

+98
-50
lines changed

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

+12-6
Original file line numberDiff line numberDiff line change
@@ -313,11 +313,14 @@ static boolean convertArguments(TypeConverter converter, Object[] arguments, Exe
313313
// convert it or wrap it in an array. For example, using StringToArrayConverter to convert
314314
// a String containing a comma would result in the String being split and repackaged in an
315315
// array when it should be used as-is. Similarly, if the argument is an array that is
316-
// assignable to the varargs array type, there is no need to convert it.
316+
// assignable to the varargs array type, there is no need to convert it. However, if the
317+
// argument is a java.util.List, we let the TypeConverter convert the list to an array.
317318
else if (!sourceType.isAssignableTo(componentTypeDesc) ||
318-
(sourceType.isArray() && !sourceType.isAssignableTo(targetType))) {
319+
(sourceType.isArray() && !sourceType.isAssignableTo(targetType)) ||
320+
(argument instanceof List)) {
319321

320-
TypeDescriptor targetTypeToUse = (sourceType.isArray() ? targetType : componentTypeDesc);
322+
TypeDescriptor targetTypeToUse =
323+
(sourceType.isArray() || argument instanceof List ? targetType : componentTypeDesc);
321324
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetTypeToUse);
322325
}
323326
// Possible outcomes of the above if-else block:
@@ -411,11 +414,14 @@ public static boolean convertAllMethodHandleArguments(TypeConverter converter, O
411414
// convert it. For example, using StringToArrayConverter to convert a String containing a
412415
// comma would result in the String being split and repackaged in an array when it should
413416
// be used as-is. Similarly, if the argument is an array that is assignable to the varargs
414-
// array type, there is no need to convert it.
417+
// array type, there is no need to convert it. However, if the argument is a java.util.List,
418+
// we let the TypeConverter convert the list to an array.
415419
else if (!sourceType.isAssignableTo(varargsComponentType) ||
416-
(sourceType.isArray() && !sourceType.isAssignableTo(varargsArrayType))) {
420+
(sourceType.isArray() && !sourceType.isAssignableTo(varargsArrayType)) ||
421+
(argument instanceof List)) {
417422

418-
TypeDescriptor targetTypeToUse = (sourceType.isArray() ? varargsArrayType : varargsComponentType);
423+
TypeDescriptor targetTypeToUse =
424+
(sourceType.isArray() || argument instanceof List ? varargsArrayType : varargsComponentType);
419425
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetTypeToUse);
420426
}
421427
// Possible outcomes of the above if-else block:

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

+53-35
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import org.springframework.expression.spel.standard.SpelExpression;
3333
import org.springframework.expression.spel.standard.SpelExpressionParser;
3434
import org.springframework.expression.spel.support.StandardEvaluationContext;
35+
import org.springframework.expression.spel.support.StandardTypeLocator;
36+
import org.springframework.expression.spel.testresources.Inventor;
3537
import org.springframework.expression.spel.testresources.PlaceOfBirth;
3638

3739
import static org.assertj.core.api.Assertions.assertThat;
@@ -48,45 +50,46 @@
4850
class MethodInvocationTests extends AbstractExpressionTests {
4951

5052
@Test
51-
void testSimpleAccess01() {
53+
void simpleAccess() {
5254
evaluate("getPlaceOfBirth().getCity()", "Smiljan", String.class);
5355
}
5456

5557
@Test
56-
void testStringClass() {
58+
void stringClass() {
5759
evaluate("new java.lang.String('hello').charAt(2)", 'l', Character.class);
5860
evaluate("new java.lang.String('hello').charAt(2).equals('l'.charAt(0))", true, Boolean.class);
5961
evaluate("'HELLO'.toLowerCase()", "hello", String.class);
6062
evaluate("' abcba '.trim()", "abcba", String.class);
6163
}
6264

6365
@Test
64-
void testNonExistentMethods() {
66+
void nonExistentMethods() {
6567
// name is ok but madeup() does not exist
6668
evaluateAndCheckError("name.madeup()", SpelMessage.METHOD_NOT_FOUND, 5);
6769
}
6870

6971
@Test
70-
void testWidening01() {
72+
void widening() {
7173
// widening of int 3 to double 3 is OK
7274
evaluate("new Double(3.0d).compareTo(8)", -1, Integer.class);
7375
evaluate("new Double(3.0d).compareTo(3)", 0, Integer.class);
7476
evaluate("new Double(3.0d).compareTo(2)", 1, Integer.class);
7577
}
7678

7779
@Test
78-
void testArgumentConversion01() {
80+
void argumentConversion() {
7981
// Rely on Double>String conversion for calling startsWith()
8082
evaluate("new String('hello 2.0 to you').startsWith(7.0d)", false, Boolean.class);
8183
evaluate("new String('7.0 foobar').startsWith(7.0d)", true, Boolean.class);
8284
}
8385

84-
@Test
85-
void testMethodThrowingException_SPR6760() {
86+
@Test // SPR-6760
87+
void methodThrowingException() {
8688
// Test method on inventor: throwException()
87-
// On 1 it will throw an IllegalArgumentException
88-
// On 2 it will throw a RuntimeException
89-
// On 3 it will exit normally
89+
// On 1 it will throw an IllegalArgumentException.
90+
// On 2 it will throw a RuntimeException.
91+
// On 4 it will throw a TestException.
92+
// Otherwise, it will exit normally.
9093
// In each case it increments the Inventor field 'counter' when invoked
9194

9295
SpelExpressionParser parser = new SpelExpressionParser();
@@ -115,7 +118,6 @@ void testMethodThrowingException_SPR6760() {
115118
assertThat(o).isEqualTo(3);
116119
assertThat(parser.parseExpression("counter").getValue(eContext)).isEqualTo(2);
117120

118-
119121
// Now cause it to throw an exception:
120122
eContext.setVariable("bar", 1);
121123
assertThatException()
@@ -135,12 +137,13 @@ void testMethodThrowingException_SPR6760() {
135137
/**
136138
* Check on first usage (when the cachedExecutor in MethodReference is null) that the exception is not wrapped.
137139
*/
138-
@Test
139-
void testMethodThrowingException_SPR6941() {
140+
@Test // SPR-6941
141+
void methodThrowingRuntimeException() {
140142
// Test method on inventor: throwException()
141-
// On 1 it will throw an IllegalArgumentException
142-
// On 2 it will throw a RuntimeException
143-
// On 3 it will exit normally
143+
// On 1 it will throw an IllegalArgumentException.
144+
// On 2 it will throw a RuntimeException.
145+
// On 4 it will throw a TestException.
146+
// Otherwise, it will exit normally.
144147
// In each case it increments the Inventor field 'counter' when invoked
145148

146149
SpelExpressionParser parser = new SpelExpressionParser();
@@ -152,12 +155,13 @@ void testMethodThrowingException_SPR6941() {
152155
.isNotInstanceOf(SpelEvaluationException.class);
153156
}
154157

155-
@Test
156-
void testMethodThrowingException_SPR6941_2() {
158+
@Test // SPR-6941
159+
void methodThrowingCustomException() {
157160
// Test method on inventor: throwException()
158-
// On 1 it will throw an IllegalArgumentException
159-
// On 2 it will throw a RuntimeException
160-
// On 3 it will exit normally
161+
// On 1 it will throw an IllegalArgumentException.
162+
// On 2 it will throw a RuntimeException.
163+
// On 4 it will throw a TestException.
164+
// Otherwise, it will exit normally.
161165
// In each case it increments the Inventor field 'counter' when invoked
162166

163167
SpelExpressionParser parser = new SpelExpressionParser();
@@ -166,12 +170,11 @@ void testMethodThrowingException_SPR6941_2() {
166170
context.setVariable("bar", 4);
167171
assertThatExceptionOfType(ExpressionInvocationTargetException.class)
168172
.isThrownBy(() -> expr.getValue(context))
169-
.satisfies(ex -> assertThat(ex.getCause().getClass().getName()).isEqualTo(
170-
"org.springframework.expression.spel.testresources.Inventor$TestException"));
173+
.withCauseExactlyInstanceOf(Inventor.TestException.class);
171174
}
172175

173-
@Test
174-
void testMethodFiltering_SPR6764() {
176+
@Test // SPR-6764
177+
void methodFiltering() {
175178
SpelExpressionParser parser = new SpelExpressionParser();
176179
StandardEvaluationContext context = new StandardEvaluationContext();
177180
context.setRootObject(new TestObject());
@@ -211,7 +214,7 @@ void testMethodFiltering_SPR6764() {
211214
}
212215

213216
@Test
214-
void testAddingMethodResolvers() {
217+
void addingMethodResolvers() {
215218
StandardEvaluationContext ctx = new StandardEvaluationContext();
216219

217220
// reflective method accessor is the only one by default
@@ -235,7 +238,7 @@ void testAddingMethodResolvers() {
235238
}
236239

237240
@Test
238-
void testVarargsInvocation01() {
241+
void varargsInvocation01() {
239242
// Calling 'public String aVarargsMethod(String... strings)'
240243
evaluate("aVarargsMethod('a','b','c')", "[a, b, c]", String.class);
241244
evaluate("aVarargsMethod('a')", "[a]", String.class);
@@ -252,7 +255,7 @@ void testVarargsInvocation01() {
252255
}
253256

254257
@Test
255-
void testVarargsInvocation02() {
258+
void varargsInvocation02() {
256259
// Calling 'public String aVarargsMethod2(int i, String... strings)'
257260
evaluate("aVarargsMethod2(5,'a','b','c')", "5-[a, b, c]", String.class);
258261
evaluate("aVarargsMethod2(2,'a')", "2-[a]", String.class);
@@ -267,7 +270,7 @@ void testVarargsInvocation02() {
267270
}
268271

269272
@Test
270-
void testVarargsInvocation03() {
273+
void varargsInvocation03() {
271274
// Calling 'public int aVarargsMethod3(String str1, String... strings)' - returns all strings concatenated with "-"
272275

273276
// No conversion necessary
@@ -295,7 +298,7 @@ void testVarargsInvocation03() {
295298
}
296299

297300
@Test // gh-33013
298-
void testVarargsWithObjectArrayType() {
301+
void varargsWithObjectArrayType() {
299302
// Calling 'public String formatObjectVarargs(String format, Object... args)' -> String.format(format, args)
300303

301304
// No var-args and no conversion necessary
@@ -336,7 +339,7 @@ void testVarargsWithObjectArrayType() {
336339
}
337340

338341
@Test
339-
void testVarargsWithPrimitiveArrayType() {
342+
void varargsWithPrimitiveArrayType() {
340343
// Calling 'public String formatPrimitiveVarargs(String format, int... nums)' -> effectively String.format(format, args)
341344

342345
// No var-args and no conversion necessary
@@ -357,13 +360,28 @@ void testVarargsWithPrimitiveArrayType() {
357360
}
358361

359362
@Test
360-
void testVarargsWithPrimitiveArrayToObjectArrayConversion() {
363+
void varargsWithPrimitiveArrayToObjectArrayConversion() {
361364
evaluate("formatObjectVarargs('x -> %s %s %s', new short[]{1, 2, 3})", "x -> 1 2 3", String.class); // short[] to Object[]
362365
evaluate("formatObjectVarargs('x -> %s %s %s', new int[]{1, 2, 3})", "x -> 1 2 3", String.class); // int[] to Object[]
363366
}
364367

368+
@Test // gh-33315
369+
void varargsWithListConvertedToVarargsArray() {
370+
((StandardTypeLocator) context.getTypeLocator()).registerImport("java.util");
371+
372+
// Calling 'public String aVarargsMethod(String... strings)' -> Arrays.toString(strings)
373+
String expected = "[a, b, c]";
374+
evaluate("aVarargsMethod(T(List).of('a', 'b', 'c'))", expected, String.class);
375+
evaluate("aVarargsMethod({'a', 'b', 'c'})", expected, String.class);
376+
377+
// Calling 'public String formatObjectVarargs(String format, Object... args)' -> String.format(format, args)
378+
expected = "x -> a b c";
379+
evaluate("formatObjectVarargs('x -> %s %s %s', T(List).of('a', 'b', 'c'))", expected, String.class);
380+
evaluate("formatObjectVarargs('x -> %s %s %s', {'a', 'b', 'c'})", expected, String.class);
381+
}
382+
365383
@Test
366-
void testVarargsOptionalInvocation() {
384+
void varargsOptionalInvocation() {
367385
// Calling 'public String optionalVarargsMethod(Optional<String>... values)'
368386
evaluate("optionalVarargsMethod()", "[]", String.class);
369387
evaluate("optionalVarargsMethod(new String[0])", "[]", String.class);
@@ -379,12 +397,12 @@ void testVarargsOptionalInvocation() {
379397
}
380398

381399
@Test
382-
void testInvocationOnNullContextObject() {
400+
void invocationOnNullContextObject() {
383401
evaluateAndCheckError("null.toString()",SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED);
384402
}
385403

386404
@Test
387-
void testMethodOfClass() {
405+
void methodOfClass() {
388406
Expression expression = parser.parseExpression("getName()");
389407
Object value = expression.getValue(new StandardEvaluationContext(String.class));
390408
assertThat(value).isEqualTo("java.lang.String");

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

+28
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.springframework.expression.spel.standard.SpelExpressionParser;
2222
import org.springframework.expression.spel.support.StandardEvaluationContext;
23+
import org.springframework.expression.spel.support.StandardTypeLocator;
2324

2425
import static org.assertj.core.api.Assertions.assertThat;
2526
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -211,6 +212,33 @@ void functionFromMethodHandleWithVarargsAndPrimitiveArrayToObjectArrayConversion
211212
evaluate("#formatObjectVarargs('x -> %s %s %s', new int[]{1, 2, 3})", "x -> 1 2 3", String.class); // int[] to Object[]
212213
}
213214

215+
@Test // gh-33315
216+
void functionFromMethodWithListConvertedToVarargsArray() {
217+
((StandardTypeLocator) context.getTypeLocator()).registerImport("java.util");
218+
String expected = "[a, b, c]";
219+
220+
evaluate("#varargsFunction(T(List).of('a', 'b', 'c'))", expected, String.class);
221+
evaluate("#varargsFunction({'a', 'b', 'c'})", expected, String.class);
222+
223+
// Calling 'public String formatObjectVarargs(String format, Object... args)' -> String.format(format, args)
224+
evaluate("#varargsObjectFunction(T(List).of('a', 'b', 'c'))", expected, String.class);
225+
evaluate("#varargsObjectFunction({'a', 'b', 'c'})", expected, String.class);
226+
}
227+
228+
@Test // gh-33315
229+
void functionFromMethodHandleWithListConvertedToVarargsArray() {
230+
((StandardTypeLocator) context.getTypeLocator()).registerImport("java.util");
231+
String expected = "x -> a b c";
232+
233+
// Calling 'public static String message(String template, String... args)' -> template.formatted((Object[]) args)
234+
evaluate("#message('x -> %s %s %s', T(List).of('a', 'b', 'c'))", expected, String.class);
235+
evaluate("#message('x -> %s %s %s', {'a', 'b', 'c'})", expected, String.class);
236+
237+
// Calling 'public static String formatObjectVarargs(String format, Object... args)' -> String.format(format, args)
238+
evaluate("#formatObjectVarargs('x -> %s %s %s', T(List).of('a', 'b', 'c'))", expected, String.class);
239+
evaluate("#formatObjectVarargs('x -> %s %s %s', {'a', 'b', 'c'})", expected, String.class);
240+
}
241+
214242
@Test
215243
void functionMethodMustBeStatic() throws Exception {
216244
SpelExpressionParser parser = new SpelExpressionParser();

spring-expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java

+5-9
Original file line numberDiff line numberDiff line change
@@ -119,20 +119,16 @@ public PlaceOfBirth getPlaceOfBirth() {
119119

120120
public int throwException(int valueIn) throws Exception {
121121
counter++;
122-
if (valueIn==1) {
123-
throw new IllegalArgumentException("IllegalArgumentException for 1");
124-
}
125-
if (valueIn==2) {
126-
throw new RuntimeException("RuntimeException for 2");
127-
}
128-
if (valueIn==4) {
129-
throw new TestException();
122+
switch (valueIn) {
123+
case 1 -> throw new IllegalArgumentException("IllegalArgumentException for 1");
124+
case 2 -> throw new RuntimeException("RuntimeException for 2");
125+
case 4 -> throw new TestException();
130126
}
131127
return valueIn;
132128
}
133129

134130
@SuppressWarnings("serial")
135-
static class TestException extends Exception {}
131+
public static class TestException extends Exception {}
136132

137133
public String throwException(PlaceOfBirth pob) {
138134
return pob.getCity();

0 commit comments

Comments
 (0)