Merge branch '2.2.x'

Closes gh-19216
pull/19218/head
Madhura Bhave 5 years ago
commit 1c3526f6b6

@ -31,6 +31,7 @@ import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.KotlinDetector; import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -118,9 +119,9 @@ class ValueObjectBinder implements DataObjectBinder {
return null; return null;
} }
if (KotlinDetector.isKotlinType(type)) { if (KotlinDetector.isKotlinType(type)) {
return KotlinValueObject.get((Constructor<T>) bindConstructor); return KotlinValueObject.get((Constructor<T>) bindConstructor, bindable.getType());
} }
return DefaultValueObject.get(bindConstructor); return DefaultValueObject.get(bindConstructor, bindable.getType());
} }
} }
@ -132,19 +133,22 @@ class ValueObjectBinder implements DataObjectBinder {
private final List<ConstructorParameter> constructorParameters; private final List<ConstructorParameter> constructorParameters;
private KotlinValueObject(Constructor<T> primaryConstructor, KFunction<T> kotlinConstructor) { private KotlinValueObject(Constructor<T> primaryConstructor, KFunction<T> kotlinConstructor,
ResolvableType type) {
super(primaryConstructor); super(primaryConstructor);
this.constructorParameters = parseConstructorParameters(kotlinConstructor); this.constructorParameters = parseConstructorParameters(kotlinConstructor, type);
} }
private List<ConstructorParameter> parseConstructorParameters(KFunction<T> kotlinConstructor) { private List<ConstructorParameter> parseConstructorParameters(KFunction<T> kotlinConstructor,
ResolvableType type) {
List<KParameter> parameters = kotlinConstructor.getParameters(); List<KParameter> parameters = kotlinConstructor.getParameters();
List<ConstructorParameter> result = new ArrayList<>(parameters.size()); List<ConstructorParameter> result = new ArrayList<>(parameters.size());
for (KParameter parameter : parameters) { for (KParameter parameter : parameters) {
String name = parameter.getName(); String name = parameter.getName();
ResolvableType type = ResolvableType.forType(ReflectJvmMapping.getJavaType(parameter.getType())); ResolvableType parameterType = ResolvableType
.forType(ReflectJvmMapping.getJavaType(parameter.getType()), type);
Annotation[] annotations = parameter.getAnnotations().toArray(new Annotation[0]); Annotation[] annotations = parameter.getAnnotations().toArray(new Annotation[0]);
result.add(new ConstructorParameter(name, type, annotations)); result.add(new ConstructorParameter(name, parameterType, annotations));
} }
return Collections.unmodifiableList(result); return Collections.unmodifiableList(result);
} }
@ -154,12 +158,12 @@ class ValueObjectBinder implements DataObjectBinder {
return this.constructorParameters; return this.constructorParameters;
} }
static <T> ValueObject<T> get(Constructor<T> bindConstructor) { static <T> ValueObject<T> get(Constructor<T> bindConstructor, ResolvableType type) {
KFunction<T> kotlinConstructor = ReflectJvmMapping.getKotlinFunction(bindConstructor); KFunction<T> kotlinConstructor = ReflectJvmMapping.getKotlinFunction(bindConstructor);
if (kotlinConstructor != null) { if (kotlinConstructor != null) {
return new KotlinValueObject<>(bindConstructor, kotlinConstructor); return new KotlinValueObject<>(bindConstructor, kotlinConstructor, type);
} }
return DefaultValueObject.get(bindConstructor); return DefaultValueObject.get(bindConstructor, type);
} }
} }
@ -174,21 +178,23 @@ class ValueObjectBinder implements DataObjectBinder {
private final List<ConstructorParameter> constructorParameters; private final List<ConstructorParameter> constructorParameters;
private DefaultValueObject(Constructor<T> constructor) { private DefaultValueObject(Constructor<T> constructor, ResolvableType type) {
super(constructor); super(constructor);
this.constructorParameters = parseConstructorParameters(constructor); this.constructorParameters = parseConstructorParameters(constructor, type);
} }
private static List<ConstructorParameter> parseConstructorParameters(Constructor<?> constructor) { private static List<ConstructorParameter> parseConstructorParameters(Constructor<?> constructor,
ResolvableType type) {
String[] names = PARAMETER_NAME_DISCOVERER.getParameterNames(constructor); String[] names = PARAMETER_NAME_DISCOVERER.getParameterNames(constructor);
Assert.state(names != null, () -> "Failed to extract parameter names for " + constructor); Assert.state(names != null, () -> "Failed to extract parameter names for " + constructor);
Parameter[] parameters = constructor.getParameters(); Parameter[] parameters = constructor.getParameters();
List<ConstructorParameter> result = new ArrayList<>(parameters.length); List<ConstructorParameter> result = new ArrayList<>(parameters.length);
for (int i = 0; i < parameters.length; i++) { for (int i = 0; i < parameters.length; i++) {
String name = names[i]; String name = names[i];
ResolvableType type = ResolvableType.forConstructorParameter(constructor, i); ResolvableType parameterType = ResolvableType.forMethodParameter(new MethodParameter(constructor, i),
type);
Annotation[] annotations = parameters[i].getDeclaredAnnotations(); Annotation[] annotations = parameters[i].getDeclaredAnnotations();
result.add(new ConstructorParameter(name, type, annotations)); result.add(new ConstructorParameter(name, parameterType, annotations));
} }
return Collections.unmodifiableList(result); return Collections.unmodifiableList(result);
} }
@ -199,8 +205,8 @@ class ValueObjectBinder implements DataObjectBinder {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static <T> ValueObject<T> get(Constructor<?> bindConstructor) { static <T> ValueObject<T> get(Constructor<?> bindConstructor, ResolvableType type) {
return new DefaultValueObject<>((Constructor<T>) bindConstructor); return new DefaultValueObject<>((Constructor<T>) bindConstructor, type);
} }
} }

