Commit 0b3015e4 authored by Phillip Webb's avatar Phillip Webb

Polish Binder classes

Polish and rename some of the internal Binder classes to better reflect
their purpose. The `BeanBinder` is now called `DataObjectBinder` and
as a `JavaBeanBinder` implementation for setter based properties, and
a `ValueObjectBinder` implementation for constructor based properties.
parent c6dae576
......@@ -55,7 +55,7 @@ public class Binder {
private static final Set<Class<?>> NON_BEAN_CLASSES = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(Object.class, Class.class)));
private static final BeanBinder[] BEAN_BINDERS = { new ConstructorParametersBinder(), new JavaBeanBinder() };
private static final DataObjectBinder[] DATA_OBJECT_BINDERS = { new ValueObjectBinder(), new JavaBeanBinder() };
private final Iterable<ConfigurationPropertySource> sources;
......@@ -282,7 +282,7 @@ public class Binder {
result = context.getConverter().convert(result, target);
}
if (result == null && create) {
result = createBean(target, context);
result = create(target, context);
result = handler.onCreate(name, target, context, result);
result = context.getConverter().convert(result, target);
Assert.state(result != null, () -> "Unable to create instance for " + target.getType());
......@@ -291,12 +291,11 @@ public class Binder {
return context.getConverter().convert(result, target);
}
private Object createBean(Bindable<?> target, Context context) {
Class<?> type = target.getType().resolve();
for (BeanBinder beanBinder : BEAN_BINDERS) {
Object bean = beanBinder.create(type, context);
if (bean != null) {
return bean;
private Object create(Bindable<?> target, Context context) {
for (DataObjectBinder dataObjectBinder : DATA_OBJECT_BINDERS) {
Object instance = dataObjectBinder.create(target, context);
if (instance != null) {
return instance;
}
}
return null;
......@@ -331,15 +330,15 @@ public class Binder {
return bindProperty(target, context, property);
}
catch (ConverterNotFoundException ex) {
// We might still be able to bind it as a bean
Object bean = bindBean(name, target, handler, context, allowRecursiveBinding);
if (bean != null) {
return bean;
// We might still be able to bind it using the recursive binders
Object instance = bindDataObject(name, target, handler, context, allowRecursiveBinding);
if (instance != null) {
return instance;
}
throw ex;
}
}
return bindBean(name, target, handler, context, allowRecursiveBinding);
return bindDataObject(name, target, handler, context, allowRecursiveBinding);
}
private AggregateBinder<?> getAggregateBinder(Bindable<?> target, Context context) {
......@@ -387,22 +386,22 @@ public class Binder {
return result;
}
private Object bindBean(ConfigurationPropertyName name, Bindable<?> target, BindHandler handler, Context context,
boolean allowRecursiveBinding) {
private Object bindDataObject(ConfigurationPropertyName name, Bindable<?> target, BindHandler handler,
Context context, boolean allowRecursiveBinding) {
if (isUnbindableBean(name, target, context)) {
return null;
}
Class<?> type = target.getType().resolve(Object.class);
if (!allowRecursiveBinding && context.hasBoundBean(type)) {
if (!allowRecursiveBinding && context.isBindingDataObject(type)) {
return null;
}
BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
propertyTarget, handler, context, false, false);
return context.withBean(type, () -> {
for (BeanBinder beanBinder : BEAN_BINDERS) {
Object bean = beanBinder.bind(name, target, context, propertyBinder);
if (bean != null) {
return bean;
return context.withDataObject(type, () -> {
for (DataObjectBinder dataObjectBinder : DATA_OBJECT_BINDERS) {
Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
if (instance != null) {
return instance;
}
}
return null;
......@@ -457,7 +456,7 @@ public class Binder {
private int sourcePushCount;
private final Deque<Class<?>> beans = new ArrayDeque<>();
private final Deque<Class<?>> dataObjectBindings = new ArrayDeque<>();
private ConfigurationProperty configurationProperty;
......@@ -487,18 +486,18 @@ public class Binder {
}
}
private <T> T withBean(Class<?> bean, Supplier<T> supplier) {
this.beans.push(bean);
private <T> T withDataObject(Class<?> type, Supplier<T> supplier) {
this.dataObjectBindings.push(type);
try {
return withIncreasedDepth(supplier);
}
finally {
this.beans.pop();
this.dataObjectBindings.pop();
}
}
private boolean hasBoundBean(Class<?> bean) {
return this.beans.contains(bean);
private boolean isBindingDataObject(Class<?> type) {
return this.dataObjectBindings.contains(type);
}
private <T> T withIncreasedDepth(Supplier<T> supplier) {
......
......@@ -20,15 +20,18 @@ import org.springframework.boot.context.properties.bind.Binder.Context;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
/**
* Internal strategy used by {@link Binder} to bind beans.
* Internal strategy used by {@link Binder} to bind data objects. A data object is an
* object composed itself of recursively bound properties.
*
* @author Phillip Webb
* @author Madhura Bhave
* @see JavaBeanBinder
* @see ValueObjectBinder
*/
interface BeanBinder {
interface DataObjectBinder {
/**
* Return a bound bean instance or {@code null} if the {@link BeanBinder} does not
* Return a bound instance or {@code null} if the {@link DataObjectBinder} does not
* support the specified {@link Bindable}.
* @param name the name being bound
* @param target the bindable to bind
......@@ -37,15 +40,17 @@ interface BeanBinder {
* @param <T> the source type
* @return a bound instance or {@code null}
*/
<T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context, BeanPropertyBinder propertyBinder);
<T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context,
DataObjectPropertyBinder propertyBinder);
/**
* Return a new instance for the specified type.
* @param type the type used for creating a new instance
* Return a newly created instance or {@code null} if the {@link DataObjectBinder}
* does not support the specified {@link Bindable}.
* @param target the bindable to create
* @param context the bind context
* @param <T> the source type
* @return the created instance
*/
<T> T create(Class<T> type, Context context);
<T> T create(Bindable<T> target, Context context);
}
......@@ -17,13 +17,13 @@
package org.springframework.boot.context.properties.bind;
/**
* Binder that can be used by {@link BeanBinder} implementations to recursively bind bean
* properties.
* Binder that can be used by {@link DataObjectBinder} implementations to bind the data
* object properties.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
interface BeanPropertyBinder {
interface DataObjectPropertyBinder {
/**
* Bind the given property.
......
......@@ -17,14 +17,15 @@
package org.springframework.boot.context.properties.bind;
/**
* Internal utility to help when dealing with Java Bean property names.
* Internal utility to help when dealing with data object property names.
*
* @author Phillip Webb
* @author Madhura Bhave
* @see DataObjectBinder
*/
abstract class BeanPropertyName {
abstract class DataObjectPropertyName {
private BeanPropertyName() {
private DataObjectPropertyName() {
}
/**
......@@ -33,19 +34,9 @@ abstract class BeanPropertyName {
* @return the dashed from
*/
public static String toDashedForm(String name) {
return toDashedForm(name, 0);
}
/**
* Return the specified Java Bean property name in dashed form.
* @param name the source name
* @param start the starting char
* @return the dashed from
*/
public static String toDashedForm(String name, int start) {
StringBuilder result = new StringBuilder();
String replaced = name.replace('_', '-');
for (int i = start; i < replaced.length(); i++) {
for (int i = 0; i < replaced.length(); i++) {
char ch = replaced.charAt(i);
if (Character.isUpperCase(ch) && result.length() > 0 && result.charAt(result.length() - 1) != '-') {
result.append('-');
......
......@@ -35,16 +35,16 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
/**
* {@link BeanBinder} for mutable Java Beans.
* {@link DataObjectBinder} for mutable Java Beans.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class JavaBeanBinder implements BeanBinder {
class JavaBeanBinder implements DataObjectBinder {
@Override
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context,
BeanPropertyBinder propertyBinder) {
DataObjectPropertyBinder propertyBinder) {
boolean hasKnownBindableProperties = hasKnownBindableProperties(name, context);
Bean<T> bean = Bean.get(target, hasKnownBindableProperties);
if (bean == null) {
......@@ -56,8 +56,10 @@ class JavaBeanBinder implements BeanBinder {
}
@Override
public <T> T create(Class<T> type, Context context) {
return BeanUtils.instantiateClass(type);
@SuppressWarnings("unchecked")
public <T> T create(Bindable<T> target, Context context) {
Class<T> type = (Class<T>) target.getType().resolve();
return (type != null) ? BeanUtils.instantiateClass(type) : null;
}
private boolean hasKnownBindableProperties(ConfigurationPropertyName name, Context context) {
......@@ -69,7 +71,7 @@ class JavaBeanBinder implements BeanBinder {
return false;
}
private <T> boolean bind(BeanPropertyBinder propertyBinder, Bean<T> bean, BeanSupplier<T> beanSupplier) {
private <T> boolean bind(DataObjectPropertyBinder propertyBinder, Bean<T> bean, BeanSupplier<T> beanSupplier) {
boolean bound = false;
for (BeanProperty beanProperty : bean.getProperties().values()) {
bound |= bind(beanSupplier, propertyBinder, beanProperty);
......@@ -77,7 +79,8 @@ class JavaBeanBinder implements BeanBinder {
return bound;
}
private <T> boolean bind(BeanSupplier<T> beanSupplier, BeanPropertyBinder propertyBinder, BeanProperty property) {
private <T> boolean bind(BeanSupplier<T> beanSupplier, DataObjectPropertyBinder propertyBinder,
BeanProperty property) {
String propertyName = property.getName();
ResolvableType type = property.getType();
Supplier<Object> value = property.getValue(beanSupplier);
......@@ -268,7 +271,7 @@ class JavaBeanBinder implements BeanBinder {
private Field field;
BeanProperty(String name, ResolvableType declaringClassType) {
this.name = BeanPropertyName.toDashedForm(name);
this.name = DataObjectPropertyName.toDashedForm(name);
this.declaringClassType = declaringClassType;
}
......
......@@ -21,21 +21,21 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link BeanPropertyName}.
* Tests for {@link DataObjectPropertyName}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class BeanPropertyNameTests {
class DataObjectPropertyNameTests {
@Test
void toDashedCaseShouldConvertValue() {
assertThat(BeanPropertyName.toDashedForm("Foo")).isEqualTo("foo");
assertThat(BeanPropertyName.toDashedForm("foo")).isEqualTo("foo");
assertThat(BeanPropertyName.toDashedForm("fooBar")).isEqualTo("foo-bar");
assertThat(BeanPropertyName.toDashedForm("foo_bar")).isEqualTo("foo-bar");
assertThat(BeanPropertyName.toDashedForm("_foo_bar")).isEqualTo("-foo-bar");
assertThat(BeanPropertyName.toDashedForm("foo_Bar")).isEqualTo("foo-bar");
assertThat(DataObjectPropertyName.toDashedForm("Foo")).isEqualTo("foo");
assertThat(DataObjectPropertyName.toDashedForm("foo")).isEqualTo("foo");
assertThat(DataObjectPropertyName.toDashedForm("fooBar")).isEqualTo("foo-bar");
assertThat(DataObjectPropertyName.toDashedForm("foo_bar")).isEqualTo("foo-bar");
assertThat(DataObjectPropertyName.toDashedForm("_foo_bar")).isEqualTo("-foo-bar");
assertThat(DataObjectPropertyName.toDashedForm("foo_Bar")).isEqualTo("foo-bar");
}
}
......@@ -30,11 +30,11 @@ import org.springframework.format.annotation.DateTimeFormat;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConstructorParametersBinder}.
* Tests for {@link ValueObjectBinder}.
*
* @author Madhura Bhave
*/
class ConstructorParametersBinderTests {
class ValueObjectBinderTests {
private final List<ConfigurationPropertySource> sources = new ArrayList<>();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment