Merge branch '5.3.x'

This commit is contained in:
Sam Brannen 2021-10-22 13:17:59 +02:00
commit 170d6dd5f2
5 changed files with 86 additions and 41 deletions

View File

@ -37,7 +37,7 @@ import org.springframework.util.ObjectUtils;
/**
* Contextual descriptor about a type to convert from or to.
* Capable of representing arrays and generic collection types.
* <p>Capable of representing arrays and generic collection types.
*
* @author Keith Donald
* @author Andy Clement
@ -345,9 +345,9 @@ public class TypeDescriptor implements Serializable {
* from the provided collection or array element.
* <p>Narrows the {@link #getElementTypeDescriptor() elementType} property to the class
* of the provided collection or array element. For example, if this describes a
* {@code java.util.List&lt;java.lang.Number&lt;} and the element argument is an
* {@code java.util.List<java.lang.Number>} and the element argument is a
* {@code java.lang.Integer}, the returned TypeDescriptor will be {@code java.lang.Integer}.
* If this describes a {@code java.util.List&lt;?&gt;} and the element argument is an
* If this describes a {@code java.util.List<?>} and the element argument is a
* {@code java.lang.Integer}, the returned TypeDescriptor will be {@code java.lang.Integer}
* as well.
* <p>Annotation and nested type context will be preserved in the narrowed
@ -388,9 +388,9 @@ public class TypeDescriptor implements Serializable {
* from the provided map key.
* <p>Narrows the {@link #getMapKeyTypeDescriptor() mapKeyType} property
* to the class of the provided map key. For example, if this describes a
* {@code java.util.Map&lt;java.lang.Number, java.lang.String&lt;} and the key
* {@code java.util.Map<java.lang.Number, java.lang.String>} and the key
* argument is a {@code java.lang.Integer}, the returned TypeDescriptor will be
* {@code java.lang.Integer}. If this describes a {@code java.util.Map&lt;?, ?&gt;}
* {@code java.lang.Integer}. If this describes a {@code java.util.Map<?, ?>}
* and the key argument is a {@code java.lang.Integer}, the returned
* TypeDescriptor will be {@code java.lang.Integer} as well.
* <p>Annotation and nested type context will be preserved in the narrowed
@ -425,9 +425,9 @@ public class TypeDescriptor implements Serializable {
* from the provided map value.
* <p>Narrows the {@link #getMapValueTypeDescriptor() mapValueType} property
* to the class of the provided map value. For example, if this describes a
* {@code java.util.Map&lt;java.lang.String, java.lang.Number&lt;} and the value
* {@code java.util.Map<java.lang.String, java.lang.Number>} and the value
* argument is a {@code java.lang.Integer}, the returned TypeDescriptor will be
* {@code java.lang.Integer}. If this describes a {@code java.util.Map&lt;?, ?&gt;}
* {@code java.lang.Integer}. If this describes a {@code java.util.Map<?, ?>}
* and the value argument is a {@code java.lang.Integer}, the returned
* TypeDescriptor will be {@code java.lang.Integer} as well.
* <p>Annotation and nested type context will be preserved in the narrowed

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -44,7 +44,7 @@ public interface TypeConverter {
* Convert (or coerce) a value from one type to another, for example from a
* {@code boolean} to a {@code String}.
* <p>The {@link TypeDescriptor} parameters enable support for typed collections:
* A caller may prefer a {@code List&lt;Integer&gt;}, for example, rather than
* A caller may prefer a {@code List<Integer>}, for example, rather than
* simply any {@code List}.
* @param value the value to be converted
* @param sourceType a type descriptor that supplies extra information about the

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -38,6 +38,7 @@ import org.springframework.util.MethodInvoker;
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0
*/
public abstract class ReflectionHelper {
@ -281,25 +282,32 @@ public abstract class ReflectionHelper {
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
conversionOccurred |= (argument != arguments[i]);
}
MethodParameter methodParam = MethodParameter.forExecutable(executable, varargsPosition);
// If the target is varargs and there is just one more argument, then convert it here.
if (varargsPosition == arguments.length - 1) {
// If the target is varargs and there is just one more argument
// then convert it here
TypeDescriptor targetType = new TypeDescriptor(methodParam);
Object argument = arguments[varargsPosition];
TypeDescriptor targetType = new TypeDescriptor(methodParam);
TypeDescriptor sourceType = TypeDescriptor.forObject(argument);
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetType);
// Three outcomes of that previous line:
// 1) the input argument was already compatible (ie. array of valid type) and nothing was done
// 2) the input argument was correct type but not in an array so it was made into an array
// 3) the input argument was the wrong type and got converted and put into an array
// 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.
if (!sourceType.equals(targetType.getElementTypeDescriptor())) {
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetType);
}
// Three outcomes of the above if-block:
// 1) the input argument was correct type but not wrapped in an array, and nothing was done.
// 2) the input argument was already compatible (i.e., array of valid type), and nothing was done.
// 3) 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
}
}
// Otherwise, convert remaining arguments to the varargs element type.
else {
// Convert remaining arguments to the varargs element type
TypeDescriptor targetType = new TypeDescriptor(methodParam).getElementTypeDescriptor();
Assert.state(targetType != null, "No element type");
for (int i = varargsPosition; i < arguments.length; i++) {
@ -332,8 +340,8 @@ public abstract class ReflectionHelper {
}
/**
* Package up the arguments so that they correctly match what is expected in parameterTypes.
* For example, if parameterTypes is {@code (int, String[])} because the second parameter
* Package up the arguments so that they correctly match what is expected in requiredParameterTypes.
* <p>For example, if requiredParameterTypes is {@code (int, String[])} because the second parameter
* was declared {@code String...}, then if arguments is {@code [1,"a","b"]} then it must be
* repackaged as {@code [1,new String[]{"a","b"}]} in order to match the expected types.
* @param requiredParameterTypes the types of the parameters for the invocation
@ -350,23 +358,24 @@ public abstract class ReflectionHelper {
requiredParameterTypes[parameterCount - 1] !=
(args[argumentCount - 1] != null ? args[argumentCount - 1].getClass() : null)) {
int arraySize = 0; // zero size array if nothing to pass as the varargs parameter
if (argumentCount >= parameterCount) {
arraySize = argumentCount - (parameterCount - 1);
}
// Create an array for the varargs arguments
// Create an array for the leading arguments plus the varargs array argument.
Object[] newArgs = new Object[parameterCount];
// Copy all leading arguments to the new array, omitting the varargs array argument.
System.arraycopy(args, 0, newArgs, 0, newArgs.length - 1);
// Now sort out the final argument, which is the varargs one. Before entering this method,
// the arguments should have been converted to the box form of the required type.
Class<?> componentType = requiredParameterTypes[parameterCount - 1].getComponentType();
Object repackagedArgs = Array.newInstance(componentType, arraySize);
for (int i = 0; i < arraySize; i++) {
Array.set(repackagedArgs, i, args[parameterCount - 1 + i]);
int varargsArraySize = 0; // zero size array if nothing to pass as the varargs parameter
if (argumentCount >= parameterCount) {
varargsArraySize = argumentCount - (parameterCount - 1);
}
newArgs[newArgs.length - 1] = repackagedArgs;
Class<?> componentType = requiredParameterTypes[parameterCount - 1].getComponentType();
Object varargsArray = Array.newInstance(componentType, varargsArraySize);
for (int i = 0; i < varargsArraySize; i++) {
Array.set(varargsArray, i, args[parameterCount - 1 + i]);
}
// Finally, add the varargs array to the new arguments array.
newArgs[newArgs.length - 1] = varargsArray;
return newArgs;
}
return args;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -46,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*
* @author Andy Clement
* @author Phillip Webb
* @author Sam Brannen
*/
public class MethodInvocationTests extends AbstractExpressionTests {
@ -233,26 +234,54 @@ public class MethodInvocationTests extends AbstractExpressionTests {
@Test
public void testVarargsInvocation01() {
// Calling 'public int aVarargsMethod(String... strings)'
//evaluate("aVarargsMethod('a','b','c')", 3, Integer.class);
//evaluate("aVarargsMethod('a')", 1, Integer.class);
// Calling 'public int aVarargsMethod(String... strings)' - returns number of arguments
evaluate("aVarargsMethod('a','b','c')", 3, Integer.class);
evaluate("aVarargsMethod('a')", 1, Integer.class);
evaluate("aVarargsMethod()", 0, Integer.class);
evaluate("aVarargsMethod(1,2,3)", 3, Integer.class); // all need converting to strings
evaluate("aVarargsMethod(1)", 1, Integer.class); // needs string conversion
evaluate("aVarargsMethod(1,'a',3.0d)", 3, Integer.class); // first and last need conversion
// evaluate("aVarargsMethod(new String[]{'a','b','c'})", 3, Integer.class);
evaluate("aVarargsMethod(new String[]{'a','b','c'})", 3, Integer.class);
}
@Test
public void testVarargsInvocation02() {
// Calling 'public int aVarargsMethod2(int i, String... strings)' - returns int+length_of_strings
// Calling 'public int aVarargsMethod2(int i, String... strings)' - returns int + length_of_strings
evaluate("aVarargsMethod2(5,'a','b','c')", 8, Integer.class);
evaluate("aVarargsMethod2(2,'a')", 3, Integer.class);
evaluate("aVarargsMethod2(4)", 4, Integer.class);
evaluate("aVarargsMethod2(8,2,3)", 10, Integer.class);
evaluate("aVarargsMethod2(9)", 9, Integer.class);
evaluate("aVarargsMethod2(2,'a',3.0d)", 4, Integer.class);
// evaluate("aVarargsMethod2(8,new String[]{'a','b','c'})", 11, Integer.class);
evaluate("aVarargsMethod2(8,new String[]{'a','b','c'})", 11, Integer.class);
}
@Test
public void testVarargsInvocation03() {
// Calling 'public int aVarargsMethod3(String str1, String... strings)' - returns all strings concatenated with "-"
// No conversion necessary
evaluate("aVarargsMethod3('x')", "x", String.class);
evaluate("aVarargsMethod3('x', 'a')", "x-a", String.class);
evaluate("aVarargsMethod3('x', 'a', 'b', 'c')", "x-a-b-c", String.class);
// Conversion necessary
evaluate("aVarargsMethod3(9)", "9", String.class);
evaluate("aVarargsMethod3(8,2,3)", "8-2-3", String.class);
evaluate("aVarargsMethod3('2','a',3.0d)", "2-a-3.0", String.class);
evaluate("aVarargsMethod3('8',new String[]{'a','b','c'})", "8-a-b-c", String.class);
// Individual string contains a comma with multiple varargs arguments
evaluate("aVarargsMethod3('foo', ',', 'baz')", "foo-,-baz", String.class);
evaluate("aVarargsMethod3('foo', 'bar', ',baz')", "foo-bar-,baz", String.class);
evaluate("aVarargsMethod3('foo', 'bar,', 'baz')", "foo-bar,-baz", String.class);
// Individual string contains a comma with single varargs argument.
// Reproduces https://github.com/spring-projects/spring-framework/issues/27582
evaluate("aVarargsMethod3('foo', ',')", "foo-,", String.class);
evaluate("aVarargsMethod3('foo', ',bar')", "foo-,bar", String.class);
evaluate("aVarargsMethod3('foo', 'bar,')", "foo-bar,", String.class);
evaluate("aVarargsMethod3('foo', 'bar,baz')", "foo-bar,baz", String.class);
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,6 +28,7 @@ import org.springframework.util.ObjectUtils;
///CLOVER:OFF
@SuppressWarnings("unused")
public class Inventor {
private String name;
public String _name;
public String _name_;
@ -202,8 +203,14 @@ public class Inventor {
return strings.length + i;
}
public Inventor(String... strings) {
public String aVarargsMethod3(String str1, String... strings) {
if (ObjectUtils.isEmpty(strings)) {
return str1;
}
return str1 + "-" + String.join("-", strings);
}
public Inventor(String... strings) {
}
public boolean getSomeProperty() {