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. * 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 Keith Donald
* @author Andy Clement * @author Andy Clement
@ -345,9 +345,9 @@ public class TypeDescriptor implements Serializable {
* from the provided collection or array element. * from the provided collection or array element.
* <p>Narrows the {@link #getElementTypeDescriptor() elementType} property to the class * <p>Narrows the {@link #getElementTypeDescriptor() elementType} property to the class
* of the provided collection or array element. For example, if this describes a * 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}. * {@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} * {@code java.lang.Integer}, the returned TypeDescriptor will be {@code java.lang.Integer}
* as well. * as well.
* <p>Annotation and nested type context will be preserved in the narrowed * <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. * from the provided map key.
* <p>Narrows the {@link #getMapKeyTypeDescriptor() mapKeyType} property * <p>Narrows the {@link #getMapKeyTypeDescriptor() mapKeyType} property
* to the class of the provided map key. For example, if this describes a * 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 * 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 * and the key argument is a {@code java.lang.Integer}, the returned
* TypeDescriptor will be {@code java.lang.Integer} as well. * TypeDescriptor will be {@code java.lang.Integer} as well.
* <p>Annotation and nested type context will be preserved in the narrowed * <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. * from the provided map value.
* <p>Narrows the {@link #getMapValueTypeDescriptor() mapValueType} property * <p>Narrows the {@link #getMapValueTypeDescriptor() mapValueType} property
* to the class of the provided map value. For example, if this describes a * 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 * 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 * and the value argument is a {@code java.lang.Integer}, the returned
* TypeDescriptor will be {@code java.lang.Integer} as well. * TypeDescriptor will be {@code java.lang.Integer} as well.
* <p>Annotation and nested type context will be preserved in the narrowed * <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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * Convert (or coerce) a value from one type to another, for example from a
* {@code boolean} to a {@code String}. * {@code boolean} to a {@code String}.
* <p>The {@link TypeDescriptor} parameters enable support for typed collections: * <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}. * simply any {@code List}.
* @param value the value to be converted * @param value the value to be converted
* @param sourceType a type descriptor that supplies extra information about the * @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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 Andy Clement
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0 * @since 3.0
*/ */
public abstract class ReflectionHelper { public abstract class ReflectionHelper {
@ -281,25 +282,32 @@ public abstract class ReflectionHelper {
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType); arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
conversionOccurred |= (argument != arguments[i]); conversionOccurred |= (argument != arguments[i]);
} }
MethodParameter methodParam = MethodParameter.forExecutable(executable, varargsPosition); 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 (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]; Object argument = arguments[varargsPosition];
TypeDescriptor targetType = new TypeDescriptor(methodParam);
TypeDescriptor sourceType = TypeDescriptor.forObject(argument); TypeDescriptor sourceType = TypeDescriptor.forObject(argument);
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetType); // If the argument type is equal to the varargs element type, there is no need
// Three outcomes of that previous line: // to convert it or wrap it in an array. For example, using StringToArrayConverter
// 1) the input argument was already compatible (ie. array of valid type) and nothing was done // to convert a String containing a comma would result in the String being split
// 2) the input argument was correct type but not in an array so it was made into an array // and repackaged in an array when it should be used as-is.
// 3) the input argument was the wrong type and got converted and put into an array 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] && if (argument != arguments[varargsPosition] &&
!isFirstEntryInArray(argument, arguments[varargsPosition])) { !isFirstEntryInArray(argument, arguments[varargsPosition])) {
conversionOccurred = true; // case 3 conversionOccurred = true; // case 3
} }
} }
// Otherwise, convert remaining arguments to the varargs element type.
else { else {
// Convert remaining arguments to the varargs element type
TypeDescriptor targetType = new TypeDescriptor(methodParam).getElementTypeDescriptor(); TypeDescriptor targetType = new TypeDescriptor(methodParam).getElementTypeDescriptor();
Assert.state(targetType != null, "No element type"); Assert.state(targetType != null, "No element type");
for (int i = varargsPosition; i < arguments.length; i++) { 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. * Package up the arguments so that they correctly match what is expected in requiredParameterTypes.
* For example, if parameterTypes is {@code (int, String[])} because the second parameter * <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 * 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. * 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 * @param requiredParameterTypes the types of the parameters for the invocation
@ -350,23 +358,24 @@ public abstract class ReflectionHelper {
requiredParameterTypes[parameterCount - 1] != requiredParameterTypes[parameterCount - 1] !=
(args[argumentCount - 1] != null ? args[argumentCount - 1].getClass() : null)) { (args[argumentCount - 1] != null ? args[argumentCount - 1].getClass() : null)) {
int arraySize = 0; // zero size array if nothing to pass as the varargs parameter // Create an array for the leading arguments plus the varargs array argument.
if (argumentCount >= parameterCount) {
arraySize = argumentCount - (parameterCount - 1);
}
// Create an array for the varargs arguments
Object[] newArgs = new Object[parameterCount]; 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); System.arraycopy(args, 0, newArgs, 0, newArgs.length - 1);
// Now sort out the final argument, which is the varargs one. Before entering this method, // 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. // the arguments should have been converted to the box form of the required type.
Class<?> componentType = requiredParameterTypes[parameterCount - 1].getComponentType(); int varargsArraySize = 0; // zero size array if nothing to pass as the varargs parameter
Object repackagedArgs = Array.newInstance(componentType, arraySize); if (argumentCount >= parameterCount) {
for (int i = 0; i < arraySize; i++) { varargsArraySize = argumentCount - (parameterCount - 1);
Array.set(repackagedArgs, i, args[parameterCount - 1 + i]);
} }
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 newArgs;
} }
return args; 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 Andy Clement
* @author Phillip Webb * @author Phillip Webb
* @author Sam Brannen
*/ */
public class MethodInvocationTests extends AbstractExpressionTests { public class MethodInvocationTests extends AbstractExpressionTests {
@ -233,26 +234,54 @@ public class MethodInvocationTests extends AbstractExpressionTests {
@Test @Test
public void testVarargsInvocation01() { public void testVarargsInvocation01() {
// Calling 'public int aVarargsMethod(String... strings)' // Calling 'public int aVarargsMethod(String... strings)' - returns number of arguments
//evaluate("aVarargsMethod('a','b','c')", 3, Integer.class); evaluate("aVarargsMethod('a','b','c')", 3, Integer.class);
//evaluate("aVarargsMethod('a')", 1, Integer.class); evaluate("aVarargsMethod('a')", 1, Integer.class);
evaluate("aVarargsMethod()", 0, Integer.class); evaluate("aVarargsMethod()", 0, Integer.class);
evaluate("aVarargsMethod(1,2,3)", 3, Integer.class); // all need converting to strings 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)", 1, Integer.class); // needs string conversion
evaluate("aVarargsMethod(1,'a',3.0d)", 3, Integer.class); // first and last need 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 @Test
public void testVarargsInvocation02() { 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(5,'a','b','c')", 8, Integer.class);
evaluate("aVarargsMethod2(2,'a')", 3, Integer.class); evaluate("aVarargsMethod2(2,'a')", 3, Integer.class);
evaluate("aVarargsMethod2(4)", 4, Integer.class); evaluate("aVarargsMethod2(4)", 4, Integer.class);
evaluate("aVarargsMethod2(8,2,3)", 10, Integer.class); evaluate("aVarargsMethod2(8,2,3)", 10, Integer.class);
evaluate("aVarargsMethod2(9)", 9, Integer.class); evaluate("aVarargsMethod2(9)", 9, Integer.class);
evaluate("aVarargsMethod2(2,'a',3.0d)", 4, 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 @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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 ///CLOVER:OFF
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class Inventor { public class Inventor {
private String name; private String name;
public String _name; public String _name;
public String _name_; public String _name_;
@ -202,8 +203,14 @@ public class Inventor {
return strings.length + i; 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() { public boolean getSomeProperty() {