Skip to content

Commit 09d8e44

Browse files
committed
Merge branch '6.1.x'
2 parents fbe781b + ae5dd54 commit 09d8e44

File tree

6 files changed

+182
-17
lines changed

6 files changed

+182
-17
lines changed

spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.regex.Pattern;
4444
import java.util.stream.Stream;
4545

46+
import org.junit.jupiter.api.Disabled;
4647
import org.junit.jupiter.api.Test;
4748

4849
import org.springframework.core.MethodParameter;
@@ -603,6 +604,12 @@ void convertStringArrayToIntArray() {
603604
assertThat(result).containsExactly(1, 2, 3);
604605
}
605606

607+
@Test
608+
void convertIntArrayToStringArray() {
609+
String[] result = conversionService.convert(new int[] {1, 2, 3}, String[].class);
610+
assertThat(result).containsExactly("1", "2", "3");
611+
}
612+
606613
@Test
607614
void convertIntegerArrayToIntegerArray() {
608615
Integer[] result = conversionService.convert(new Integer[] {1, 2, 3}, Integer[].class);
@@ -615,6 +622,12 @@ void convertIntegerArrayToIntArray() {
615622
assertThat(result).containsExactly(1, 2, 3);
616623
}
617624

625+
@Test
626+
void convertIntArrayToIntegerArray() {
627+
Integer[] result = conversionService.convert(new int[] {1, 2}, Integer[].class);
628+
assertThat(result).containsExactly(1, 2);
629+
}
630+
618631
@Test
619632
void convertObjectArrayToIntegerArray() {
620633
Integer[] result = conversionService.convert(new Object[] {1, 2, 3}, Integer[].class);
@@ -627,15 +640,34 @@ void convertObjectArrayToIntArray() {
627640
assertThat(result).containsExactly(1, 2, 3);
628641
}
629642

643+
@Disabled("Primitive array to Object[] conversion is not currently supported")
644+
@Test
645+
void convertIntArrayToObjectArray() {
646+
Object[] result = conversionService.convert(new int[] {1, 2}, Object[].class);
647+
assertThat(result).containsExactly(1, 2);
648+
}
649+
650+
@Test
651+
void convertIntArrayToFloatArray() {
652+
Float[] result = conversionService.convert(new int[] {1, 2}, Float[].class);
653+
assertThat(result).containsExactly(1.0F, 2.0F);
654+
}
655+
656+
@Test
657+
void convertIntArrayToPrimitiveFloatArray() {
658+
float[] result = conversionService.convert(new int[] {1, 2}, float[].class);
659+
assertThat(result).containsExactly(1.0F, 2.0F);
660+
}
661+
630662
@Test
631-
void convertByteArrayToWrapperArray() {
663+
void convertPrimitiveByteArrayToByteWrapperArray() {
632664
byte[] byteArray = {1, 2, 3};
633665
Byte[] converted = conversionService.convert(byteArray, Byte[].class);
634666
assertThat(converted).isEqualTo(new Byte[]{1, 2, 3});
635667
}
636668

637669
@Test
638-
void convertArrayToArrayAssignable() {
670+
void convertIntArrayToIntArray() {
639671
int[] result = conversionService.convert(new int[] {1, 2, 3}, int[].class);
640672
assertThat(result).containsExactly(1, 2, 3);
641673
}

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

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -383,29 +383,37 @@ public static boolean convertAllMethodHandleArguments(TypeConverter converter, O
383383
conversionOccurred |= (argument != arguments[i]);
384384
}
385385

386-
Class<?> varArgClass = methodHandleType.lastParameterType();
387-
ResolvableType varArgResolvableType = ResolvableType.forClass(varArgClass);
388-
TypeDescriptor targetType = new TypeDescriptor(varArgResolvableType, varArgClass.componentType(), null);
389-
TypeDescriptor componentTypeDesc = targetType.getElementTypeDescriptor();
390-
Assert.state(componentTypeDesc != null, "Component type must not be null for a varargs array");
386+
Class<?> varargsArrayClass = methodHandleType.lastParameterType();
387+
// We use the wrapper type for a primitive varargs array, since we eventually
388+
// need an Object array in order to invoke the MethodHandle in
389+
// FunctionReference#executeFunctionViaMethodHandle().
390+
Class<?> varargsComponentClass = ClassUtils.resolvePrimitiveIfNecessary(varargsArrayClass.componentType());
391+
TypeDescriptor varargsArrayType = TypeDescriptor.array(TypeDescriptor.valueOf(varargsComponentClass));
392+
Assert.state(varargsArrayType != null, "Array type must not be null for a varargs array");
393+
TypeDescriptor varargsComponentType = varargsArrayType.getElementTypeDescriptor();
394+
Assert.state(varargsComponentType != null, "Component type must not be null for a varargs array");
391395

392396
// If the target is varargs and there is just one more argument, then convert it here.
393397
if (varargsPosition == arguments.length - 1) {
394398
Object argument = arguments[varargsPosition];
395399
TypeDescriptor sourceType = TypeDescriptor.forObject(argument);
396400
if (argument == null) {
397401
// Perform the equivalent of GenericConversionService.convertNullSource() for a single argument.
398-
if (componentTypeDesc.getObjectType() == Optional.class) {
402+
if (varargsComponentType.getObjectType() == Optional.class) {
399403
arguments[varargsPosition] = Optional.empty();
400404
conversionOccurred = true;
401405
}
402406
}
403407
// If the argument type is assignable to the varargs component type, there is no need to
404-
// convert it or wrap it in an array. For example, using StringToArrayConverter to
405-
// convert a String containing a comma would result in the String being split and
406-
// repackaged in an array when it should be used as-is.
407-
else if (!sourceType.isAssignableTo(componentTypeDesc)) {
408-
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetType);
408+
// convert it. For example, using StringToArrayConverter to convert a String containing a
409+
// comma would result in the String being split and repackaged in an array when it should
410+
// be used as-is. Similarly, if the argument is an array that is assignable to the varargs
411+
// array type, there is no need to convert it.
412+
else if (!sourceType.isAssignableTo(varargsComponentType) ||
413+
(sourceType.isArray() && !sourceType.isAssignableTo(varargsArrayType))) {
414+
415+
TypeDescriptor targetTypeToUse = (sourceType.isArray() ? varargsArrayType : varargsComponentType);
416+
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetTypeToUse);
409417
}
410418
// Possible outcomes of the above if-else block:
411419
// 1) the input argument was null, and nothing was done.
@@ -423,7 +431,7 @@ else if (!sourceType.isAssignableTo(componentTypeDesc)) {
423431
for (int i = varargsPosition; i < arguments.length; i++) {
424432
Object argument = arguments[i];
425433
TypeDescriptor sourceType = TypeDescriptor.forObject(argument);
426-
arguments[i] = converter.convertValue(argument, sourceType, componentTypeDesc);
434+
arguments[i] = converter.convertValue(argument, sourceType, varargsComponentType);
427435
conversionOccurred |= (argument != arguments[i]);
428436
}
429437
}

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.ArrayList;
2424
import java.util.List;
2525

26+
import org.junit.jupiter.api.Disabled;
2627
import org.junit.jupiter.api.Test;
2728

2829
import org.springframework.expression.Expression;
@@ -245,6 +246,7 @@ void testVarargsInvocation01() {
245246
evaluate("aVarargsMethod(1,'a',3.0d)", "[1, a, 3.0]", String.class); // first and last need conversion
246247
evaluate("aVarargsMethod(new String[]{'a','b','c'})", "[a, b, c]", String.class);
247248
evaluate("aVarargsMethod(new String[]{})", "[]", String.class);
249+
evaluate("aVarargsMethod(new int[]{1, 2, 3})", "[1, 2, 3]", String.class); // needs int[] to String[] conversion
248250
evaluate("aVarargsMethod(null)", "[null]", String.class);
249251
evaluate("aVarargsMethod(null,'a')", "[null, a]", String.class);
250252
evaluate("aVarargsMethod('a',null,'b')", "[a, null, b]", String.class);
@@ -320,6 +322,7 @@ void testVarargsWithObjectArrayType() {
320322
// Conversion necessary
321323
evaluate("formatObjectVarargs('x -> %s %s', 2, 3)", "x -> 2 3", String.class);
322324
evaluate("formatObjectVarargs('x -> %s %s', 'a', 3.0d)", "x -> a 3.0", String.class);
325+
evaluate("formatObjectVarargs('x -> %s %s %s', new Integer[]{1, 2, 3})", "x -> 1 2 3", String.class);
323326

324327
// Individual string contains a comma with multiple varargs arguments
325328
evaluate("formatObjectVarargs('foo -> %s %s', ',', 'baz')", "foo -> , baz", String.class);
@@ -333,6 +336,34 @@ void testVarargsWithObjectArrayType() {
333336
evaluate("formatObjectVarargs('foo -> %s', 'bar,baz')", "foo -> bar,baz", String.class);
334337
}
335338

339+
@Test
340+
void testVarargsWithPrimitiveArrayType() {
341+
// Calling 'public String formatPrimitiveVarargs(String format, int... nums)' -> effectively String.format(format, args)
342+
343+
// No var-args and no conversion necessary
344+
evaluate("formatPrimitiveVarargs(9)", "9", String.class);
345+
346+
// No var-args but conversion necessary
347+
evaluate("formatPrimitiveVarargs('7')", "7", String.class);
348+
349+
// No conversion necessary
350+
evaluate("formatPrimitiveVarargs('x -> %s', 9)", "x -> 9", String.class);
351+
evaluate("formatPrimitiveVarargs('x -> %s %s %s', 1, 2, 3)", "x -> 1 2 3", String.class);
352+
evaluate("formatPrimitiveVarargs('x -> %s', new int[]{1})", "x -> 1", String.class);
353+
evaluate("formatPrimitiveVarargs('x -> %s %s %s', new int[]{1, 2, 3})", "x -> 1 2 3", String.class);
354+
355+
// Conversion necessary
356+
evaluate("formatPrimitiveVarargs('x -> %s %s', '2', '3')", "x -> 2 3", String.class);
357+
evaluate("formatPrimitiveVarargs('x -> %s %s', '2', 3.0d)", "x -> 2 3", String.class);
358+
}
359+
360+
@Disabled("Primitive array to Object[] conversion is not currently supported")
361+
@Test
362+
void testVarargsWithPrimitiveArrayToObjectArrayConversion() {
363+
evaluate("formatObjectVarargs('x -> %s %s %s', new short[]{1, 2, 3})", "x -> 1 2 3", String.class); // short[] to Object[]
364+
evaluate("formatObjectVarargs('x -> %s %s %s', new int[]{1, 2, 3})", "x -> 1 2 3", String.class); // int[] to Object[]
365+
}
366+
336367
@Test
337368
void testVarargsOptionalInvocation() {
338369
// Calling 'public String optionalVarargsMethod(Optional<String>... values)'

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ private static void populateFunctions(StandardEvaluationContext testContext) {
6666
TestScenarioCreator.class.getDeclaredMethod("varargsFunction", String[].class));
6767
testContext.registerFunction("varargsFunction2",
6868
TestScenarioCreator.class.getDeclaredMethod("varargsFunction2", int.class, String[].class));
69+
testContext.registerFunction("varargsObjectFunction",
70+
TestScenarioCreator.class.getDeclaredMethod("varargsObjectFunction", Object[].class));
6971
}
7072
catch (Exception ex) {
7173
throw new IllegalStateException(ex);
@@ -106,6 +108,11 @@ private static void populateMethodHandles(StandardEvaluationContext testContext)
106108
"formatObjectVarargs", MethodType.methodType(String.class, String.class, Object[].class));
107109
testContext.registerFunction("formatObjectVarargs", formatObjectVarargs);
108110

111+
// #formatObjectVarargs(format, args...)
112+
MethodHandle formatPrimitiveVarargs = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
113+
"formatPrimitiveVarargs", MethodType.methodType(String.class, String.class, int[].class));
114+
testContext.registerFunction("formatPrimitiveVarargs", formatPrimitiveVarargs);
115+
109116
// #add(int, int)
110117
MethodHandle add = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
111118
"add", MethodType.methodType(int.class, int.class, int.class));
@@ -160,6 +167,10 @@ public static String varargsFunction2(int i, String... strings) {
160167
return i + "-" + Arrays.toString(strings);
161168
}
162169

170+
public static String varargsObjectFunction(Object... args) {
171+
return Arrays.toString(args);
172+
}
173+
163174
public static String message(String template, String... args) {
164175
return template.formatted((Object[]) args);
165176
}
@@ -168,6 +179,14 @@ public static String formatObjectVarargs(String format, Object... args) {
168179
return String.format(format, args);
169180
}
170181

182+
public static String formatPrimitiveVarargs(String format, int... nums) {
183+
Object[] args = new Object[nums.length];
184+
for (int i = 0; i < nums.length; i++) {
185+
args[i] = nums[i];
186+
}
187+
return String.format(format, args);
188+
}
189+
171190
public static int add(int x, int y) {
172191
return x + y;
173192
}

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

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.expression.spel;
1818

19+
import org.junit.jupiter.api.Disabled;
1920
import org.junit.jupiter.api.Test;
2021

2122
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -80,9 +81,11 @@ void functionWithVarargs() {
8081
evaluate("#varargsFunction(new String[0])", "[]", String.class);
8182
evaluate("#varargsFunction('a')", "[a]", String.class);
8283
evaluate("#varargsFunction('a','b','c')", "[a, b, c]", String.class);
84+
evaluate("#varargsFunction(new String[]{'a','b','c'})", "[a, b, c]", String.class);
8385
// Conversion from int to String
8486
evaluate("#varargsFunction(25)", "[25]", String.class);
8587
evaluate("#varargsFunction('b',25)", "[b, 25]", String.class);
88+
evaluate("#varargsFunction(new int[]{1, 2, 3})", "[1, 2, 3]", String.class);
8689
// Strings that contain a comma
8790
evaluate("#varargsFunction('a,b')", "[a,b]", String.class);
8891
evaluate("#varargsFunction('a', 'x,y', 'd')", "[a, x,y, d]", String.class);
@@ -103,24 +106,47 @@ void functionWithVarargs() {
103106
// null values
104107
evaluate("#varargsFunction2(9,null)", "9-[null]", String.class);
105108
evaluate("#varargsFunction2(9,'a',null,'b')", "9-[a, null, b]", String.class);
109+
110+
evaluate("#varargsObjectFunction()", "[]", String.class);
111+
evaluate("#varargsObjectFunction(new String[0])", "[]", String.class);
112+
evaluate("#varargsObjectFunction('a')", "[a]", String.class);
113+
evaluate("#varargsObjectFunction('a','b','c')", "[a, b, c]", String.class);
114+
evaluate("#varargsObjectFunction(new String[]{'a','b','c'})", "[a, b, c]", String.class);
115+
// Conversion from int to String
116+
evaluate("#varargsObjectFunction(25)", "[25]", String.class);
117+
evaluate("#varargsObjectFunction('b',25)", "[b, 25]", String.class);
118+
// Strings that contain a comma
119+
evaluate("#varargsObjectFunction('a,b')", "[a,b]", String.class);
120+
evaluate("#varargsObjectFunction('a', 'x,y', 'd')", "[a, x,y, d]", String.class);
121+
// null values
122+
evaluate("#varargsObjectFunction(null)", "[null]", String.class);
123+
evaluate("#varargsObjectFunction('a',null,'b')", "[a, null, b]", String.class);
106124
}
107125

108126
@Test // gh-33013
109127
void functionWithVarargsViaMethodHandle() {
110128
// Calling 'public static String formatObjectVarargs(String format, Object... args)' -> String.format(format, args)
111129

112130
// No var-args and no conversion necessary
131+
evaluate("#message('x')", "x", String.class);
113132
evaluate("#formatObjectVarargs('x')", "x", String.class);
114133

115134
// No var-args but conversion necessary
135+
evaluate("#message(9)", "9", String.class);
116136
evaluate("#formatObjectVarargs(9)", "9", String.class);
117137

118138
// No conversion necessary
119139
evaluate("#add(3, 4)", 7, Integer.class);
140+
evaluate("#message('x -> %s %s %s', 'a', 'b', 'c')", "x -> a b c", String.class);
120141
evaluate("#formatObjectVarargs('x -> %s', '')", "x -> ", String.class);
121142
evaluate("#formatObjectVarargs('x -> %s', ' ')", "x -> ", String.class);
122143
evaluate("#formatObjectVarargs('x -> %s', 'a')", "x -> a", String.class);
123144
evaluate("#formatObjectVarargs('x -> %s %s %s', 'a', 'b', 'c')", "x -> a b c", String.class);
145+
evaluate("#message('x -> %s %s %s', new Object[]{'a', 'b', 'c'})", "x -> a b c", String.class); // Object[] instanceof Object[]
146+
evaluate("#message('x -> %s %s %s', new String[]{'a', 'b', 'c'})", "x -> a b c", String.class); // String[] instanceof Object[]
147+
evaluate("#message('x -> %s %s %s', new Integer[]{1, 2, 3})", "x -> 1 2 3", String.class); // Integer[] instanceof Object[]
148+
evaluate("#formatObjectVarargs('x -> %s %s', 2, 3)", "x -> 2 3", String.class); // Integer instanceof Object
149+
evaluate("#formatObjectVarargs('x -> %s %s', 'a', 3.0F)", "x -> a 3.0", String.class); // String/Float instanceof Object
124150
evaluate("#formatObjectVarargs('x -> %s', new Object[]{''})", "x -> ", String.class);
125151
evaluate("#formatObjectVarargs('x -> %s', new String[]{''})", "x -> ", String.class);
126152
evaluate("#formatObjectVarargs('x -> %s', new Object[]{' '})", "x -> ", String.class);
@@ -131,9 +157,12 @@ void functionWithVarargsViaMethodHandle() {
131157
evaluate("#formatObjectVarargs('x -> %s %s %s', new String[]{'a', 'b', 'c'})", "x -> a b c", String.class);
132158

133159
// Conversion necessary
134-
evaluate("#add('2', 5.0)", 7, Integer.class);
135-
evaluate("#formatObjectVarargs('x -> %s %s', 2, 3)", "x -> 2 3", String.class);
136-
evaluate("#formatObjectVarargs('x -> %s %s', 'a', 3.0d)", "x -> a 3.0", String.class);
160+
evaluate("#add('2', 5.0)", 7, Integer.class); // String/Double to Integer
161+
evaluate("#messageStatic('x -> %s %s %s', 1, 2, 3)", "x -> 1 2 3", String.class); // Integer to String
162+
evaluate("#messageStatic('x -> %s %s %s', new Integer[]{1, 2, 3})", "x -> 1 2 3", String.class); // Integer[] to String[]
163+
evaluate("#messageStatic('x -> %s %s %s', new int[]{1, 2, 3})", "x -> 1 2 3", String.class); // int[] to String[]
164+
evaluate("#messageStatic('x -> %s %s %s', new short[]{1, 2, 3})", "x -> 1 2 3", String.class); // short[] to String[]
165+
evaluate("#formatObjectVarargs('x -> %s %s %s', new Integer[]{1, 2, 3})", "x -> 1 2 3", String.class); // Integer[] to String[]
137166

138167
// Individual string contains a comma with multiple varargs arguments
139168
evaluate("#formatObjectVarargs('foo -> %s %s', ',', 'baz')", "foo -> , baz", String.class);
@@ -147,6 +176,44 @@ void functionWithVarargsViaMethodHandle() {
147176
evaluate("#formatObjectVarargs('foo -> %s', 'bar,baz')", "foo -> bar,baz", String.class);
148177
}
149178

179+
@Test
180+
void functionWithPrimitiveVarargsViaMethodHandle() {
181+
// Calling 'public String formatPrimitiveVarargs(String format, int... nums)' -> effectively String.format(format, args)
182+
183+
// No var-args and no conversion necessary
184+
evaluate("#formatPrimitiveVarargs(9)", "9", String.class);
185+
186+
// No var-args but conversion necessary
187+
evaluate("#formatPrimitiveVarargs('7')", "7", String.class);
188+
189+
// No conversion necessary
190+
evaluate("#formatPrimitiveVarargs('x -> %s', 9)", "x -> 9", String.class);
191+
evaluate("#formatPrimitiveVarargs('x -> %s %s %s', 1, 2, 3)", "x -> 1 2 3", String.class);
192+
evaluate("#formatPrimitiveVarargs('x -> %s', new int[]{1})", "x -> 1", String.class);
193+
evaluate("#formatPrimitiveVarargs('x -> %s %s %s', new int[]{1, 2, 3})", "x -> 1 2 3", String.class);
194+
195+
// Conversion necessary
196+
evaluate("#formatPrimitiveVarargs('x -> %s %s', '2', '3')", "x -> 2 3", String.class); // String to int
197+
evaluate("#formatPrimitiveVarargs('x -> %s %s', '2', 3.0F)", "x -> 2 3", String.class); // String/Float to int
198+
evaluate("#formatPrimitiveVarargs('x -> %s %s %s', new Integer[]{1, 2, 3})", "x -> 1 2 3", String.class); // Integer[] to int[]
199+
evaluate("#formatPrimitiveVarargs('x -> %s %s %s', new String[]{'1', '2', '3'})", "x -> 1 2 3", String.class); // String[] to int[]
200+
}
201+
202+
@Disabled("Primitive array to Object[] conversion is not currently supported")
203+
@Test
204+
void functionFromMethodWithVarargsAndPrimitiveArrayToObjectArrayConversion() {
205+
evaluate("#varargsObjectFunction(new short[]{1, 2, 3})", "[1, 2, 3]", String.class); // short[] to Object[]
206+
evaluate("#varargsObjectFunction(new int[]{1, 2, 3})", "[1, 2, 3]", String.class); // int[] to Object[]
207+
}
208+
209+
@Disabled("Primitive array to Object[] conversion is not currently supported")
210+
@Test
211+
void functionFromMethodHandleWithVarargsAndPrimitiveArrayToObjectArrayConversion() {
212+
evaluate("#message('x -> %s %s %s', new short[]{1, 2, 3})", "x -> 1 2 3", String.class); // short[] to Object[]
213+
evaluate("#message('x -> %s %s %s', new int[]{1, 2, 3})", "x -> 1 2 3", String.class); // int[] to Object[]
214+
evaluate("#formatObjectVarargs('x -> %s %s %s', new int[]{1, 2, 3})", "x -> 1 2 3", String.class); // int[] to Object[]
215+
}
216+
150217
@Test
151218
void functionMethodMustBeStatic() throws Exception {
152219
SpelExpressionParser parser = new SpelExpressionParser();

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@ public String formatObjectVarargs(String format, Object... args) {
221221
return String.format(format, args);
222222
}
223223

224+
public String formatPrimitiveVarargs(String format, int... nums) {
225+
Object[] args = new Object[nums.length];
226+
for (int i = 0; i < nums.length; i++) {
227+
args[i] = nums[i];
228+
}
229+
return String.format(format, args);
230+
}
231+
224232

225233
public Inventor(String... strings) {
226234
if (strings.length > 0) {

0 commit comments

Comments
 (0)