@ -19,6 +19,7 @@ import java.lang.reflect.Constructor;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -26,6 +27,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource; import org.springframework.boot.context.properties.source.MockConfigurationPropertySource;
import org.springframework.core.ResolvableType;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -233,6 +235,19 @@ class ValueObjectBinderTests {
.satisfies(this::noConfigurationProperty); .satisfies(this::noConfigurationProperty);
} }
@Test
void bindToClassShouldBindWithGenerics() {
// gh-19156
ResolvableType type = ResolvableType.forClassWithGenerics(Map.class, String.class, String.class);
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.value.bar", "baz");
this.sources.add(source);
GenericValue<Map<String, String>> bean = this.binder.bind("foo", Bindable
.<GenericValue<Map<String, String>>>of(ResolvableType.forClassWithGenerics(GenericValue.class, type)))
.get();
assertThat(bean.getValue().get("bar")).isEqualTo("baz");
}
private void noConfigurationProperty(BindException ex) { private void noConfigurationProperty(BindException ex) {
assertThat(ex.getProperty()).isNull(); assertThat(ex.getProperty()).isNull();
} }
@ -452,4 +467,18 @@ class ValueObjectBinderTests {
} }
static class GenericValue<T> {
private final T value;
GenericValue(T value) {
this.value = value;
}
T getValue() {
return this.value;
}
}
} }

@ -4,6 +4,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.springframework.boot.context.properties.source.ConfigurationPropertyName import org.springframework.boot.context.properties.source.ConfigurationPropertyName
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource import org.springframework.boot.context.properties.source.MockConfigurationPropertySource
import org.springframework.core.ResolvableType
/** /**
* Tests for `ConstructorParametersBinder`. * Tests for `ConstructorParametersBinder`.
@ -173,6 +174,19 @@ class KotlinConstructorParametersBinderTests {
assertThat(bean.enumValue).isEqualTo(ExampleEnum.FOO_BAR) assertThat(bean.enumValue).isEqualTo(ExampleEnum.FOO_BAR)
} }
@Test
fun `Bind to data class with generics`() {
val source = MockConfigurationPropertySource()
source.put("foo.value.bar", "baz")
val binder = Binder(source)
val type = ResolvableType.forClassWithGenerics(Map::class.java, String::class.java,
String::class.java)
val bean = binder.bind("foo", Bindable
.of<GenericValue<Map<String, String>>>(ResolvableType.forClassWithGenerics(GenericValue::class.java, type)))
.get()
assertThat(bean.value.get("bar")).isEqualTo("baz");
}
class ExampleValueBean(val intValue: Int?, val longValue: Long?, class ExampleValueBean(val intValue: Int?, val longValue: Long?,
val booleanValue: Boolean?, val stringValue: String?, val booleanValue: Boolean?, val stringValue: String?,
val enumValue: ExampleEnum?) val enumValue: ExampleEnum?)
@ -214,4 +228,8 @@ class KotlinConstructorParametersBinderTests {
val stringValue: String = "my data", val stringValue: String = "my data",
val enumValue: ExampleEnum = ExampleEnum.BAR_BAZ) val enumValue: ExampleEnum = ExampleEnum.BAR_BAZ)
data class GenericValue<T>(
val value: T
)
} }

Loading…
Cancel
Save