Skip to content

Commit a4fcd30

Browse files
committed
Polish SpEL's ReflectionHelper
1 parent ec4558d commit a4fcd30

File tree

1 file changed

+69
-63
lines changed

1 file changed

+69
-63
lines changed

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

+69-63
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-
* 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-
* 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 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,19 +235,22 @@ else if (typeConverter.canConvert(suppliedArg, TypeDescriptor.valueOf(varargsPar
234235
}
235236

236237

237-
// TODO could do with more refactoring around argument handling and varargs
238238
/**
239-
* Convert a supplied set of arguments into the requested types. If the parameterTypes are related to
240-
* a varargs method then the final entry in the parameterTypes array is going to be an array itself whose
241-
* component type should be used as the conversion target for extraneous arguments. (For example, if the
242-
* parameterTypes are {Integer, String[]} and the input arguments are {Integer, boolean, float} then both
243-
* the boolean and float must be converted to strings). This method does *not* repackage the arguments
244-
* into a form suitable for the varargs invocation - a subsequent call to setupArgumentsForVarargsInvocation handles that.
239+
* Convert the supplied set of arguments into the parameter types of the supplied
240+
* {@link Method}.
241+
* <p>If the supplied method is a varargs method, the final parameter type must be an
242+
* array whose component type should be used as the conversion target for extraneous
243+
* arguments. For example, if the parameter types are <code>{Integer, String[]}</code>
244+
* and the input arguments are <code>{Integer, boolean, float}</code>, then both the
245+
* {@code boolean} and the {@code float} must be converted to strings.
246+
* <p>This method does <strong>not</strong> repackage the arguments into a form suitable
247+
* for the varargs invocation: a subsequent call to
248+
* {@link #setupArgumentsForVarargsInvocation(Class[], Object...)} is required for that.
245249
* @param converter the converter to use for type conversions
246-
* @param arguments the arguments to convert to the requested parameter types
247-
* @param method the target Method
248-
* @return true if some kind of conversion occurred on the argument
249-
* @throws SpelEvaluationException if there is a problem with conversion
250+
* @param arguments the arguments to convert to the required parameter types
251+
* @param method the target {@code Method}
252+
* @return {@code true} if some kind of conversion occurred on an argument
253+
* @throws SpelEvaluationException if a problem occurs during conversion
250254
*/
251255
public static boolean convertAllArguments(TypeConverter converter, Object[] arguments, Method method)
252256
throws SpelEvaluationException {
@@ -256,11 +260,12 @@ public static boolean convertAllArguments(TypeConverter converter, Object[] argu
256260
}
257261

258262
/**
259-
* Takes an input set of argument values and converts them to the types specified as the
260-
* required parameter types. The arguments are converted 'in-place' in the input array.
261-
* @param converter the type converter to use for attempting conversions
262-
* @param arguments the actual arguments that need conversion
263-
* @param executable the target Method or Constructor
263+
* Convert the supplied set of arguments into the parameter types of the supplied
264+
* {@link Executable}, taking the varargs position into account.
265+
* <p>The arguments are converted 'in-place' in the input array.
266+
* @param converter the converter to use for type conversions
267+
* @param arguments the arguments to convert to the required parameter types
268+
* @param executable the target {@code Method} or {@code Constructor}
264269
* @param varargsPosition the known position of the varargs argument, if any
265270
* ({@code null} if not varargs)
266271
* @return {@code true} if some kind of conversion occurred on an argument
@@ -288,30 +293,31 @@ static boolean convertArguments(TypeConverter converter, Object[] arguments, Exe
288293
}
289294

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

292300
// If the target is varargs and there is just one more argument, then convert it here.
293301
if (varargsPosition == arguments.length - 1) {
294302
Object argument = arguments[varargsPosition];
295-
TypeDescriptor targetType = new TypeDescriptor(methodParam);
296303
TypeDescriptor sourceType = TypeDescriptor.forObject(argument);
297304
if (argument == null) {
298305
// Perform the equivalent of GenericConversionService.convertNullSource() for a single argument.
299-
TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();
300-
if (elementDesc != null && elementDesc.getObjectType() == Optional.class) {
306+
if (componentTypeDesc.getObjectType() == Optional.class) {
301307
arguments[varargsPosition] = Optional.empty();
302308
conversionOccurred = true;
303309
}
304310
}
305-
// If the argument type is equal to the varargs element type, there is no need to
311+
// If the argument type is equal to the varargs component type, there is no need to
306312
// convert it or wrap it in an array. For example, using StringToArrayConverter to
307313
// convert a String containing a comma would result in the String being split and
308314
// repackaged in an array when it should be used as-is.
309-
else if (!sourceType.equals(targetType.getElementTypeDescriptor())) {
315+
else if (!sourceType.equals(componentTypeDesc)) {
310316
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetType);
311317
}
312318
// Possible outcomes of the above if-else block:
313319
// 1) the input argument was null, and nothing was done.
314-
// 2) the input argument was null; the varargs element type is Optional; and the argument was converted to Optional.empty().
320+
// 2) the input argument was null; the varargs component type is Optional; and the argument was converted to Optional.empty().
315321
// 3) the input argument was correct type but not wrapped in an array, and nothing was done.
316322
// 4) the input argument was already compatible (i.e., array of valid type), and nothing was done.
317323
// 5) the input argument was the wrong type and got converted and wrapped in an array.
@@ -320,13 +326,12 @@ else if (!sourceType.equals(targetType.getElementTypeDescriptor())) {
320326
conversionOccurred = true; // case 5
321327
}
322328
}
323-
// Otherwise, convert remaining arguments to the varargs element type.
329+
// Otherwise, convert remaining arguments to the varargs component type.
324330
else {
325-
TypeDescriptor targetType = new TypeDescriptor(methodParam).getElementTypeDescriptor();
326-
Assert.state(targetType != null, "No element type");
327331
for (int i = varargsPosition; i < arguments.length; i++) {
328332
Object argument = arguments[i];
329-
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
333+
TypeDescriptor sourceType = TypeDescriptor.forObject(argument);
334+
arguments[i] = converter.convertValue(argument, sourceType, componentTypeDesc);
330335
conversionOccurred |= (argument != arguments[i]);
331336
}
332337
}
@@ -335,11 +340,12 @@ else if (!sourceType.equals(targetType.getElementTypeDescriptor())) {
335340
}
336341

337342
/**
338-
* Takes an input set of argument values and converts them to the types specified as the
339-
* required parameter types. The arguments are converted 'in-place' in the input array.
340-
* @param converter the type converter to use for attempting conversions
341-
* @param arguments the actual arguments that need conversion
342-
* @param methodHandle the target MethodHandle
343+
* Convert the supplied set of arguments into the parameter types of the supplied
344+
* {@link MethodHandle}, taking the varargs position into account.
345+
* <p>The arguments are converted 'in-place' in the input array.
346+
* @param converter the converter to use for type conversions
347+
* @param arguments the arguments to convert to the required parameter types
348+
* @param methodHandle the target {@code MethodHandle}
343349
* @param varargsPosition the known position of the varargs argument, if any
344350
* ({@code null} if not varargs)
345351
* @return {@code true} if some kind of conversion occurred on an argument
@@ -350,7 +356,7 @@ public static boolean convertAllMethodHandleArguments(TypeConverter converter, O
350356
MethodHandle methodHandle, @Nullable Integer varargsPosition) throws EvaluationException {
351357

352358
boolean conversionOccurred = false;
353-
final MethodType methodHandleArgumentTypes = methodHandle.type();
359+
MethodType methodHandleArgumentTypes = methodHandle.type();
354360
if (varargsPosition == null) {
355361
for (int i = 0; i < arguments.length; i++) {
356362
Class<?> argumentClass = methodHandleArgumentTypes.parameterType(i);
@@ -374,32 +380,34 @@ public static boolean convertAllMethodHandleArguments(TypeConverter converter, O
374380
conversionOccurred |= (argument != arguments[i]);
375381
}
376382

377-
final Class<?> varArgClass = methodHandleArgumentTypes.lastParameterType().componentType();
383+
Class<?> varArgClass = methodHandleArgumentTypes.lastParameterType().componentType();
378384
ResolvableType varArgResolvableType = ResolvableType.forClass(varArgClass);
379-
TypeDescriptor varArgContentType = new TypeDescriptor(varArgResolvableType, varArgClass, null);
385+
TypeDescriptor varArgComponentType = new TypeDescriptor(varArgResolvableType, varArgClass, null);
386+
TypeDescriptor componentTypeDesc = varArgComponentType.getElementTypeDescriptor();
387+
// TODO Determine why componentTypeDesc is null.
388+
// Assert.state(componentTypeDesc != null, "Component type must not be null for a varargs array");
380389

381390
// If the target is varargs and there is just one more argument, then convert it here.
382391
if (varargsPosition == arguments.length - 1) {
383392
Object argument = arguments[varargsPosition];
384393
TypeDescriptor sourceType = TypeDescriptor.forObject(argument);
385394
if (argument == null) {
386395
// Perform the equivalent of GenericConversionService.convertNullSource() for a single argument.
387-
TypeDescriptor elementDesc = varArgContentType.getElementTypeDescriptor();
388-
if (elementDesc != null && elementDesc.getObjectType() == Optional.class) {
396+
if (componentTypeDesc != null && componentTypeDesc.getObjectType() == Optional.class) {
389397
arguments[varargsPosition] = Optional.empty();
390398
conversionOccurred = true;
391399
}
392400
}
393-
// If the argument type is equal to the varargs element type, there is no need to
401+
// If the argument type is equal to the varargs component type, there is no need to
394402
// convert it or wrap it in an array. For example, using StringToArrayConverter to
395403
// convert a String containing a comma would result in the String being split and
396404
// repackaged in an array when it should be used as-is.
397-
else if (!sourceType.equals(varArgContentType.getElementTypeDescriptor())) {
398-
arguments[varargsPosition] = converter.convertValue(argument, sourceType, varArgContentType);
405+
else if (!sourceType.equals(componentTypeDesc)) {
406+
arguments[varargsPosition] = converter.convertValue(argument, sourceType, varArgComponentType);
399407
}
400408
// Possible outcomes of the above if-else block:
401409
// 1) the input argument was null, and nothing was done.
402-
// 2) the input argument was null; the varargs element type is Optional; and the argument was converted to Optional.empty().
410+
// 2) the input argument was null; the varargs component type is Optional; and the argument was converted to Optional.empty().
403411
// 3) the input argument was correct type but not wrapped in an array, and nothing was done.
404412
// 4) the input argument was already compatible (i.e., array of valid type), and nothing was done.
405413
// 5) the input argument was the wrong type and got converted and wrapped in an array.
@@ -408,11 +416,11 @@ else if (!sourceType.equals(varArgContentType.getElementTypeDescriptor())) {
408416
conversionOccurred = true; // case 5
409417
}
410418
}
411-
// Otherwise, convert remaining arguments to the varargs element type.
419+
// Otherwise, convert remaining arguments to the varargs component type.
412420
else {
413421
for (int i = varargsPosition; i < arguments.length; i++) {
414422
Object argument = arguments[i];
415-
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), varArgContentType);
423+
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), varArgComponentType);
416424
conversionOccurred |= (argument != arguments[i]);
417425
}
418426
}
@@ -448,7 +456,7 @@ private static boolean isFirstEntryInArray(Object value, @Nullable Object possib
448456
* {@code [1, new String[] {"a", "b"}]} in order to match the expected types.
449457
* @param requiredParameterTypes the types of the parameters for the invocation
450458
* @param args the arguments to be set up for the invocation
451-
* @return a repackaged array of arguments where any varargs setup has performed
459+
* @return a repackaged array of arguments where any varargs setup has been performed
452460
*/
453461
public static Object[] setupArgumentsForVarargsInvocation(Class<?>[] requiredParameterTypes, Object... args) {
454462
Assert.notEmpty(requiredParameterTypes, "Required parameter types array must not be empty");
@@ -505,11 +513,9 @@ enum ArgumentsMatchKind {
505513

506514

507515
/**
508-
* An instance of ArgumentsMatchInfo describes what kind of match was achieved
516+
* An instance of {@code ArgumentsMatchInfo} describes what kind of match was achieved
509517
* between two sets of arguments - the set that a method/constructor is expecting
510-
* and the set that are being supplied at the point of invocation. If the kind
511-
* indicates that conversion is required for some of the arguments then the arguments
512-
* that require conversion are listed in the argsRequiringConversion array.
518+
* and the set that is being supplied at the point of invocation.
513519
*
514520
* @param kind the kind of match that was achieved
515521
*/
@@ -529,7 +535,7 @@ public boolean isMatchRequiringConversion() {
529535

530536
@Override
531537
public String toString() {
532-
return "ArgumentMatchInfo: " + this.kind;
538+
return "ArgumentsMatchInfo: " + this.kind;
533539
}
534540
}
535541

0 commit comments

Comments
 (0)