Skip to content

Commit ee6402e

Browse files
committed
Merge branch '6.1.x'
2 parents 555e11b + c55c644 commit ee6402e

File tree

6 files changed

+184
-76
lines changed

6 files changed

+184
-76
lines changed

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

Lines changed: 67 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,20 @@ public abstract class ReflectionHelper {
4949

5050
/**
5151
* Compare argument arrays and return information about whether they match.
52-
* <p>A supplied type converter and conversionAllowed flag allow for matches to take
53-
* into account that a type may be transformed into a different type by the converter.
52+
* <p>The supplied type converter allows for matches to take into account that a type
53+
* may be transformed into a different type by the converter.
5454
* @param expectedArgTypes the types the method/constructor is expecting
5555
* @param suppliedArgTypes the types that are being supplied at the point of invocation
5656
* @param typeConverter a registered type converter
57-
* @return a MatchInfo object indicating what kind of match it was,
57+
* @return an {@code ArgumentsMatchInfo} object indicating what kind of match it was,
5858
* or {@code null} if it was not a match
5959
*/
6060
@Nullable
6161
static ArgumentsMatchInfo compareArguments(
6262
List<TypeDescriptor> expectedArgTypes, List<TypeDescriptor> suppliedArgTypes, TypeConverter typeConverter) {
6363

6464
Assert.isTrue(expectedArgTypes.size() == suppliedArgTypes.size(),
65-
"Expected argument types and supplied argument types should be arrays of same length");
65+
"Expected argument types and supplied argument types should be lists of the same size");
6666

6767
ArgumentsMatchKind match = ArgumentsMatchKind.EXACT;
6868
for (int i = 0; i < expectedArgTypes.size() && match != null; i++) {
@@ -136,13 +136,14 @@ else if (ClassUtils.isAssignable(paramTypeClazz, superClass)) {
136136

137137
/**
138138
* Compare argument arrays and return information about whether they match.
139-
* <p>A supplied type converter and conversionAllowed flag allow for matches to
140-
* take into account that a type may be transformed into a different type by the
141-
* converter. This variant of {@link #compareArguments} also allows for a varargs match.
139+
* <p>The supplied type converter allows for matches to take into account that a type
140+
* may be transformed into a different type by the converter.
141+
* <p>This variant of {@link #compareArguments(List, List, TypeConverter)} also allows
142+
* for a varargs match.
142143
* @param expectedArgTypes the types the method/constructor is expecting
143144
* @param suppliedArgTypes the types that are being supplied at the point of invocation
144145
* @param typeConverter a registered type converter
145-
* @return a MatchInfo object indicating what kind of match it was,
146+
* @return an {@code ArgumentsMatchInfo} object indicating what kind of match it was,
146147
* or {@code null} if it was not a match
147148
*/
148149
@Nullable
@@ -200,26 +201,26 @@ else if (typeConverter.canConvert(suppliedArg, expectedArg)) {
200201
// Now... we have the final argument in the method we are checking as a match and we have 0
201202
// or more other arguments left to pass to it.
202203
TypeDescriptor varargsDesc = expectedArgTypes.get(expectedArgTypes.size() - 1);
203-
TypeDescriptor elementDesc = varargsDesc.getElementTypeDescriptor();
204-
Assert.state(elementDesc != null, "No element type");
205-
Class<?> varargsParamType = elementDesc.getType();
204+
TypeDescriptor componentTypeDesc = varargsDesc.getElementTypeDescriptor();
205+
Assert.state(componentTypeDesc != null, "Component type must not be null for a varargs array");
206+
Class<?> varargsComponentType = componentTypeDesc.getType();
206207

207208
// All remaining parameters must be of this type or convertible to this type
208209
for (int i = expectedArgTypes.size() - 1; i < suppliedArgTypes.size(); i++) {
209210
TypeDescriptor suppliedArg = suppliedArgTypes.get(i);
210211
if (suppliedArg == null) {
211-
if (varargsParamType.isPrimitive()) {
212+
if (varargsComponentType.isPrimitive()) {
212213
match = null;
213214
}
214215
}
215216
else {
216-
if (varargsParamType != suppliedArg.getType()) {
217-
if (ClassUtils.isAssignable(varargsParamType, suppliedArg.getType())) {
217+
if (varargsComponentType != suppliedArg.getType()) {
218+
if (ClassUtils.isAssignable(varargsComponentType, suppliedArg.getType())) {
218219
if (match != ArgumentsMatchKind.REQUIRES_CONVERSION) {
219220
match = ArgumentsMatchKind.CLOSE;
220221
}
221222
}
222-
else if (typeConverter.canConvert(suppliedArg, TypeDescriptor.valueOf(varargsParamType))) {
223+
else if (typeConverter.canConvert(suppliedArg, TypeDescriptor.valueOf(varargsComponentType))) {
223224
match = ArgumentsMatchKind.REQUIRES_CONVERSION;
224225
}
225226
else {
@@ -234,25 +235,21 @@ else if (typeConverter.canConvert(suppliedArg, TypeDescriptor.valueOf(varargsPar
234235
}
235236

236237
/**
237-
* Convert the supplied set of arguments into the parameter types specified
238-
* by the supplied {@link Method}.
239-
* <p>The arguments are converted 'in-place' in the input array.
240-
* <p>If the method accepts varargs, the final entry in its parameterTypes
241-
* array is going to be an array itself whose component type will be used as
242-
* the conversion target for any additional arguments. For example, if the
243-
* parameterTypes are {Integer, String[]} and the input arguments are
244-
* {Integer, boolean, float}, then both the boolean and float must be converted
245-
* to strings.
246-
* <p>This method does <strong>not</strong> repackage the arguments into a
247-
* form suitable for the varargs invocation. A subsequent call to
248-
* {@link #setupArgumentsForVarargsInvocation(Class[], Object...)} must be
249-
* used for that.
238+
* Convert the supplied set of arguments into the parameter types of the supplied
239+
* {@link Method}.
240+
* <p>If the supplied method is a varargs method, the final parameter type must be an
241+
* array whose component type should be used as the conversion target for extraneous
242+
* arguments. For example, if the parameter types are <code>{Integer, String[]}</code>
243+
* and the input arguments are <code>{Integer, boolean, float}</code>, then both the
244+
* {@code boolean} and the {@code float} must be converted to strings.
245+
* <p>This method does <strong>not</strong> repackage the arguments into a form suitable
246+
* for the varargs invocation: a subsequent call to
247+
* {@link #setupArgumentsForVarargsInvocation(Class[], Object...)} is required for that.
250248
* @param converter the converter to use for type conversions
251-
* @param arguments the arguments to convert to the parameter types of the
252-
* target method
253-
* @param method the target method
254-
* @return true if some kind of conversion occurred on an argument
255-
* @throws SpelEvaluationException if there is a problem with conversion
249+
* @param arguments the arguments to convert to the required parameter types
250+
* @param method the target {@code Method}
251+
* @return {@code true} if some kind of conversion occurred on an argument
252+
* @throws SpelEvaluationException if a problem occurs during conversion
256253
*/
257254
public static boolean convertAllArguments(TypeConverter converter, Object[] arguments, Method method)
258255
throws SpelEvaluationException {
@@ -262,12 +259,12 @@ public static boolean convertAllArguments(TypeConverter converter, Object[] argu
262259
}
263260

264261
/**
265-
* Takes an input set of argument values and converts them to the parameter
266-
* types of the supplied {@link Executable} (i.e., constructor or method).
262+
* Convert the supplied set of arguments into the parameter types of the supplied
263+
* {@link Executable}, taking the varargs position into account.
267264
* <p>The arguments are converted 'in-place' in the input array.
268-
* @param converter the type converter to use for attempting conversions
269-
* @param arguments the actual arguments that need conversion
270-
* @param executable the target Method or Constructor
265+
* @param converter the converter to use for type conversions
266+
* @param arguments the arguments to convert to the required parameter types
267+
* @param executable the target {@code Method} or {@code Constructor}
271268
* @param varargsPosition the known position of the varargs argument, if any
272269
* ({@code null} if not varargs)
273270
* @return {@code true} if some kind of conversion occurred on an argument
@@ -295,30 +292,31 @@ static boolean convertArguments(TypeConverter converter, Object[] arguments, Exe
295292
}
296293

297294
MethodParameter methodParam = MethodParameter.forExecutable(executable, varargsPosition);
295+
TypeDescriptor targetType = new TypeDescriptor(methodParam);
296+
TypeDescriptor componentTypeDesc = targetType.getElementTypeDescriptor();
297+
Assert.state(componentTypeDesc != null, "Component type must not be null for a varargs array");
298298

299299
// If the target is varargs and there is just one more argument, then convert it here.
300300
if (varargsPosition == arguments.length - 1) {
301301
Object argument = arguments[varargsPosition];
302-
TypeDescriptor targetType = new TypeDescriptor(methodParam);
303302
TypeDescriptor sourceType = TypeDescriptor.forObject(argument);
304303
if (argument == null) {
305304
// Perform the equivalent of GenericConversionService.convertNullSource() for a single argument.
306-
TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();
307-
if (elementDesc != null && elementDesc.getObjectType() == Optional.class) {
305+
if (componentTypeDesc.getObjectType() == Optional.class) {
308306
arguments[varargsPosition] = Optional.empty();
309307
conversionOccurred = true;
310308
}
311309
}
312-
// If the argument type is equal to the varargs element type, there is no need to
310+
// If the argument type is assignable to the varargs component type, there is no need to
313311
// convert it or wrap it in an array. For example, using StringToArrayConverter to
314312
// convert a String containing a comma would result in the String being split and
315313
// repackaged in an array when it should be used as-is.
316-
else if (!sourceType.equals(targetType.getElementTypeDescriptor())) {
314+
else if (!sourceType.isAssignableTo(componentTypeDesc)) {
317315
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetType);
318316
}
319317
// Possible outcomes of the above if-else block:
320318
// 1) the input argument was null, and nothing was done.
321-
// 2) the input argument was null; the varargs element type is Optional; and the argument was converted to Optional.empty().
319+
// 2) the input argument was null; the varargs component type is Optional; and the argument was converted to Optional.empty().
322320
// 3) the input argument was correct type but not wrapped in an array, and nothing was done.
323321
// 4) the input argument was already compatible (i.e., array of valid type), and nothing was done.
324322
// 5) the input argument was the wrong type and got converted and wrapped in an array.
@@ -327,13 +325,12 @@ else if (!sourceType.equals(targetType.getElementTypeDescriptor())) {
327325
conversionOccurred = true; // case 5
328326
}
329327
}
330-
// Otherwise, convert remaining arguments to the varargs element type.
328+
// Otherwise, convert remaining arguments to the varargs component type.
331329
else {
332-
TypeDescriptor targetType = new TypeDescriptor(methodParam).getElementTypeDescriptor();
333-
Assert.state(targetType != null, "No element type");
334330
for (int i = varargsPosition; i < arguments.length; i++) {
335331
Object argument = arguments[i];
336-
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
332+
TypeDescriptor sourceType = TypeDescriptor.forObject(argument);
333+
arguments[i] = converter.convertValue(argument, sourceType, componentTypeDesc);
337334
conversionOccurred |= (argument != arguments[i]);
338335
}
339336
}
@@ -342,12 +339,12 @@ else if (!sourceType.equals(targetType.getElementTypeDescriptor())) {
342339
}
343340

344341
/**
345-
* Takes an input set of argument values and converts them to the parameter
346-
* types of the supplied {@link MethodHandle}.
342+
* Convert the supplied set of arguments into the parameter types of the supplied
343+
* {@link MethodHandle}, taking the varargs position into account.
347344
* <p>The arguments are converted 'in-place' in the input array.
348-
* @param converter the type converter to use for attempting conversions
349-
* @param arguments the actual arguments that need conversion
350-
* @param methodHandle the target MethodHandle
345+
* @param converter the converter to use for type conversions
346+
* @param arguments the arguments to convert to the required parameter types
347+
* @param methodHandle the target {@code MethodHandle}
351348
* @param varargsPosition the known position of the varargs argument, if any
352349
* ({@code null} if not varargs)
353350
* @return {@code true} if some kind of conversion occurred on an argument
@@ -358,7 +355,7 @@ public static boolean convertAllMethodHandleArguments(TypeConverter converter, O
358355
MethodHandle methodHandle, @Nullable Integer varargsPosition) throws EvaluationException {
359356

360357
boolean conversionOccurred = false;
361-
final MethodType methodHandleArgumentTypes = methodHandle.type();
358+
MethodType methodHandleArgumentTypes = methodHandle.type();
362359
if (varargsPosition == null) {
363360
for (int i = 0; i < arguments.length; i++) {
364361
Class<?> argumentClass = methodHandleArgumentTypes.parameterType(i);
@@ -382,32 +379,34 @@ public static boolean convertAllMethodHandleArguments(TypeConverter converter, O
382379
conversionOccurred |= (argument != arguments[i]);
383380
}
384381

385-
final Class<?> varArgClass = methodHandleArgumentTypes.lastParameterType().componentType();
382+
Class<?> varArgClass = methodHandleArgumentTypes.lastParameterType().componentType();
386383
ResolvableType varArgResolvableType = ResolvableType.forClass(varArgClass);
387-
TypeDescriptor varArgContentType = new TypeDescriptor(varArgResolvableType, varArgClass, null);
384+
TypeDescriptor varArgComponentType = new TypeDescriptor(varArgResolvableType, varArgClass, null);
385+
TypeDescriptor componentTypeDesc = varArgComponentType.getElementTypeDescriptor();
386+
// TODO Determine why componentTypeDesc can be null.
387+
// Assert.state(componentTypeDesc != null, "Component type must not be null for a varargs array");
388388

389389
// If the target is varargs and there is just one more argument, then convert it here.
390390
if (varargsPosition == arguments.length - 1) {
391391
Object argument = arguments[varargsPosition];
392392
TypeDescriptor sourceType = TypeDescriptor.forObject(argument);
393393
if (argument == null) {
394394
// Perform the equivalent of GenericConversionService.convertNullSource() for a single argument.
395-
TypeDescriptor elementDesc = varArgContentType.getElementTypeDescriptor();
396-
if (elementDesc != null && elementDesc.getObjectType() == Optional.class) {
395+
if (componentTypeDesc != null && componentTypeDesc.getObjectType() == Optional.class) {
397396
arguments[varargsPosition] = Optional.empty();
398397
conversionOccurred = true;
399398
}
400399
}
401-
// If the argument type is equal to the varargs element type, there is no need to
400+
// If the argument type is assignable to the varargs component type, there is no need to
402401
// convert it or wrap it in an array. For example, using StringToArrayConverter to
403402
// convert a String containing a comma would result in the String being split and
404403
// repackaged in an array when it should be used as-is.
405-
else if (!sourceType.equals(varArgContentType.getElementTypeDescriptor())) {
406-
arguments[varargsPosition] = converter.convertValue(argument, sourceType, varArgContentType);
404+
else if (componentTypeDesc != null && !sourceType.isAssignableTo(componentTypeDesc)) {
405+
arguments[varargsPosition] = converter.convertValue(argument, sourceType, varArgComponentType);
407406
}
408407
// Possible outcomes of the above if-else block:
409408
// 1) the input argument was null, and nothing was done.
410-
// 2) the input argument was null; the varargs element type is Optional; and the argument was converted to Optional.empty().
409+
// 2) the input argument was null; the varargs component type is Optional; and the argument was converted to Optional.empty().
411410
// 3) the input argument was correct type but not wrapped in an array, and nothing was done.
412411
// 4) the input argument was already compatible (i.e., array of valid type), and nothing was done.
413412
// 5) the input argument was the wrong type and got converted and wrapped in an array.
@@ -416,11 +415,11 @@ else if (!sourceType.equals(varArgContentType.getElementTypeDescriptor())) {
416415
conversionOccurred = true; // case 5
417416
}
418417
}
419-
// Otherwise, convert remaining arguments to the varargs element type.
418+
// Otherwise, convert remaining arguments to the varargs component type.
420419
else {
421420
for (int i = varargsPosition; i < arguments.length; i++) {
422421
Object argument = arguments[i];
423-
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), varArgContentType);
422+
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), varArgComponentType);
424423
conversionOccurred |= (argument != arguments[i]);
425424
}
426425
}
@@ -456,7 +455,7 @@ private static boolean isFirstEntryInArray(Object value, @Nullable Object possib
456455
* {@code [1, new String[] {"a", "b"}]} in order to match the expected types.
457456
* @param requiredParameterTypes the types of the parameters for the invocation
458457
* @param args the arguments to be set up for the invocation
459-
* @return a repackaged array of arguments where any varargs setup has performed
458+
* @return a repackaged array of arguments where any varargs setup has been performed
460459
*/
461460
public static Object[] setupArgumentsForVarargsInvocation(Class<?>[] requiredParameterTypes, Object... args) {
462461
Assert.notEmpty(requiredParameterTypes, "Required parameter types array must not be empty");
@@ -513,11 +512,9 @@ enum ArgumentsMatchKind {
513512

514513

515514
/**
516-
* An instance of ArgumentsMatchInfo describes what kind of match was achieved
515+
* An instance of {@code ArgumentsMatchInfo} describes what kind of match was achieved
517516
* between two sets of arguments - the set that a method/constructor is expecting
518-
* and the set that are being supplied at the point of invocation. If the kind
519-
* indicates that conversion is required for some of the arguments then the arguments
520-
* that require conversion are listed in the argsRequiringConversion array.
517+
* and the set that is being supplied at the point of invocation.
521518
*
522519
* @param kind the kind of match that was achieved
523520
*/
@@ -537,7 +534,7 @@ public boolean isMatchRequiringConversion() {
537534

538535
@Override
539536
public String toString() {
540-
return "ArgumentMatchInfo: " + this.kind;
537+
return "ArgumentsMatchInfo: " + this.kind;
541538
}
542539
}
543540

0 commit comments

Comments
 (0)