Skip to content

Commit f51be0a

Browse files
LeMikaelFsbrannen
authored andcommitted
Support varargs invocations in SpEL for varargs array subtype
Closes gh-32704
1 parent 1d2b5a1 commit f51be0a

File tree

3 files changed

+108
-21
lines changed

3 files changed

+108
-21
lines changed

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

+13-4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import org.springframework.util.CollectionUtils;
3737
import org.springframework.util.MethodInvoker;
3838

39+
import static org.springframework.util.ObjectUtils.isArray;
40+
3941
/**
4042
* Utility methods used by the reflection resolver code to discover the appropriate
4143
* methods/constructors and fields that should be used in expressions.
@@ -451,14 +453,18 @@ private static boolean isFirstEntryInArray(Object value, @Nullable Object possib
451453
* @return a repackaged array of arguments where any varargs setup has performed
452454
*/
453455
public static Object[] setupArgumentsForVarargsInvocation(Class<?>[] requiredParameterTypes, Object... args) {
454-
// Check if array already built for final argument
455456
int parameterCount = requiredParameterTypes.length;
457+
Assert.notEmpty(requiredParameterTypes, "Required parameter types must not be empty");
458+
459+
Class<?> lastRequiredParameterType = requiredParameterTypes[parameterCount - 1];
460+
Assert.isTrue(lastRequiredParameterType.isArray(), "Method must be varargs");
461+
456462
int argumentCount = args.length;
463+
Object lastArgument = argumentCount > 0 ? args[argumentCount - 1] : null;
457464

458465
// Check if repackaging is needed...
459466
if (parameterCount != args.length ||
460-
requiredParameterTypes[parameterCount - 1] !=
461-
(args[argumentCount - 1] != null ? args[argumentCount - 1].getClass() : null)) {
467+
(!isArray(lastArgument) && differentTypes(lastRequiredParameterType, lastArgument))) {
462468

463469
// Create an array for the leading arguments plus the varargs array argument.
464470
Object[] newArgs = new Object[parameterCount];
@@ -471,7 +477,7 @@ public static Object[] setupArgumentsForVarargsInvocation(Class<?>[] requiredPar
471477
if (argumentCount >= parameterCount) {
472478
varargsArraySize = argumentCount - (parameterCount - 1);
473479
}
474-
Class<?> componentType = requiredParameterTypes[parameterCount - 1].componentType();
480+
Class<?> componentType = lastRequiredParameterType.componentType();
475481
Object varargsArray = Array.newInstance(componentType, varargsArraySize);
476482
for (int i = 0; i < varargsArraySize; i++) {
477483
Array.set(varargsArray, i, args[parameterCount - 1 + i]);
@@ -483,6 +489,9 @@ public static Object[] setupArgumentsForVarargsInvocation(Class<?>[] requiredPar
483489
return args;
484490
}
485491

492+
private static boolean differentTypes(Class<?> lastRequiredParameterType, @Nullable Object lastArgument) {
493+
return lastArgument == null || lastRequiredParameterType != lastArgument.getClass();
494+
}
486495

487496
/**
488497
* Arguments match kinds.

Diff for: spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java

+25-10
Original file line numberDiff line numberDiff line change
@@ -4221,16 +4221,27 @@ void methodReferenceVarargs() {
42214221
assertThat(tc.s).isEqualTo("aaabbbccc");
42224222
tc.reset();
42234223

4224-
// TODO Fails related to conversion service converting a String[] to satisfy Object...
4225-
// expression = parser.parseExpression("sixteen(stringArray)");
4226-
// assertCantCompile(expression);
4227-
// expression.getValue(tc);
4228-
// assertEquals("aaabbbccc", tc.s);
4229-
// assertCanCompile(expression);
4230-
// tc.reset();
4231-
// expression.getValue(tc);
4232-
// assertEquals("aaabbbccc", tc.s);
4233-
// tc.reset();
4224+
expression = parser.parseExpression("sixteen(seventeen)");
4225+
assertCantCompile(expression);
4226+
expression.getValue(tc);
4227+
assertThat(tc.s).isEqualTo("aaabbbccc");
4228+
assertCanCompile(expression);
4229+
tc.reset();
4230+
// see TODO below
4231+
// expression.getValue(tc);
4232+
// assertThat(tc.s).isEqualTo("aaabbbccc");
4233+
// tc.reset();
4234+
4235+
// TODO Determine why the String[] is passed as the first element of the Object... varargs array instead of the entire varargs array.
4236+
// expression = parser.parseExpression("sixteen(stringArray)");
4237+
// assertCantCompile(expression);
4238+
// expression.getValue(tc);
4239+
// assertThat(tc.s).isEqualTo("aaabbbccc");
4240+
// assertCanCompile(expression);
4241+
// tc.reset();
4242+
// expression.getValue(tc);
4243+
// assertThat(tc.s).isEqualTo("aaabbbccc");
4244+
// tc.reset();
42344245

42354246
// varargs int
42364247
expression = parser.parseExpression("twelve(1,2,3)");
@@ -6089,6 +6100,10 @@ public void sixteen(Object... vargs) {
60896100
}
60906101
}
60916102
}
6103+
6104+
public String[] seventeen() {
6105+
return new String[] { "aaa", "bbb", "ccc" };
6106+
}
60926107
}
60936108

60946109

Diff for: spring-expression/src/test/java/org/springframework/expression/spel/support/ReflectionHelperTests.java

+70-7
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040

4141
import static org.assertj.core.api.Assertions.assertThat;
4242
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
43+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
44+
import static org.assertj.core.api.InstanceOfAssertFactories.array;
4345
import static org.springframework.expression.spel.support.ReflectionHelper.ArgumentsMatchKind.CLOSE;
4446
import static org.springframework.expression.spel.support.ReflectionHelper.ArgumentsMatchKind.EXACT;
4547
import static org.springframework.expression.spel.support.ReflectionHelper.ArgumentsMatchKind.REQUIRES_CONVERSION;
@@ -252,14 +254,75 @@ void convertAllArguments() throws Exception {
252254

253255
@Test
254256
void setupArgumentsForVarargsInvocation() {
255-
Object[] newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
256-
new Class<?>[] {String[].class}, "a", "b", "c");
257+
Object[] newArray;
257258

258-
assertThat(newArray).hasSize(1);
259-
Object firstParam = newArray[0];
260-
assertThat(firstParam.getClass().componentType()).isEqualTo(String.class);
261-
Object[] firstParamArray = (Object[]) firstParam;
262-
assertThat(firstParamArray).containsExactly("a", "b", "c");
259+
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
260+
new Class<?>[] {String[].class}, "a", "b", "c");
261+
assertThat(newArray)
262+
.singleElement()
263+
.asInstanceOf(array(String[].class))
264+
.containsExactly("a", "b", "c");
265+
266+
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
267+
new Class<?>[] { Object[].class }, "a", "b", "c");
268+
assertThat(newArray)
269+
.singleElement()
270+
.asInstanceOf(array(Object[].class))
271+
.containsExactly("a", "b", "c");
272+
273+
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
274+
new Class<?>[] { Integer.class, Integer.class, String[].class }, 123, 456, "a", "b", "c");
275+
assertThat(newArray)
276+
.satisfiesExactly(
277+
i -> assertThat(i).isEqualTo(123),
278+
i -> assertThat(i).isEqualTo(456),
279+
i -> assertThat(i).asInstanceOf(array(String[].class)).containsExactly("a", "b", "c"));
280+
281+
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
282+
new Class<?>[] { String[].class });
283+
assertThat(newArray)
284+
.singleElement()
285+
.asInstanceOf(array(String[].class))
286+
.isEmpty();
287+
288+
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
289+
new Class<?>[] { String[].class }, new Object[] { new String[] { "a", "b", "c" } });
290+
assertThat(newArray)
291+
.singleElement()
292+
.asInstanceOf(array(String[].class))
293+
.containsExactly("a", "b", "c");
294+
295+
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
296+
new Class<?>[] { Object[].class }, new Object[] { new String[] { "a", "b", "c" } });
297+
assertThat(newArray)
298+
.singleElement()
299+
.asInstanceOf(array(Object[].class))
300+
.containsExactly("a", "b", "c");
301+
302+
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
303+
new Class<?>[] { String[].class }, "a");
304+
assertThat(newArray)
305+
.singleElement()
306+
.asInstanceOf(array(String[].class))
307+
.containsExactly("a");
308+
309+
310+
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
311+
new Class<?>[] { String[].class }, new Object[]{null});
312+
assertThat(newArray)
313+
.singleElement()
314+
.asInstanceOf(array(String[].class))
315+
.singleElement()
316+
.isNull();
317+
318+
assertThatThrownBy(() -> ReflectionHelper.setupArgumentsForVarargsInvocation(
319+
new Class<?>[] { Integer.class, Integer.class }, 123, 456))
320+
.isInstanceOf(IllegalArgumentException.class)
321+
.hasMessage("Method must be varargs");
322+
323+
assertThatThrownBy(() -> ReflectionHelper.setupArgumentsForVarargsInvocation(new Class[] {}, "a", "b", "c"))
324+
.isInstanceOf(IllegalArgumentException.class)
325+
.hasMessage("Required parameter types must not be empty");
263326
}
264327

265328
@Test

0 commit comments

Comments
 (0)