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