Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.springframework.boot.context.properties.bind.ValueObjectBinder Maven / Gradle / Ivy
/*
* Copyright 2012-2023 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.jvm.ReflectJvmMapping;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.bind.Binder.Context;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.core.CollectionFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
/**
* {@link DataObjectBinder} for immutable value objects.
*
* @author Madhura Bhave
* @author Stephane Nicoll
* @author Phillip Webb
* @author Scott Frederick
*/
class ValueObjectBinder implements DataObjectBinder {
private static final Log logger = LogFactory.getLog(ValueObjectBinder.class);
private final BindConstructorProvider constructorProvider;
ValueObjectBinder(BindConstructorProvider constructorProvider) {
this.constructorProvider = constructorProvider;
}
@Override
public T bind(ConfigurationPropertyName name, Bindable target, Binder.Context context,
DataObjectPropertyBinder propertyBinder) {
ValueObject valueObject = ValueObject.get(target, this.constructorProvider, context, Discoverer.LENIENT);
if (valueObject == null) {
return null;
}
context.pushConstructorBoundTypes(target.getType().resolve());
List parameters = valueObject.getConstructorParameters();
List args = new ArrayList<>(parameters.size());
boolean bound = false;
for (ConstructorParameter parameter : parameters) {
Object arg = parameter.bind(propertyBinder);
bound = bound || arg != null;
arg = (arg != null) ? arg : getDefaultValue(context, parameter);
args.add(arg);
}
context.clearConfigurationProperty();
context.popConstructorBoundTypes();
return bound ? valueObject.instantiate(args) : null;
}
@Override
public T create(Bindable target, Binder.Context context) {
ValueObject valueObject = ValueObject.get(target, this.constructorProvider, context, Discoverer.LENIENT);
if (valueObject == null) {
return null;
}
List parameters = valueObject.getConstructorParameters();
List args = new ArrayList<>(parameters.size());
for (ConstructorParameter parameter : parameters) {
args.add(getDefaultValue(context, parameter));
}
return valueObject.instantiate(args);
}
@Override
public void onUnableToCreateInstance(Bindable target, Context context, RuntimeException exception) {
try {
ValueObject.get(target, this.constructorProvider, context, Discoverer.STRICT);
}
catch (Exception ex) {
exception.addSuppressed(ex);
}
}
private T getDefaultValue(Binder.Context context, ConstructorParameter parameter) {
ResolvableType type = parameter.getType();
Annotation[] annotations = parameter.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof DefaultValue defaultValueAnnotation) {
String[] defaultValue = defaultValueAnnotation.value();
if (defaultValue.length == 0) {
return getNewDefaultValueInstanceIfPossible(context, type);
}
return convertDefaultValue(context.getConverter(), defaultValue, type, annotations);
}
}
return null;
}
private T convertDefaultValue(BindConverter converter, String[] defaultValue, ResolvableType type,
Annotation[] annotations) {
try {
return converter.convert(defaultValue, type, annotations);
}
catch (ConversionException ex) {
// Try again in case ArrayToObjectConverter is not in play
if (defaultValue.length == 1) {
return converter.convert(defaultValue[0], type, annotations);
}
throw ex;
}
}
@SuppressWarnings("unchecked")
private T getNewDefaultValueInstanceIfPossible(Binder.Context context, ResolvableType type) {
Class resolved = (Class) type.resolve();
Assert.state(resolved == null || isEmptyDefaultValueAllowed(resolved),
() -> "Parameter of type " + type + " must have a non-empty default value.");
if (resolved != null) {
if (Optional.class == resolved) {
return (T) Optional.empty();
}
if (Collection.class.isAssignableFrom(resolved)) {
return (T) CollectionFactory.createCollection(resolved, 0);
}
if (Map.class.isAssignableFrom(resolved)) {
return (T) CollectionFactory.createMap(resolved, 0);
}
if (resolved.isArray()) {
return (T) Array.newInstance(resolved.getComponentType(), 0);
}
}
T instance = create(Bindable.of(type), context);
if (instance != null) {
return instance;
}
return (resolved != null) ? BeanUtils.instantiateClass(resolved) : null;
}
private boolean isEmptyDefaultValueAllowed(Class type) {
return (Optional.class == type || isAggregate(type))
|| !(type.isPrimitive() || type.isEnum() || type.getName().startsWith("java.lang"));
}
private boolean isAggregate(Class type) {
return type.isArray() || Map.class.isAssignableFrom(type) || Collection.class.isAssignableFrom(type);
}
/**
* The value object being bound.
*
* @param the value object type
*/
private abstract static class ValueObject {
private final Constructor constructor;
protected ValueObject(Constructor constructor) {
this.constructor = constructor;
}
T instantiate(List args) {
return BeanUtils.instantiateClass(this.constructor, args.toArray());
}
abstract List getConstructorParameters();
@SuppressWarnings("unchecked")
static ValueObject get(Bindable bindable, BindConstructorProvider constructorProvider,
Binder.Context context, ParameterNameDiscoverer parameterNameDiscoverer) {
Class type = (Class) bindable.getType().resolve();
if (type == null || type.isEnum() || Modifier.isAbstract(type.getModifiers())) {
return null;
}
Constructor bindConstructor = constructorProvider.getBindConstructor(bindable,
context.isNestedConstructorBinding());
if (bindConstructor == null) {
return null;
}
if (KotlinDetector.isKotlinType(type)) {
return KotlinValueObject.get((Constructor) bindConstructor, bindable.getType(),
parameterNameDiscoverer);
}
return DefaultValueObject.get(bindConstructor, bindable.getType(), parameterNameDiscoverer);
}
}
/**
* A {@link ValueObject} implementation that is aware of Kotlin specific constructs.
*/
private static final class KotlinValueObject extends ValueObject {
private static final Annotation[] ANNOTATION_ARRAY = new Annotation[0];
private final List constructorParameters;
private KotlinValueObject(Constructor primaryConstructor, KFunction kotlinConstructor,
ResolvableType type) {
super(primaryConstructor);
this.constructorParameters = parseConstructorParameters(kotlinConstructor, type);
}
private List parseConstructorParameters(KFunction kotlinConstructor,
ResolvableType type) {
List parameters = kotlinConstructor.getParameters();
List result = new ArrayList<>(parameters.size());
for (KParameter parameter : parameters) {
String name = getParameterName(parameter);
ResolvableType parameterType = ResolvableType
.forType(ReflectJvmMapping.getJavaType(parameter.getType()), type);
Annotation[] annotations = parameter.getAnnotations().toArray(ANNOTATION_ARRAY);
result.add(new ConstructorParameter(name, parameterType, annotations));
}
return Collections.unmodifiableList(result);
}
private String getParameterName(KParameter parameter) {
return MergedAnnotations.from(parameter, parameter.getAnnotations().toArray(ANNOTATION_ARRAY))
.get(Name.class)
.getValue(MergedAnnotation.VALUE, String.class)
.orElseGet(parameter::getName);
}
@Override
List getConstructorParameters() {
return this.constructorParameters;
}
static ValueObject get(Constructor bindConstructor, ResolvableType type,
ParameterNameDiscoverer parameterNameDiscoverer) {
KFunction kotlinConstructor = ReflectJvmMapping.getKotlinFunction(bindConstructor);
if (kotlinConstructor != null) {
return new KotlinValueObject<>(bindConstructor, kotlinConstructor, type);
}
return DefaultValueObject.get(bindConstructor, type, parameterNameDiscoverer);
}
}
/**
* A default {@link ValueObject} implementation that uses only standard Java
* reflection calls.
*/
private static final class DefaultValueObject extends ValueObject {
private final List constructorParameters;
private DefaultValueObject(Constructor constructor, List constructorParameters) {
super(constructor);
this.constructorParameters = constructorParameters;
}
@Override
List getConstructorParameters() {
return this.constructorParameters;
}
@SuppressWarnings("unchecked")
static ValueObject get(Constructor bindConstructor, ResolvableType type,
ParameterNameDiscoverer parameterNameDiscoverer) {
String[] names = parameterNameDiscoverer.getParameterNames(bindConstructor);
if (names == null) {
return null;
}
List constructorParameters = parseConstructorParameters(bindConstructor, type, names);
return new DefaultValueObject<>((Constructor) bindConstructor, constructorParameters);
}
private static List parseConstructorParameters(Constructor constructor,
ResolvableType type, String[] names) {
Parameter[] parameters = constructor.getParameters();
List result = new ArrayList<>(parameters.length);
for (int i = 0; i < parameters.length; i++) {
String name = MergedAnnotations.from(parameters[i])
.get(Name.class)
.getValue(MergedAnnotation.VALUE, String.class)
.orElse(names[i]);
ResolvableType parameterType = ResolvableType.forMethodParameter(new MethodParameter(constructor, i),
type);
Annotation[] annotations = parameters[i].getDeclaredAnnotations();
result.add(new ConstructorParameter(name, parameterType, annotations));
}
return Collections.unmodifiableList(result);
}
}
/**
* A constructor parameter being bound.
*/
private static class ConstructorParameter {
private final String name;
private final ResolvableType type;
private final Annotation[] annotations;
ConstructorParameter(String name, ResolvableType type, Annotation[] annotations) {
this.name = DataObjectPropertyName.toDashedForm(name);
this.type = type;
this.annotations = annotations;
}
Object bind(DataObjectPropertyBinder propertyBinder) {
return propertyBinder.bindProperty(this.name, Bindable.of(this.type).withAnnotations(this.annotations));
}
Annotation[] getAnnotations() {
return this.annotations;
}
ResolvableType getType() {
return this.type;
}
}
/**
* {@link ParameterNameDiscoverer} used for value data object binding.
*/
static final class Discoverer implements ParameterNameDiscoverer {
private static final ParameterNameDiscoverer DEFAULT_DELEGATE = new DefaultParameterNameDiscoverer();
private static final ParameterNameDiscoverer LENIENT = new Discoverer(DEFAULT_DELEGATE, (message) -> {
});
private static final ParameterNameDiscoverer STRICT = new Discoverer(DEFAULT_DELEGATE, (message) -> {
throw new IllegalStateException(message.toString());
});
private final ParameterNameDiscoverer delegate;
private final Consumer noParameterNamesHandler;
private Discoverer(ParameterNameDiscoverer delegate, Consumer noParameterNamesHandler) {
this.delegate = delegate;
this.noParameterNamesHandler = noParameterNamesHandler;
}
@Override
public String[] getParameterNames(Method method) {
throw new UnsupportedOperationException();
}
@Override
public String[] getParameterNames(Constructor constructor) {
String[] names = this.delegate.getParameterNames(constructor);
if (names != null) {
return names;
}
LogMessage message = LogMessage.format(
"Unable to use value object binding with constructor [%s] as parameter names cannot be discovered. "
+ "Ensure that the compiler uses the '-parameters' flag",
constructor);
this.noParameterNamesHandler.accept(message);
logger.debug(message);
return null;
}
}
}