Reuse Kotlin parameter names if possible
This commit detects a Kotlin constructor so that it is not required to transmit the parameter names information to the Java side. See gh-8762
This commit is contained in:
parent
b34b217d1e
commit
fd0a11b7a7
|
|
@ -19,13 +19,16 @@ import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Parameter;
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import kotlin.reflect.KFunction;
|
||||||
|
import kotlin.reflect.KParameter;
|
||||||
|
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||||
|
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.boot.context.properties.ConfigurationPropertyDefaultValue;
|
import org.springframework.boot.context.properties.ConfigurationPropertyDefaultValue;
|
||||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||||
|
|
@ -42,18 +45,6 @@ class ConstructorParametersBinder implements BeanBinder {
|
||||||
|
|
||||||
private static boolean KOTLIN_PRESENT = KotlinDetector.isKotlinPresent();
|
private static boolean KOTLIN_PRESENT = KotlinDetector.isKotlinPresent();
|
||||||
|
|
||||||
private static final Map<Class<?>, Object> DEFAULT_TYPE_VALUES;
|
|
||||||
|
|
||||||
static {
|
|
||||||
Map<Class<?>, Object> values = new HashMap<>();
|
|
||||||
values.put(boolean.class, false);
|
|
||||||
values.put(byte.class, (byte) 0);
|
|
||||||
values.put(short.class, (short) 0);
|
|
||||||
values.put(int.class, 0);
|
|
||||||
values.put(long.class, (long) 0);
|
|
||||||
DEFAULT_TYPE_VALUES = Collections.unmodifiableMap(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
|
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
|
||||||
|
|
@ -72,14 +63,14 @@ class ConstructorParametersBinder implements BeanBinder {
|
||||||
for (ConstructorParameter parameter : bean.getParameters().values()) {
|
for (ConstructorParameter parameter : bean.getParameters().values()) {
|
||||||
Object bound = bind(parameter, propertyBinder);
|
Object bound = bind(parameter, propertyBinder);
|
||||||
if (bound == null) {
|
if (bound == null) {
|
||||||
bound = getDefaultValue(parameter, bean, converter);
|
bound = getDefaultValue(parameter, converter);
|
||||||
}
|
}
|
||||||
boundParams.add(bound);
|
boundParams.add(bound);
|
||||||
}
|
}
|
||||||
return boundParams;
|
return boundParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object getDefaultValue(ConstructorParameter parameter, Bean bean,
|
private Object getDefaultValue(ConstructorParameter parameter,
|
||||||
BindConverter converter) {
|
BindConverter converter) {
|
||||||
if (parameter.getDefaultValue() != null) {
|
if (parameter.getDefaultValue() != null) {
|
||||||
return converter.convert(parameter.getDefaultValue(), parameter.getType(),
|
return converter.convert(parameter.getDefaultValue(), parameter.getType(),
|
||||||
|
|
@ -88,7 +79,7 @@ class ConstructorParametersBinder implements BeanBinder {
|
||||||
else {
|
else {
|
||||||
Class<?> resolve = parameter.getType().resolve();
|
Class<?> resolve = parameter.getType().resolve();
|
||||||
if (resolve != null && resolve.isPrimitive()) {
|
if (resolve != null && resolve.isPrimitive()) {
|
||||||
return (bean.kotlinType) ? null : DEFAULT_TYPE_VALUES.get(resolve);
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -103,15 +94,12 @@ class ConstructorParametersBinder implements BeanBinder {
|
||||||
|
|
||||||
private static final class Bean {
|
private static final class Bean {
|
||||||
|
|
||||||
private final boolean kotlinType;
|
|
||||||
|
|
||||||
private final Constructor<?> constructor;
|
private final Constructor<?> constructor;
|
||||||
|
|
||||||
private final Map<String, ConstructorParameter> parameters;
|
private final Map<String, ConstructorParameter> parameters;
|
||||||
|
|
||||||
private Bean(boolean kotlinType, Constructor<?> constructor,
|
private Bean(Constructor<?> constructor,
|
||||||
Map<String, ConstructorParameter> parameters) {
|
Map<String, ConstructorParameter> parameters) {
|
||||||
this.kotlinType = kotlinType;
|
|
||||||
this.constructor = constructor;
|
this.constructor = constructor;
|
||||||
this.parameters = parameters;
|
this.parameters = parameters;
|
||||||
}
|
}
|
||||||
|
|
@ -129,20 +117,37 @@ class ConstructorParametersBinder implements BeanBinder {
|
||||||
.findPrimaryConstructor(type);
|
.findPrimaryConstructor(type);
|
||||||
if (primaryConstructor != null
|
if (primaryConstructor != null
|
||||||
&& primaryConstructor.getParameterCount() > 0) {
|
&& primaryConstructor.getParameterCount() > 0) {
|
||||||
return new Bean(true, primaryConstructor,
|
return KotlinBeanProvider.get(primaryConstructor);
|
||||||
parseParameters(primaryConstructor));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Constructor<?>[] constructors = type.getDeclaredConstructors();
|
Constructor<?>[] constructors = type.getDeclaredConstructors();
|
||||||
if (constructors.length == 1 && constructors[0].getParameterCount() > 0) {
|
if (constructors.length == 1 && constructors[0].getParameterCount() > 0) {
|
||||||
Constructor<?> constructor = constructors[0];
|
return SimpleBeanProvider.get(constructors[0]);
|
||||||
return new Bean(false, constructor, parseParameters(constructor));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, ConstructorParameter> getParameters() {
|
||||||
|
return this.parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Constructor<?> getConstructor() {
|
||||||
|
return this.constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple bean provider that uses `-parameters` to extract the parameter names.
|
||||||
|
*/
|
||||||
|
private static class SimpleBeanProvider {
|
||||||
|
|
||||||
|
public static Bean get(Constructor<?> constructor) {
|
||||||
|
return new Bean(constructor, parseParameters(constructor));
|
||||||
|
}
|
||||||
|
|
||||||
private static Map<String, ConstructorParameter> parseParameters(
|
private static Map<String, ConstructorParameter> parseParameters(
|
||||||
Constructor<?> constructor) {
|
Constructor<?> constructor) {
|
||||||
Map<String, ConstructorParameter> parameters = new LinkedHashMap<>();
|
Map<String, ConstructorParameter> parameters = new LinkedHashMap<>();
|
||||||
|
|
@ -160,12 +165,37 @@ class ConstructorParametersBinder implements BeanBinder {
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, ConstructorParameter> getParameters() {
|
}
|
||||||
return this.parameters;
|
|
||||||
|
/**
|
||||||
|
* A bean provider for a Kotlin class. Uses the Kotlin constructor to extract the
|
||||||
|
* parameter names.
|
||||||
|
*/
|
||||||
|
private static class KotlinBeanProvider {
|
||||||
|
|
||||||
|
public static Bean get(Constructor<?> constructor) {
|
||||||
|
KFunction<?> kotlinConstructor = ReflectJvmMapping
|
||||||
|
.getKotlinFunction(constructor);
|
||||||
|
if (kotlinConstructor != null) {
|
||||||
|
return new Bean(constructor, parseParameters(kotlinConstructor));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return SimpleBeanProvider.get(constructor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Constructor<?> getConstructor() {
|
private static Map<String, ConstructorParameter> parseParameters(
|
||||||
return this.constructor;
|
KFunction<?> constructor) {
|
||||||
|
Map<String, ConstructorParameter> parameters = new LinkedHashMap<>();
|
||||||
|
for (KParameter parameter : constructor.getParameters()) {
|
||||||
|
String name = parameter.getName();
|
||||||
|
Type type = ReflectJvmMapping.getJavaType(parameter.getType());
|
||||||
|
Annotation[] annotations = parameter.getAnnotations()
|
||||||
|
.toArray(new Annotation[0]);
|
||||||
|
parameters.computeIfAbsent(name, (s) -> new ConstructorParameter(name,
|
||||||
|
ResolvableType.forType(type), annotations, null));
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue