Convert single null argument to Optional.empty() in SpEL varargs expression

Prior to this commit, if a single null value was passed to a method with
a varargs array of type java.util.Optional, that null value was passed
unmodified. On the contrary, a null passed with additional values to
such a method resulted in the null being converted to Optional.empty().

This commit ensures that a single null value is also converted to
Optional.empty() for such SpEL expressions.

Closes gh-27795
This commit is contained in:
Sam Brannen 2021-12-10 13:53:28 +01:00
parent ad7cdc5ce9
commit b2e94f611f
2 changed files with 20 additions and 14 deletions

View File

@ -20,6 +20,7 @@ import java.lang.reflect.Array;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
@ -290,21 +291,29 @@ public abstract class ReflectionHelper {
Object argument = arguments[varargsPosition];
TypeDescriptor targetType = new TypeDescriptor(methodParam);
TypeDescriptor sourceType = TypeDescriptor.forObject(argument);
// If the argument is null or the argument type is equal to the varargs element type,
// there is no need to convert it or wrap it in an array. For example, using
// StringToArrayConverter to convert a String containing a comma would result in the
// String being split and repackaged in an array when it should be used as-is.
if (argument != null && !sourceType.equals(targetType.getElementTypeDescriptor())) {
if (argument == null) {
// Perform the equivalent of GenericConversionService.convertNullSource() for a single argument.
if (targetType.getElementTypeDescriptor().getObjectType() == Optional.class) {
arguments[varargsPosition] = Optional.empty();
conversionOccurred = true;
}
}
// If the argument type is equal to the varargs element type, there is no need to
// convert it or wrap it in an array. For example, using StringToArrayConverter to
// convert a String containing a comma would result in the String being split and
// repackaged in an array when it should be used as-is.
else if (!sourceType.equals(targetType.getElementTypeDescriptor())) {
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetType);
}
// Possible outcomes of the above if-block:
// Possible outcomes of the above if-else block:
// 1) the input argument was null, and nothing was done.
// 2) the input argument was correct type but not wrapped in an array, and nothing was done.
// 3) the input argument was already compatible (i.e., array of valid type), and nothing was done.
// 4) the input argument was the wrong type and got converted and wrapped in an array.
// 2) the input argument was null; the varargs element type is Optional; and the argument was converted to Optional.empty().
// 3) the input argument was correct type but not wrapped in an array, and nothing was done.
// 4) the input argument was already compatible (i.e., array of valid type), and nothing was done.
// 5) the input argument was the wrong type and got converted and wrapped in an array.
if (argument != arguments[varargsPosition] &&
!isFirstEntryInArray(argument, arguments[varargsPosition])) {
conversionOccurred = true; // case 3
conversionOccurred = true; // case 5
}
}
// Otherwise, convert remaining arguments to the varargs element type.

View File

@ -302,10 +302,7 @@ public class MethodInvocationTests extends AbstractExpressionTests {
evaluate("optionalVarargsMethod(2,3)", "[Optional[2], Optional[3]]", String.class);
evaluate("optionalVarargsMethod('a',3.0d)", "[Optional[a], Optional[3.0]]", String.class);
evaluate("optionalVarargsMethod(new String[]{'a','b','c'})", "[Optional[a], Optional[b], Optional[c]]", String.class);
// The following should actually evaluate to [Optional.empty] instead of [null],
// but ReflectionHelper.convertArguments() currently does not provide explicit
// Optional support for a single argument passed to a varargs array.
evaluate("optionalVarargsMethod(null)", "[null]", String.class);
evaluate("optionalVarargsMethod(null)", "[Optional.empty]", String.class);
evaluate("optionalVarargsMethod(null,'a')", "[Optional.empty, Optional[a]]", String.class);
evaluate("optionalVarargsMethod('a',null,'b')", "[Optional[a], Optional.empty, Optional[b]]", String.class);
}