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
pull/16203/head
Stephane Nicoll 6 years ago
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;
} }
public Constructor<?> getConstructor() { /**
return this.constructor; * 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);
}
}
private static Map<String, ConstructorParameter> parseParameters(
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…
Cancel
Save