* Integrate BeanBinder functionality into Bindertags/8.0.0.beta2
<ul><h4>New Data Binding API related changes</h4> | <ul><h4>New Data Binding API related changes</h4> | ||||
<li>Components using <tt>Property</tt>, <tt>Item</tt> or <tt>Container</tt> API have been reimplemented for the new API, except <tt>Tree</tt>, <tt>Table</tt>, <tt>TreeTable</tt> and <tt>Calendar</tt></li> | <li>Components using <tt>Property</tt>, <tt>Item</tt> or <tt>Container</tt> API have been reimplemented for the new API, except <tt>Tree</tt>, <tt>Table</tt>, <tt>TreeTable</tt> and <tt>Calendar</tt></li> | ||||
<li>Framework 7 versions of the components available in the v7 compatibility package for easier migration, <a href="#legacycomponents">see list of legacy components</a></li> | <li>Framework 7 versions of the components available in the v7 compatibility package for easier migration, <a href="#legacycomponents">see list of legacy components</a></li> | ||||
<li><tt>Binder</tt> is the replacement of <tt>FieldGroup</tt>, and similarly <tt>BeanBinder</tt> is the replacement of <tt>BeanFieldGroup</tt></li> | |||||
<li><tt>Binder</tt> is the replacement of <tt>FieldGroup</tt> and <tt>BeanFieldGroup</tt></li> | |||||
<li>Converters and Validators have been moved from Components to <tt>Binder</tt></li> | <li>Converters and Validators have been moved from Components to <tt>Binder</tt></li> | ||||
<li><tt>DataProvider</tt> is the replacement of <tt>Container</tt></li> | <li><tt>DataProvider</tt> is the replacement of <tt>Container</tt></li> | ||||
<ul> | <ul> |
grid.getEditor().setCancelCaption("Peruuta")); | grid.getEditor().setCancelCaption("Peruuta")); | ||||
---- | ---- | ||||
[[components.grid.editing.validation]] | [[components.grid.editing.validation]] | ||||
=== Handling Validation Errors | === Handling Validation Errors | ||||
== Binding Beans to Forms | == Binding Beans to Forms | ||||
The business objects used in an application are in most cases implemented as Java beans or POJOs. | The business objects used in an application are in most cases implemented as Java beans or POJOs. | ||||
There is special support for that kind of business object in [classname]#BeanBinder#. | |||||
It can use reflection based on bean property names to bind values. This reduces the amount of code you have to write when binding to fields in the bean. | |||||
There is special support for that kind of business object in [classname]#Binder#. | |||||
It can use reflection based on bean property names to bind values. | |||||
This reduces the amount of code you have to write when binding to fields in the bean. | |||||
[source, java] | [source, java] | ||||
---- | ---- | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
// Bind based on property name | // Bind based on property name | ||||
binder.bind(nameField, "name"); | binder.bind(nameField, "name"); | ||||
---- | ---- | ||||
[NOTE] | [NOTE] | ||||
[classname]#BeanBinder# uses strings to identify the properties so it is not refactor safe. | |||||
Code using strings to identify properties will cause exceptions during runtime if the string contains a typo or if the name of the setter and getter methods have been changed without also updating the string. | |||||
[classname]#BeanBinder# will automatically use JSR 303 Bean Validation annotations from the bean class if a Bean Validation implementation is available. | |||||
Bindings created based on a property name will automatically use JSR 303 Bean Validation annotations from the bean class if a Bean Validation implementation is available. | |||||
Constraints defined for properties in the bean will work in the same way as if configured when the binding is created. | Constraints defined for properties in the bean will work in the same way as if configured when the binding is created. | ||||
[source, java] | [source, java] | ||||
[NOTE] | [NOTE] | ||||
Bean level validation can only be performed once the bean has been updated. This means that this functionality can only be used together with `setBean`. You need to trigger validation manually if using `readBean` and `writeBean`. | Bean level validation can only be performed once the bean has been updated. This means that this functionality can only be used together with `setBean`. You need to trigger validation manually if using `readBean` and `writeBean`. | ||||
Validation errors caused by that bean level validation might not be directly associated with any field component shown in the user interface, so [classname]#BeanBinder# cannot know where such messages should be displayed. | |||||
Validation errors caused by that bean level validation might not be directly associated with any field component shown in the user interface, so [classname]#Binder# cannot know where such messages should be displayed. | |||||
Similarly to how the [methodname]#withStatusLabel# method can be used for defining where messages for a specific binding should be showed, we can also define a [classname]#Label# that is used for showing status messages that are not related to any specific field. | Similarly to how the [methodname]#withStatusLabel# method can be used for defining where messages for a specific binding should be showed, we can also define a [classname]#Label# that is used for showing status messages that are not related to any specific field. | ||||
---- | ---- | ||||
Label formStatusLabel = new Label(); | Label formStatusLabel = new Label(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.setStatusLabel(formStatusLabel); | binder.setStatusLabel(formStatusLabel); | ||||
} | } | ||||
---- | ---- | ||||
Based on those files, we can create a subclass of the design that uses a [classname]#BeanBinder# to automatically connect bean properties to field instances. | |||||
Based on those files, we can create a subclass of the design that uses a [classname]#Binder# to automatically connect bean properties to field instances. | |||||
This will look at all instance fields that are of a Field type in the class and try to find a bean property with the same name. | This will look at all instance fields that are of a Field type in the class and try to find a bean property with the same name. | ||||
[source, java] | [source, java] | ||||
---- | ---- | ||||
public class PersonForm extends PersonFormDesign { | public class PersonForm extends PersonFormDesign { | ||||
private BeanBinder<Person> binder | |||||
= new BeanBinder<>(Person.class); | |||||
private Binder<Person> binder | |||||
= new Binder<>(Person.class); | |||||
public PersonForm(Person person) { | public PersonForm(Person person) { | ||||
binder.bindInstanceFields(this); | binder.bindInstanceFields(this); |
import java.lang.annotation.RetentionPolicy; | import java.lang.annotation.RetentionPolicy; | ||||
import java.lang.annotation.Target; | import java.lang.annotation.Target; | ||||
import com.vaadin.data.BeanBinder; | |||||
import com.vaadin.data.Binder; | import com.vaadin.data.Binder; | ||||
import com.vaadin.data.HasValue; | import com.vaadin.data.HasValue; | ||||
/** | /** | ||||
* Defines the custom property name to be bound to a {@link Field} using | |||||
* {@link Binder} or {@link BeanBinder}. | |||||
* Defines the custom property name to be bound to a {@link HasValue field | |||||
* component} using {@link Binder}. | |||||
* <p> | * <p> | ||||
* The automatic data binding in Binder and BeanBinder relies on a naming | |||||
* convention by default: properties of an item are bound to similarly named | |||||
* field components in given a editor object. If you want to map a property with | |||||
* a different name (ID) to a {@link HasValue}, you can use this annotation for | |||||
* the member fields, with the name (ID) of the desired property as the | |||||
* parameter. | |||||
* The automatic data binding in Binder relies on a naming convention by | |||||
* default: properties of an item are bound to similarly named field components | |||||
* in given a editor object. If you want to map a property with a different name | |||||
* (ID) to a {@link HasValue}, you can use this annotation for the member | |||||
* fields, with the name (ID) of the desired property as the parameter. | |||||
* <p> | * <p> | ||||
* In following usage example, the text field would be bound to property "foo" | * In following usage example, the text field would be bound to property "foo" | ||||
* in the Entity class. <code> | * in the Entity class. <code> | ||||
{ | { | ||||
Editor editor = new Editor(); | Editor editor = new Editor(); | ||||
BeanBinder<Entity> binder = new BeanBinder(Entity.class); | |||||
Binder<Entity> binder = new Binder(Entity.class); | |||||
binder.bindInstanceFields(editor); | binder.bindInstanceFields(editor); | ||||
} | } | ||||
</pre> | </pre> |
/* | |||||
* Copyright 2000-2016 Vaadin Ltd. | |||||
* | |||||
* 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 | |||||
* | |||||
* http://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 com.vaadin.data; | |||||
import java.beans.IntrospectionException; | |||||
import java.beans.PropertyDescriptor; | |||||
import java.lang.reflect.Field; | |||||
import java.lang.reflect.InvocationTargetException; | |||||
import java.lang.reflect.Method; | |||||
import java.lang.reflect.Type; | |||||
import java.util.ArrayList; | |||||
import java.util.Arrays; | |||||
import java.util.HashSet; | |||||
import java.util.IdentityHashMap; | |||||
import java.util.List; | |||||
import java.util.Locale; | |||||
import java.util.Map; | |||||
import java.util.Objects; | |||||
import java.util.Optional; | |||||
import java.util.Set; | |||||
import java.util.function.BiConsumer; | |||||
import com.googlecode.gentyref.GenericTypeReflector; | |||||
import com.vaadin.annotations.PropertyId; | |||||
import com.vaadin.data.util.BeanUtil; | |||||
import com.vaadin.data.validator.BeanValidator; | |||||
import com.vaadin.server.SerializableFunction; | |||||
import com.vaadin.server.SerializablePredicate; | |||||
import com.vaadin.server.Setter; | |||||
import com.vaadin.ui.Label; | |||||
import com.vaadin.util.ReflectTools; | |||||
/** | |||||
* A {@code Binder} subclass specialized for binding <em>beans</em>: classes | |||||
* that conform to the JavaBeans specification. Bean properties are bound by | |||||
* their names. If a JSR-303 bean validation implementation is present on the | |||||
* classpath, {@code BeanBinder} adds a {@link BeanValidator} to each binding. | |||||
* | |||||
* @author Vaadin Ltd. | |||||
* | |||||
* @param <BEAN> | |||||
* the bean type | |||||
* | |||||
* @since 8.0 | |||||
*/ | |||||
public class BeanBinder<BEAN> extends Binder<BEAN> { | |||||
/** | |||||
* Represents the binding between a single field and a bean property. | |||||
* | |||||
* @param <BEAN> | |||||
* the bean type | |||||
* @param <TARGET> | |||||
* the target property type | |||||
*/ | |||||
public interface BeanBindingBuilder<BEAN, TARGET> | |||||
extends BindingBuilder<BEAN, TARGET> { | |||||
@Override | |||||
public BeanBindingBuilder<BEAN, TARGET> withValidator( | |||||
Validator<? super TARGET> validator); | |||||
@Override | |||||
public default BeanBindingBuilder<BEAN, TARGET> withValidator( | |||||
SerializablePredicate<? super TARGET> predicate, | |||||
String message) { | |||||
return (BeanBindingBuilder<BEAN, TARGET>) BindingBuilder.super.withValidator( | |||||
predicate, message); | |||||
} | |||||
@Override | |||||
default BeanBindingBuilder<BEAN, TARGET> withValidator( | |||||
SerializablePredicate<? super TARGET> predicate, | |||||
ErrorMessageProvider errorMessageProvider) { | |||||
return (BeanBindingBuilder<BEAN, TARGET>) BindingBuilder.super.withValidator( | |||||
predicate, errorMessageProvider); | |||||
} | |||||
@Override | |||||
default BeanBindingBuilder<BEAN, TARGET> withNullRepresentation( | |||||
TARGET nullRepresentation) { | |||||
return (BeanBindingBuilder<BEAN, TARGET>) BindingBuilder.super.withNullRepresentation( | |||||
nullRepresentation); | |||||
} | |||||
@Override | |||||
public BeanBindingBuilder<BEAN, TARGET> asRequired( | |||||
ErrorMessageProvider errorMessageProvider); | |||||
@Override | |||||
public default BeanBindingBuilder<BEAN, TARGET> asRequired( | |||||
String errorMessage) { | |||||
return (BeanBindingBuilder<BEAN, TARGET>) BindingBuilder.super.asRequired( | |||||
errorMessage); | |||||
} | |||||
@Override | |||||
public <NEWTARGET> BeanBindingBuilder<BEAN, NEWTARGET> withConverter( | |||||
Converter<TARGET, NEWTARGET> converter); | |||||
@Override | |||||
public default <NEWTARGET> BeanBindingBuilder<BEAN, NEWTARGET> withConverter( | |||||
SerializableFunction<TARGET, NEWTARGET> toModel, | |||||
SerializableFunction<NEWTARGET, TARGET> toPresentation) { | |||||
return (BeanBindingBuilder<BEAN, NEWTARGET>) BindingBuilder.super.withConverter( | |||||
toModel, toPresentation); | |||||
} | |||||
@Override | |||||
public default <NEWTARGET> BeanBindingBuilder<BEAN, NEWTARGET> withConverter( | |||||
SerializableFunction<TARGET, NEWTARGET> toModel, | |||||
SerializableFunction<NEWTARGET, TARGET> toPresentation, | |||||
String errorMessage) { | |||||
return (BeanBindingBuilder<BEAN, NEWTARGET>) BindingBuilder.super.withConverter( | |||||
toModel, toPresentation, errorMessage); | |||||
} | |||||
@Override | |||||
public BeanBindingBuilder<BEAN, TARGET> withValidationStatusHandler( | |||||
BindingValidationStatusHandler handler); | |||||
@Override | |||||
public default BeanBindingBuilder<BEAN, TARGET> withStatusLabel( | |||||
Label label) { | |||||
return (BeanBindingBuilder<BEAN, TARGET>) BindingBuilder.super.withStatusLabel( | |||||
label); | |||||
} | |||||
/** | |||||
* Completes this binding by connecting the field to the property with | |||||
* the given name. The getter and setter methods of the property are | |||||
* looked up with bean introspection and used to read and write the | |||||
* property value. | |||||
* <p> | |||||
* If a JSR-303 bean validation implementation is present on the | |||||
* classpath, adds a {@link BeanValidator} to this binding. | |||||
* <p> | |||||
* The property must have an accessible getter method. It need not have | |||||
* an accessible setter; in that case the property value is never | |||||
* updated and the binding is said to be <i>read-only</i>. | |||||
* | |||||
* @param propertyName | |||||
* the name of the property to bind, not null | |||||
* @return the newly created binding | |||||
* | |||||
* @throws IllegalArgumentException | |||||
* if the property name is invalid | |||||
* @throws IllegalArgumentException | |||||
* if the property has no accessible getter | |||||
* | |||||
* @see BindingBuilder#bind(ValueProvider, Setter) | |||||
*/ | |||||
public Binding<BEAN, TARGET> bind(String propertyName); | |||||
} | |||||
/** | |||||
* An internal implementation of {@link BeanBindingBuilder}. | |||||
* | |||||
* @param <BEAN> | |||||
* the bean type | |||||
* @param <FIELDVALUE> | |||||
* the field value type | |||||
* @param <TARGET> | |||||
* the target property type | |||||
*/ | |||||
protected static class BeanBindingImpl<BEAN, FIELDVALUE, TARGET> | |||||
extends BindingBuilderImpl<BEAN, FIELDVALUE, TARGET> | |||||
implements BeanBindingBuilder<BEAN, TARGET> { | |||||
/** | |||||
* Creates a new bean binding. | |||||
* | |||||
* @param binder | |||||
* the binder this instance is connected to, not null | |||||
* @param field | |||||
* the field to use, not null | |||||
* @param converter | |||||
* the initial converter to use, not null | |||||
* @param statusHandler | |||||
* the handler to notify of status changes, not null | |||||
*/ | |||||
protected BeanBindingImpl(BeanBinder<BEAN> binder, | |||||
HasValue<FIELDVALUE> field, | |||||
Converter<FIELDVALUE, TARGET> converter, | |||||
BindingValidationStatusHandler statusHandler) { | |||||
super(binder, field, converter, statusHandler); | |||||
} | |||||
@Override | |||||
public BeanBindingBuilder<BEAN, TARGET> withValidator( | |||||
Validator<? super TARGET> validator) { | |||||
return (BeanBindingBuilder<BEAN, TARGET>) super.withValidator( | |||||
validator); | |||||
} | |||||
@Override | |||||
public <NEWTARGET> BeanBindingBuilder<BEAN, NEWTARGET> withConverter( | |||||
Converter<TARGET, NEWTARGET> converter) { | |||||
return (BeanBindingBuilder<BEAN, NEWTARGET>) super.withConverter( | |||||
converter); | |||||
} | |||||
@Override | |||||
public BeanBindingBuilder<BEAN, TARGET> withValidationStatusHandler( | |||||
BindingValidationStatusHandler handler) { | |||||
return (BeanBindingBuilder<BEAN, TARGET>) super.withValidationStatusHandler( | |||||
handler); | |||||
} | |||||
@Override | |||||
public BeanBindingBuilder<BEAN, TARGET> asRequired( | |||||
ErrorMessageProvider errorMessageProvider) { | |||||
return (BeanBindingBuilder<BEAN, TARGET>) super.asRequired( | |||||
errorMessageProvider); | |||||
} | |||||
@SuppressWarnings("unchecked") | |||||
@Override | |||||
public Binding<BEAN, TARGET> bind(String propertyName) { | |||||
checkUnbound(); | |||||
BindingBuilder<BEAN, Object> finalBinding; | |||||
PropertyDescriptor descriptor = getDescriptor(propertyName); | |||||
Method getter = descriptor.getReadMethod(); | |||||
Method setter = descriptor.getWriteMethod(); | |||||
finalBinding = withConverter( | |||||
createConverter(getter.getReturnType()), false); | |||||
if (BeanUtil.checkBeanValidationAvailable()) { | |||||
finalBinding = finalBinding.withValidator( | |||||
new BeanValidator(getBinder().beanType, propertyName)); | |||||
} | |||||
try { | |||||
return (Binding<BEAN, TARGET>) finalBinding.bind( | |||||
bean -> invokeWrapExceptions(getter, bean), | |||||
(bean, value) -> invokeWrapExceptions(setter, bean, | |||||
value)); | |||||
} finally { | |||||
getBinder().boundProperties.add(propertyName); | |||||
getBinder().incompleteMemberFieldBindings.remove(getField()); | |||||
} | |||||
} | |||||
@Override | |||||
protected BeanBinder<BEAN> getBinder() { | |||||
return (BeanBinder<BEAN>) super.getBinder(); | |||||
} | |||||
private static Object invokeWrapExceptions(Method method, Object target, | |||||
Object... parameters) { | |||||
if (method == null) { | |||||
return null; | |||||
} | |||||
try { | |||||
return method.invoke(target, parameters); | |||||
} catch (IllegalAccessException | InvocationTargetException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | |||||
private PropertyDescriptor getDescriptor(String propertyName) { | |||||
final Class<?> beanType = getBinder().beanType; | |||||
PropertyDescriptor descriptor = null; | |||||
try { | |||||
descriptor = BeanUtil.getPropertyDescriptor(beanType, | |||||
propertyName); | |||||
} catch (IntrospectionException ie) { | |||||
throw new IllegalArgumentException( | |||||
"Could not resolve bean property name (see the cause): " | |||||
+ beanType.getName() + "." + propertyName, | |||||
ie); | |||||
} | |||||
if (descriptor == null) { | |||||
throw new IllegalArgumentException( | |||||
"Could not resolve bean property name (please check spelling and getter visibility): " | |||||
+ beanType.getName() + "." + propertyName); | |||||
} | |||||
if (descriptor.getReadMethod() == null) { | |||||
throw new IllegalArgumentException( | |||||
"Bean property has no accessible getter: " | |||||
+ beanType.getName() + "." + propertyName); | |||||
} | |||||
return descriptor; | |||||
} | |||||
@SuppressWarnings("unchecked") | |||||
private Converter<TARGET, Object> createConverter(Class<?> getterType) { | |||||
return Converter.from(fieldValue -> cast(fieldValue, getterType), | |||||
propertyValue -> (TARGET) propertyValue, exception -> { | |||||
throw new RuntimeException(exception); | |||||
}); | |||||
} | |||||
private <T> T cast(TARGET value, Class<T> clazz) { | |||||
if (clazz.isPrimitive()) { | |||||
return (T) ReflectTools.convertPrimitiveType(clazz).cast(value); | |||||
} else { | |||||
return clazz.cast(value); | |||||
} | |||||
} | |||||
} | |||||
private final Class<? extends BEAN> beanType; | |||||
private final Set<String> boundProperties = new HashSet<>(); | |||||
private final Map<HasValue<?>, BeanBindingImpl<BEAN, ?, ?>> incompleteMemberFieldBindings = new IdentityHashMap<>(); | |||||
/** | |||||
* Creates a new {@code BeanBinder} supporting beans of the given type. | |||||
* | |||||
* @param beanType | |||||
* the bean {@code Class} instance, not null | |||||
*/ | |||||
public BeanBinder(Class<? extends BEAN> beanType) { | |||||
BeanUtil.checkBeanValidationAvailable(); | |||||
this.beanType = beanType; | |||||
} | |||||
@Override | |||||
public <FIELDVALUE> BeanBindingBuilder<BEAN, FIELDVALUE> forField( | |||||
HasValue<FIELDVALUE> field) { | |||||
return (BeanBindingBuilder<BEAN, FIELDVALUE>) super.forField(field); | |||||
} | |||||
/** | |||||
* Creates a new binding for the given field. The returned builder may be | |||||
* further configured before invoking {@link #bindInstanceFields(Object)}. | |||||
* Unlike with the {@link #forField(HasValue)} method, no explicit call to | |||||
* {@link BeanBindingBuilder#bind(String)} is needed to complete this | |||||
* binding in the case that the name of the field matches a field name found | |||||
* in the bean. | |||||
* | |||||
* @param <FIELDVALUE> | |||||
* the value type of the field | |||||
* @param field | |||||
* the field to be bound, not null | |||||
* @return the new binding builder | |||||
* | |||||
* @see #forField(HasValue) | |||||
* @see #bindInstanceFields(Object) | |||||
*/ | |||||
public <FIELDVALUE> BeanBindingBuilder<BEAN, FIELDVALUE> forMemberField( | |||||
HasValue<FIELDVALUE> field) { | |||||
incompleteMemberFieldBindings.put(field, null); | |||||
return forField(field); | |||||
} | |||||
/** | |||||
* Binds the given field to the property with the given name. The getter and | |||||
* setter methods of the property are looked up with bean introspection and | |||||
* used to read and write the property value. | |||||
* <p> | |||||
* Use the {@link #forField(HasValue)} overload instead if you want to | |||||
* further configure the new binding. | |||||
* <p> | |||||
* The property must have an accessible getter method. It need not have an | |||||
* accessible setter; in that case the property value is never updated and | |||||
* the binding is said to be <i>read-only</i>. | |||||
* | |||||
* @param <FIELDVALUE> | |||||
* the value type of the field to bind | |||||
* @param field | |||||
* the field to bind, not null | |||||
* @param propertyName | |||||
* the name of the property to bind, not null | |||||
* @return the newly created binding | |||||
* | |||||
* @throws IllegalArgumentException | |||||
* if the property name is invalid | |||||
* @throws IllegalArgumentException | |||||
* if the property has no accessible getter | |||||
* | |||||
* @see #bind(HasValue, ValueProvider, Setter) | |||||
*/ | |||||
public <FIELDVALUE> Binding<BEAN, FIELDVALUE> bind( | |||||
HasValue<FIELDVALUE> field, String propertyName) { | |||||
return forField(field).bind(propertyName); | |||||
} | |||||
@Override | |||||
public BeanBinder<BEAN> withValidator(Validator<? super BEAN> validator) { | |||||
return (BeanBinder<BEAN>) super.withValidator(validator); | |||||
} | |||||
@SuppressWarnings("unchecked") | |||||
@Override | |||||
protected <FIELDVALUE, TARGET> BeanBindingImpl<BEAN, FIELDVALUE, TARGET> createBinding( | |||||
HasValue<FIELDVALUE> field, Converter<FIELDVALUE, TARGET> converter, | |||||
BindingValidationStatusHandler handler) { | |||||
Objects.requireNonNull(field, "field cannot be null"); | |||||
Objects.requireNonNull(converter, "converter cannot be null"); | |||||
if (incompleteMemberFieldBindings.containsKey(field)) { | |||||
BeanBindingImpl<BEAN, FIELDVALUE, TARGET> newBinding = doCreateBinding( | |||||
field, converter, handler); | |||||
incompleteMemberFieldBindings.put(field, newBinding); | |||||
return newBinding; | |||||
} else { | |||||
return (BeanBindingImpl<BEAN, FIELDVALUE, TARGET>) super.createBinding( | |||||
field, converter, handler); | |||||
} | |||||
} | |||||
@Override | |||||
protected <FIELDVALUE, TARGET> BeanBindingImpl<BEAN, FIELDVALUE, TARGET> doCreateBinding( | |||||
HasValue<FIELDVALUE> field, Converter<FIELDVALUE, TARGET> converter, | |||||
BindingValidationStatusHandler handler) { | |||||
return new BeanBindingImpl<>(this, field, converter, handler); | |||||
} | |||||
/** | |||||
* Binds member fields found in the given object. | |||||
* <p> | |||||
* This method processes all (Java) member fields whose type extends | |||||
* {@link HasValue} and that can be mapped to a property id. Property id | |||||
* mapping is done based on the field name or on a @{@link PropertyId} | |||||
* annotation on the field. All non-null unbound fields for which a property | |||||
* id can be determined are bound to the property id. | |||||
* </p> | |||||
* <p> | |||||
* For example: | |||||
* | |||||
* <pre> | |||||
* public class MyForm extends VerticalLayout { | |||||
* private TextField firstName = new TextField("First name"); | |||||
* @PropertyId("last") | |||||
* private TextField lastName = new TextField("Last name"); | |||||
* | |||||
* MyForm myForm = new MyForm(); | |||||
* ... | |||||
* binder.bindMemberFields(myForm); | |||||
* </pre> | |||||
* | |||||
* </p> | |||||
* This binds the firstName TextField to a "firstName" property in the item, | |||||
* lastName TextField to a "last" property. | |||||
* <p> | |||||
* It's not always possible to bind a field to a property because their | |||||
* types are incompatible. E.g. custom converter is required to bind | |||||
* {@code HasValue<String>} and {@code Integer} property (that would be a | |||||
* case of "age" property). In such case {@link IllegalStateException} will | |||||
* be thrown unless the field has been configured manually before calling | |||||
* the {@link #bindInstanceFields(Object)} method. | |||||
* <p> | |||||
* It's always possible to do custom binding for any field: the | |||||
* {@link #bindInstanceFields(Object)} method doesn't override existing | |||||
* bindings. | |||||
* | |||||
* @param objectWithMemberFields | |||||
* The object that contains (Java) member fields to bind | |||||
* @throws IllegalStateException | |||||
* if there are incompatible HasValue<T> and property types | |||||
*/ | |||||
public void bindInstanceFields(Object objectWithMemberFields) { | |||||
Class<?> objectClass = objectWithMemberFields.getClass(); | |||||
getFieldsInDeclareOrder(objectClass).stream() | |||||
.filter(memberField -> HasValue.class | |||||
.isAssignableFrom(memberField.getType())) | |||||
.forEach(memberField -> handleProperty(memberField, | |||||
objectWithMemberFields, | |||||
(property, type) -> bindProperty(objectWithMemberFields, | |||||
memberField, property, type))); | |||||
} | |||||
@SuppressWarnings("unchecked") | |||||
private BeanBindingImpl<BEAN, ?, ?> getIncompleteMemberFieldBinding( | |||||
Field memberField, Object objectWithMemberFields) { | |||||
memberField.setAccessible(true); | |||||
try { | |||||
return incompleteMemberFieldBindings | |||||
.get(memberField.get(objectWithMemberFields)); | |||||
} catch (IllegalArgumentException | IllegalAccessException e) { | |||||
throw new RuntimeException(e); | |||||
} finally { | |||||
memberField.setAccessible(false); | |||||
} | |||||
} | |||||
/** | |||||
* Binds {@code property} with {@code propertyType} to the field in the | |||||
* {@code objectWithMemberFields} instance using {@code memberField} as a | |||||
* reference to a member. | |||||
* | |||||
* @param objectWithMemberFields | |||||
* the object that contains (Java) member fields to build and | |||||
* bind | |||||
* @param memberField | |||||
* reference to a member field to bind | |||||
* @param property | |||||
* property name to bind | |||||
* @param propertyType | |||||
* type of the property | |||||
*/ | |||||
protected void bindProperty(Object objectWithMemberFields, | |||||
Field memberField, String property, Class<?> propertyType) { | |||||
Type valueType = GenericTypeReflector.getTypeParameter( | |||||
memberField.getGenericType(), | |||||
HasValue.class.getTypeParameters()[0]); | |||||
if (valueType == null) { | |||||
throw new IllegalStateException(String.format( | |||||
"Unable to detect value type for the member '%s' in the " | |||||
+ "class '%s'.", | |||||
memberField.getName(), | |||||
objectWithMemberFields.getClass().getName())); | |||||
} | |||||
if (propertyType.equals(GenericTypeReflector.erase(valueType))) { | |||||
HasValue<?> field; | |||||
// Get the field from the object | |||||
try { | |||||
field = (HasValue<?>) ReflectTools.getJavaFieldValue( | |||||
objectWithMemberFields, memberField, HasValue.class); | |||||
} catch (IllegalArgumentException | IllegalAccessException | |||||
| InvocationTargetException e) { | |||||
// If we cannot determine the value, just skip the field | |||||
return; | |||||
} | |||||
if (field == null) { | |||||
field = makeFieldInstance( | |||||
(Class<? extends HasValue<?>>) memberField.getType()); | |||||
initializeField(objectWithMemberFields, memberField, field); | |||||
} | |||||
forField(field).bind(property); | |||||
} else { | |||||
throw new IllegalStateException(String.format( | |||||
"Property type '%s' doesn't " | |||||
+ "match the field type '%s'. " | |||||
+ "Binding should be configured manually using converter.", | |||||
propertyType.getName(), valueType.getTypeName())); | |||||
} | |||||
} | |||||
/** | |||||
* Makes an instance of the field type {@code fieldClass}. | |||||
* <p> | |||||
* The resulting field instance is used to bind a property to it using the | |||||
* {@link #bindInstanceFields(Object)} method. | |||||
* <p> | |||||
* The default implementation relies on the default constructor of the | |||||
* class. If there is no suitable default constructor or you want to | |||||
* configure the instantiated class then override this method and provide | |||||
* your own implementation. | |||||
* | |||||
* @see #bindInstanceFields(Object) | |||||
* @param fieldClass | |||||
* type of the field | |||||
* @return a {@code fieldClass} instance object | |||||
*/ | |||||
protected HasValue<?> makeFieldInstance( | |||||
Class<? extends HasValue<?>> fieldClass) { | |||||
try { | |||||
return fieldClass.newInstance(); | |||||
} catch (InstantiationException | IllegalAccessException e) { | |||||
throw new IllegalStateException( | |||||
String.format("Couldn't create an '%s' type instance", | |||||
fieldClass.getName()), | |||||
e); | |||||
} | |||||
} | |||||
/** | |||||
* Returns an array containing {@link Field} objects reflecting all the | |||||
* fields of the class or interface represented by this Class object. The | |||||
* elements in the array returned are sorted in declare order from sub class | |||||
* to super class. | |||||
* | |||||
* @param searchClass | |||||
* class to introspect | |||||
* @return list of all fields in the class considering hierarchy | |||||
*/ | |||||
protected List<Field> getFieldsInDeclareOrder(Class<?> searchClass) { | |||||
ArrayList<Field> memberFieldInOrder = new ArrayList<>(); | |||||
while (searchClass != null) { | |||||
memberFieldInOrder | |||||
.addAll(Arrays.asList(searchClass.getDeclaredFields())); | |||||
searchClass = searchClass.getSuperclass(); | |||||
} | |||||
return memberFieldInOrder; | |||||
} | |||||
@Override | |||||
protected void checkBindingsCompleted(String methodName) { | |||||
if (!incompleteMemberFieldBindings.isEmpty()) { | |||||
throw new IllegalStateException( | |||||
"All bindings created with forMemberField must " | |||||
+ "be completed with bindInstanceFields before calling " | |||||
+ methodName); | |||||
} | |||||
super.checkBindingsCompleted(methodName); | |||||
} | |||||
private void initializeField(Object objectWithMemberFields, | |||||
Field memberField, HasValue<?> value) { | |||||
try { | |||||
ReflectTools.setJavaFieldValue(objectWithMemberFields, memberField, | |||||
value); | |||||
} catch (IllegalArgumentException | IllegalAccessException | |||||
| InvocationTargetException e) { | |||||
throw new IllegalStateException( | |||||
String.format("Could not assign value to field '%s'", | |||||
memberField.getName()), | |||||
e); | |||||
} | |||||
} | |||||
private void handleProperty(Field field, Object objectWithMemberFields, | |||||
BiConsumer<String, Class<?>> propertyHandler) { | |||||
Optional<PropertyDescriptor> descriptor = getPropertyDescriptor(field); | |||||
if (!descriptor.isPresent()) { | |||||
return; | |||||
} | |||||
String propertyName = descriptor.get().getName(); | |||||
if (boundProperties.contains(propertyName)) { | |||||
return; | |||||
} | |||||
BeanBindingImpl<BEAN, ?, ?> tentativeBinding = getIncompleteMemberFieldBinding( | |||||
field, objectWithMemberFields); | |||||
if (tentativeBinding != null) { | |||||
tentativeBinding.bind(propertyName); | |||||
return; | |||||
} | |||||
propertyHandler.accept(propertyName, | |||||
descriptor.get().getPropertyType()); | |||||
boundProperties.add(propertyName); | |||||
} | |||||
private Optional<PropertyDescriptor> getPropertyDescriptor(Field field) { | |||||
PropertyId propertyIdAnnotation = field.getAnnotation(PropertyId.class); | |||||
String propertyId; | |||||
if (propertyIdAnnotation != null) { | |||||
// @PropertyId(propertyId) always overrides property id | |||||
propertyId = propertyIdAnnotation.value(); | |||||
} else { | |||||
propertyId = field.getName(); | |||||
} | |||||
List<PropertyDescriptor> descriptors; | |||||
try { | |||||
descriptors = BeanUtil.getBeanPropertyDescriptors(beanType); | |||||
} catch (IntrospectionException e) { | |||||
throw new IllegalArgumentException(String.format( | |||||
"Could not resolve bean '%s' properties (see the cause):", | |||||
beanType.getName()), e); | |||||
} | |||||
Optional<PropertyDescriptor> propertyDescitpor = descriptors.stream() | |||||
.filter(descriptor -> minifyFieldName(descriptor.getName()) | |||||
.equals(minifyFieldName(propertyId))) | |||||
.findFirst(); | |||||
return propertyDescitpor; | |||||
} | |||||
private String minifyFieldName(String fieldName) { | |||||
return fieldName.toLowerCase(Locale.ENGLISH).replace("_", ""); | |||||
} | |||||
} |
/* | |||||
* Copyright 2000-2016 Vaadin Ltd. | |||||
* | |||||
* 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 | |||||
* | |||||
* http://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 com.vaadin.data; | |||||
import java.beans.IntrospectionException; | |||||
import java.beans.PropertyDescriptor; | |||||
import java.io.IOException; | |||||
import java.io.ObjectInputStream; | |||||
import java.io.ObjectOutputStream; | |||||
import java.lang.reflect.InvocationTargetException; | |||||
import java.lang.reflect.Method; | |||||
import java.util.Map; | |||||
import java.util.Objects; | |||||
import java.util.Optional; | |||||
import java.util.concurrent.ConcurrentHashMap; | |||||
import java.util.concurrent.ConcurrentMap; | |||||
import java.util.function.Function; | |||||
import java.util.stream.Collectors; | |||||
import java.util.stream.Stream; | |||||
import com.vaadin.data.Binder.BindingBuilder; | |||||
import com.vaadin.data.util.BeanUtil; | |||||
import com.vaadin.data.validator.BeanValidator; | |||||
import com.vaadin.server.Setter; | |||||
import com.vaadin.util.ReflectTools; | |||||
/** | |||||
* A {@link BinderPropertySet} that uses reflection to find bean properties. | |||||
* | |||||
* @author Vaadin Ltd | |||||
* | |||||
* @since | |||||
* | |||||
* @param <T> | |||||
* the type of the bean | |||||
*/ | |||||
public class BeanBinderPropertySet<T> implements BinderPropertySet<T> { | |||||
private static class BeanBinderPropertyDefinition<T, V> | |||||
implements BinderPropertyDefinition<T, V> { | |||||
private transient PropertyDescriptor descriptor; | |||||
private final BeanBinderPropertySet<T> propertySet; | |||||
public BeanBinderPropertyDefinition( | |||||
BeanBinderPropertySet<T> propertySet, | |||||
PropertyDescriptor descriptor) { | |||||
this.propertySet = propertySet; | |||||
this.descriptor = descriptor; | |||||
if (descriptor.getReadMethod() == null) { | |||||
throw new IllegalArgumentException( | |||||
"Bean property has no accessible getter: " | |||||
+ propertySet.beanType + "." | |||||
+ descriptor.getName()); | |||||
} | |||||
} | |||||
@Override | |||||
public ValueProvider<T, V> getGetter() { | |||||
return bean -> { | |||||
Method readMethod = descriptor.getReadMethod(); | |||||
Object value = invokeWrapExceptions(readMethod, bean); | |||||
return getType().cast(value); | |||||
}; | |||||
} | |||||
@Override | |||||
public Optional<Setter<T, V>> getSetter() { | |||||
Method setter = descriptor.getWriteMethod(); | |||||
if (setter == null) { | |||||
return Optional.empty(); | |||||
} | |||||
return Optional.of( | |||||
(bean, value) -> invokeWrapExceptions(setter, bean, value)); | |||||
} | |||||
@SuppressWarnings("unchecked") | |||||
@Override | |||||
public Class<V> getType() { | |||||
return (Class<V>) ReflectTools | |||||
.convertPrimitiveType(descriptor.getPropertyType()); | |||||
} | |||||
@Override | |||||
public BindingBuilder<T, V> beforeBind( | |||||
BindingBuilder<T, V> originalBuilder) { | |||||
if (BeanUtil.checkBeanValidationAvailable()) { | |||||
return originalBuilder.withValidator(new BeanValidator( | |||||
getPropertySet().beanType, descriptor.getName())); | |||||
} else { | |||||
return originalBuilder; | |||||
} | |||||
} | |||||
@Override | |||||
public String getName() { | |||||
return descriptor.getName(); | |||||
} | |||||
@Override | |||||
public BeanBinderPropertySet<T> getPropertySet() { | |||||
return propertySet; | |||||
} | |||||
private void writeObject(ObjectOutputStream stream) throws IOException { | |||||
stream.defaultWriteObject(); | |||||
stream.writeObject(descriptor.getName()); | |||||
stream.writeObject(propertySet.beanType); | |||||
} | |||||
private void readObject(ObjectInputStream stream) | |||||
throws IOException, ClassNotFoundException { | |||||
stream.defaultReadObject(); | |||||
String propertyName = (String) stream.readObject(); | |||||
@SuppressWarnings("unchecked") | |||||
Class<T> beanType = (Class<T>) stream.readObject(); | |||||
try { | |||||
descriptor = BeanUtil.getBeanPropertyDescriptors(beanType) | |||||
.stream() | |||||
.filter(descriptor -> descriptor.getName() | |||||
.equals(propertyName)) | |||||
.findAny() | |||||
.orElseThrow(() -> new IOException( | |||||
"Property " + propertyName + " not found for " | |||||
+ beanType.getName())); | |||||
} catch (IntrospectionException e) { | |||||
throw new IOException(e); | |||||
} | |||||
} | |||||
} | |||||
private static final ConcurrentMap<Class<?>, BeanBinderPropertySet<?>> instances = new ConcurrentHashMap<>(); | |||||
private final Class<T> beanType; | |||||
private final Map<String, BinderPropertyDefinition<T, ?>> definitions; | |||||
private BeanBinderPropertySet(Class<T> beanType) { | |||||
this.beanType = beanType; | |||||
try { | |||||
definitions = BeanUtil.getBeanPropertyDescriptors(beanType).stream() | |||||
.filter(BeanBinderPropertySet::hasReadMethod) | |||||
.map(descriptor -> new BeanBinderPropertyDefinition<>(this, | |||||
descriptor)) | |||||
.collect(Collectors.toMap(BinderPropertyDefinition::getName, | |||||
Function.identity())); | |||||
} catch (IntrospectionException e) { | |||||
throw new IllegalArgumentException( | |||||
"Cannot find property descriptors for " | |||||
+ beanType.getName(), | |||||
e); | |||||
} | |||||
} | |||||
/** | |||||
* Gets a {@link BeanBinderPropertySet} for the given bean type. | |||||
* | |||||
* @param beanType | |||||
* the bean type to get a property set for, not <code>null</code> | |||||
* @return the bean binder property set, not <code>null</code> | |||||
*/ | |||||
@SuppressWarnings("unchecked") | |||||
public static <T> BinderPropertySet<T> get(Class<? extends T> beanType) { | |||||
Objects.requireNonNull(beanType, "Bean type cannot be null"); | |||||
// Cache the reflection results | |||||
return (BinderPropertySet<T>) instances.computeIfAbsent(beanType, | |||||
BeanBinderPropertySet::new); | |||||
} | |||||
@Override | |||||
public Stream<BinderPropertyDefinition<T, ?>> getProperties() { | |||||
return definitions.values().stream(); | |||||
} | |||||
@Override | |||||
public Optional<BinderPropertyDefinition<T, ?>> getProperty(String name) { | |||||
return Optional.ofNullable(definitions.get(name)); | |||||
} | |||||
private static boolean hasReadMethod(PropertyDescriptor descriptor) { | |||||
return descriptor.getReadMethod() != null; | |||||
} | |||||
private static Object invokeWrapExceptions(Method method, Object target, | |||||
Object... parameters) { | |||||
try { | |||||
return method.invoke(target, parameters); | |||||
} catch (IllegalAccessException | InvocationTargetException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "Property set for bean " + beanType.getName(); | |||||
} | |||||
} |
package com.vaadin.data; | package com.vaadin.data; | ||||
import java.io.Serializable; | import java.io.Serializable; | ||||
import java.lang.reflect.Field; | |||||
import java.lang.reflect.InvocationTargetException; | |||||
import java.lang.reflect.Type; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.HashSet; | |||||
import java.util.IdentityHashMap; | import java.util.IdentityHashMap; | ||||
import java.util.LinkedHashSet; | import java.util.LinkedHashSet; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Objects; | import java.util.Objects; | ||||
import java.util.Optional; | import java.util.Optional; | ||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.function.BiConsumer; | |||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import java.util.stream.Stream; | |||||
import com.googlecode.gentyref.GenericTypeReflector; | |||||
import com.vaadin.annotations.PropertyId; | |||||
import com.vaadin.data.HasValue.ValueChangeEvent; | import com.vaadin.data.HasValue.ValueChangeEvent; | ||||
import com.vaadin.data.converter.StringToIntegerConverter; | import com.vaadin.data.converter.StringToIntegerConverter; | ||||
import com.vaadin.data.validator.BeanValidator; | |||||
import com.vaadin.event.EventRouter; | import com.vaadin.event.EventRouter; | ||||
import com.vaadin.server.ErrorMessage; | import com.vaadin.server.ErrorMessage; | ||||
import com.vaadin.server.SerializableFunction; | import com.vaadin.server.SerializableFunction; | ||||
import com.vaadin.ui.Component; | import com.vaadin.ui.Component; | ||||
import com.vaadin.ui.Label; | import com.vaadin.ui.Label; | ||||
import com.vaadin.ui.UI; | import com.vaadin.ui.UI; | ||||
import com.vaadin.util.ReflectTools; | |||||
/** | /** | ||||
* Connects one or more {@code Field} components to properties of a backing data | * Connects one or more {@code Field} components to properties of a backing data | ||||
public Binding<BEAN, TARGET> bind(ValueProvider<BEAN, TARGET> getter, | public Binding<BEAN, TARGET> bind(ValueProvider<BEAN, TARGET> getter, | ||||
Setter<BEAN, TARGET> setter); | Setter<BEAN, TARGET> setter); | ||||
/** | |||||
* Completes this binding by connecting the field to the property with | |||||
* the given name. The getter and setter of the property are looked up | |||||
* using a {@link BinderPropertySet}. | |||||
* <p> | |||||
* For a <code>Binder</code> created using the | |||||
* {@link Binder#Binder(Class)} constructor, introspection will be used | |||||
* to find a Java Bean property. If a JSR-303 bean validation | |||||
* implementation is present on the classpath, a {@link BeanValidator} | |||||
* is also added to the binding. | |||||
* <p> | |||||
* The property must have an accessible getter method. It need not have | |||||
* an accessible setter; in that case the property value is never | |||||
* updated and the binding is said to be <i>read-only</i>. | |||||
* | |||||
* @param propertyName | |||||
* the name of the property to bind, not null | |||||
* @return the newly created binding | |||||
* | |||||
* @throws IllegalArgumentException | |||||
* if the property name is invalid | |||||
* @throws IllegalArgumentException | |||||
* if the property has no accessible getter | |||||
* @throws IllegalStateException | |||||
* if the binder is not configured with an appropriate | |||||
* {@link BinderPropertySet} | |||||
* | |||||
* @see Binder.BindingBuilder#bind(ValueProvider, Setter) | |||||
*/ | |||||
public Binding<BEAN, TARGET> bind(String propertyName); | |||||
/** | /** | ||||
* Adds a validator to this binding. Validators are applied, in | * Adds a validator to this binding. Validators are applied, in | ||||
* registration order, when the field value is written to the backing | * registration order, when the field value is written to the backing | ||||
return binding; | return binding; | ||||
} | } | ||||
@Override | |||||
@SuppressWarnings({ "unchecked", "rawtypes" }) | |||||
public Binding<BEAN, TARGET> bind(String propertyName) { | |||||
Objects.requireNonNull(propertyName, | |||||
"Property name cannot be null"); | |||||
checkUnbound(); | |||||
BinderPropertyDefinition<BEAN, ?> definition = getBinder().propertySet | |||||
.getProperty(propertyName) | |||||
.orElseThrow(() -> new IllegalArgumentException( | |||||
"Could not resolve property name " + propertyName | |||||
+ " from " + getBinder().propertySet)); | |||||
ValueProvider<BEAN, ?> getter = definition.getGetter(); | |||||
Setter<BEAN, ?> setter = definition.getSetter() | |||||
.orElse((bean, value) -> { | |||||
// Setter ignores value | |||||
}); | |||||
BindingBuilder finalBinding = withConverter( | |||||
createConverter(definition.getType()), false); | |||||
finalBinding = definition.beforeBind(finalBinding); | |||||
try { | |||||
return finalBinding.bind(getter, setter); | |||||
} finally { | |||||
getBinder().boundProperties.add(propertyName); | |||||
getBinder().incompleteMemberFieldBindings.remove(getField()); | |||||
} | |||||
} | |||||
@SuppressWarnings("unchecked") | |||||
private Converter<TARGET, Object> createConverter(Class<?> getterType) { | |||||
return Converter.from(fieldValue -> getterType.cast(fieldValue), | |||||
propertyValue -> (TARGET) propertyValue, exception -> { | |||||
throw new RuntimeException(exception); | |||||
}); | |||||
} | |||||
@Override | @Override | ||||
public BindingBuilder<BEAN, TARGET> withValidator( | public BindingBuilder<BEAN, TARGET> withValidator( | ||||
Validator<? super TARGET> validator) { | Validator<? super TARGET> validator) { | ||||
} | } | ||||
} | } | ||||
private final BinderPropertySet<BEAN> propertySet; | |||||
/** | |||||
* Property names that have been used for creating a binding. | |||||
*/ | |||||
private final Set<String> boundProperties = new HashSet<>(); | |||||
private final Map<HasValue<?>, BindingBuilder<BEAN, ?>> incompleteMemberFieldBindings = new IdentityHashMap<>(); | |||||
private BEAN bean; | private BEAN bean; | ||||
private final Set<BindingImpl<BEAN, ?, ?>> bindings = new LinkedHashSet<>(); | private final Set<BindingImpl<BEAN, ?, ?>> bindings = new LinkedHashSet<>(); | ||||
private boolean hasChanges = false; | private boolean hasChanges = false; | ||||
/** | |||||
* Creates a binder using a custom {@link BinderPropertySet} implementation | |||||
* for finding and resolving property names for | |||||
* {@link #bindInstanceFields(Object)}, {@link #bind(HasValue, String)} and | |||||
* {@link BindingBuilder#bind(String)}. | |||||
* | |||||
* @param propertySet | |||||
* the binder property set implementation to use, not | |||||
* <code>null</code>. | |||||
*/ | |||||
protected Binder(BinderPropertySet<BEAN> propertySet) { | |||||
Objects.requireNonNull(propertySet, "propertySet cannot be null"); | |||||
this.propertySet = propertySet; | |||||
} | |||||
/** | |||||
* Creates a new binder that uses reflection based on the provided bean type | |||||
* to resolve bean properties. If a JSR-303 bean validation implementation | |||||
* is present on the classpath, a {@link BeanValidator} is added to each | |||||
* binding that is defined using a property name. | |||||
* | |||||
* @param beanType | |||||
* the bean type to use, not <code>null</code> | |||||
*/ | |||||
public Binder(Class<BEAN> beanType) { | |||||
this(BeanBinderPropertySet.get(beanType)); | |||||
} | |||||
/** | |||||
* Creates a new binder without support for creating bindings based on | |||||
* property names. Use an alternative constructor, such as | |||||
* {@link Binder#Binder(Class)}, to create a binder that support creating | |||||
* bindings based on instance fields through | |||||
* {@link #bindInstanceFields(Object)}, or based on a property name through | |||||
* {@link #bind(HasValue, String)} or {@link BindingBuilder#bind(String)}. | |||||
*/ | |||||
public Binder() { | |||||
this(new BinderPropertySet<BEAN>() { | |||||
@Override | |||||
public Stream<BinderPropertyDefinition<BEAN, ?>> getProperties() { | |||||
throw new IllegalStateException( | |||||
"A Binder created with the default constructor doesn't support listing properties."); | |||||
} | |||||
@Override | |||||
public Optional<BinderPropertyDefinition<BEAN, ?>> getProperty( | |||||
String name) { | |||||
throw new IllegalStateException( | |||||
"A Binder created with the default constructor doesn't support finding properties by name."); | |||||
} | |||||
}); | |||||
} | |||||
/** | |||||
* Creates a binder using a custom {@link BinderPropertySet} implementation | |||||
* for finding and resolving property names for | |||||
* {@link #bindInstanceFields(Object)}, {@link #bind(HasValue, String)} and | |||||
* {@link BindingBuilder#bind(String)}. | |||||
* <p> | |||||
* This functionality is provided as static method instead of as a public | |||||
* constructor in order to make it possible to use a custom property set | |||||
* without creating a subclass while still leaving the public constructors | |||||
* focused on the common use cases. | |||||
* | |||||
* @see Binder#Binder() | |||||
* @see Binder#Binder(Class) | |||||
* | |||||
* @param propertySet | |||||
* the binder property set implementation to use, not | |||||
* <code>null</code>. | |||||
* @return a new binder using the provided property set, not | |||||
* <code>null</code> | |||||
*/ | |||||
public static <BEAN> Binder<BEAN> withPropertySet( | |||||
BinderPropertySet<BEAN> propertySet) { | |||||
return new Binder<>(propertySet); | |||||
} | |||||
/** | /** | ||||
* Returns the bean that has been bound with {@link #bind}, or null if a | * Returns the bean that has been bound with {@link #bind}, or null if a | ||||
* bean is not currently bound. | * bean is not currently bound. | ||||
this::handleValidationStatus); | this::handleValidationStatus); | ||||
} | } | ||||
/** | |||||
* Creates a new binding for the given field. The returned builder may be | |||||
* further configured before invoking {@link #bindInstanceFields(Object)}. | |||||
* Unlike with the {@link #forField(HasValue)} method, no explicit call to | |||||
* {@link BindingBuilder#bind(String)} is needed to complete this binding in | |||||
* the case that the name of the field matches a field name found in the | |||||
* bean. | |||||
* | |||||
* @param <FIELDVALUE> | |||||
* the value type of the field | |||||
* @param field | |||||
* the field to be bound, not null | |||||
* @return the new binding builder | |||||
* | |||||
* @see #forField(HasValue) | |||||
* @see #bindInstanceFields(Object) | |||||
*/ | |||||
public <FIELDVALUE> BindingBuilder<BEAN, FIELDVALUE> forMemberField( | |||||
HasValue<FIELDVALUE> field) { | |||||
incompleteMemberFieldBindings.put(field, null); | |||||
return forField(field); | |||||
} | |||||
/** | /** | ||||
* Binds a field to a bean property represented by the given getter and | * Binds a field to a bean property represented by the given getter and | ||||
* setter pair. The functions are used to update the field value from the | * setter pair. The functions are used to update the field value from the | ||||
* {@link HasValue#getEmptyValue()}. This conversion is one-way only, if you | * {@link HasValue#getEmptyValue()}. This conversion is one-way only, if you | ||||
* want to have a two-way mapping back to {@code null}, use | * want to have a two-way mapping back to {@code null}, use | ||||
* {@link #forField(HasValue)} and | * {@link #forField(HasValue)} and | ||||
* {@link BindingBuilder#withNullRepresentation(Object))}. | |||||
* {@link BindingBuilder#withNullRepresentation(Object)}. | |||||
* <p> | * <p> | ||||
* When a bean is bound with {@link Binder#setBean(BEAN)}, the field value | * When a bean is bound with {@link Binder#setBean(BEAN)}, the field value | ||||
* is set to the return value of the given getter. The property value is | * is set to the return value of the given getter. The property value is | ||||
return forField(field).bind(getter, setter); | return forField(field).bind(getter, setter); | ||||
} | } | ||||
/** | |||||
* Binds the given field to the property with the given name. The getter and | |||||
* setter of the property are looked up using a {@link BinderPropertySet}. | |||||
* <p> | |||||
* For a <code>Binder</code> created using the {@link Binder#Binder(Class)} | |||||
* constructor, introspection will be used to find a Java Bean property. If | |||||
* a JSR-303 bean validation implementation is present on the classpath, a | |||||
* {@link BeanValidator} is also added to the binding. | |||||
* <p> | |||||
* The property must have an accessible getter method. It need not have an | |||||
* accessible setter; in that case the property value is never updated and | |||||
* the binding is said to be <i>read-only</i>. | |||||
* | |||||
* @param <FIELDVALUE> | |||||
* the value type of the field to bind | |||||
* @param field | |||||
* the field to bind, not null | |||||
* @param propertyName | |||||
* the name of the property to bind, not null | |||||
* @return the newly created binding | |||||
* | |||||
* @throws IllegalArgumentException | |||||
* if the property name is invalid | |||||
* @throws IllegalArgumentException | |||||
* if the property has no accessible getter | |||||
* @throws IllegalStateException | |||||
* if the binder is not configured with an appropriate | |||||
* {@link BinderPropertySet} | |||||
* | |||||
* @see #bind(HasValue, ValueProvider, Setter) | |||||
*/ | |||||
public <FIELDVALUE> Binding<BEAN, FIELDVALUE> bind( | |||||
HasValue<FIELDVALUE> field, String propertyName) { | |||||
return forField(field).bind(propertyName); | |||||
} | |||||
/** | /** | ||||
* Binds the given bean to all the fields added to this Binder. A | * Binds the given bean to all the fields added to this Binder. A | ||||
* {@code null} value removes a currently bound bean. | * {@code null} value removes a currently bound bean. | ||||
* Only the one validation error message is shown in this label at a time. | * Only the one validation error message is shown in this label at a time. | ||||
* <p> | * <p> | ||||
* This is a convenience method for | * This is a convenience method for | ||||
* {@link #setValidationStatusHandler(BinderValidationStatusHandler)}, which means | |||||
* that this method cannot be used after the handler has been set. Also the | |||||
* handler cannot be set after this label has been set. | |||||
* {@link #setValidationStatusHandler(BinderValidationStatusHandler)}, which | |||||
* means that this method cannot be used after the handler has been set. | |||||
* Also the handler cannot be set after this label has been set. | |||||
* | * | ||||
* @param statusLabel | * @param statusLabel | ||||
* the status label to set | * the status label to set | ||||
* Gets the status handler of this form. | * Gets the status handler of this form. | ||||
* <p> | * <p> | ||||
* If none has been set with | * If none has been set with | ||||
* {@link #setValidationStatusHandler(BinderValidationStatusHandler)}, the default | |||||
* implementation is returned. | |||||
* {@link #setValidationStatusHandler(BinderValidationStatusHandler)}, the | |||||
* default implementation is returned. | |||||
* | * | ||||
* @return the status handler used, never <code>null</code> | * @return the status handler used, never <code>null</code> | ||||
* @see #setValidationStatusHandler(BinderValidationStatusHandler) | * @see #setValidationStatusHandler(BinderValidationStatusHandler) | ||||
BindingValidationStatusHandler handler) { | BindingValidationStatusHandler handler) { | ||||
BindingBuilder<BEAN, TARGET> newBinding = doCreateBinding(field, | BindingBuilder<BEAN, TARGET> newBinding = doCreateBinding(field, | ||||
converter, handler); | converter, handler); | ||||
if (incompleteMemberFieldBindings.containsKey(field)) { | |||||
incompleteMemberFieldBindings.put(field, newBinding); | |||||
} | |||||
incompleteBindings.put(field, newBinding); | incompleteBindings.put(field, newBinding); | ||||
return newBinding; | return newBinding; | ||||
} | } | ||||
/** | /** | ||||
* Throws if this binder has incomplete bindings. | * Throws if this binder has incomplete bindings. | ||||
* | |||||
* | |||||
* @param methodName | * @param methodName | ||||
* name of the method where this call is originated from | * name of the method where this call is originated from | ||||
* @throws IllegalStateException | * @throws IllegalStateException | ||||
* if this binder has incomplete bindings | * if this binder has incomplete bindings | ||||
*/ | */ | ||||
protected void checkBindingsCompleted(String methodName) { | |||||
private void checkBindingsCompleted(String methodName) { | |||||
if (!incompleteMemberFieldBindings.isEmpty()) { | |||||
throw new IllegalStateException( | |||||
"All bindings created with forMemberField must " | |||||
+ "be completed with bindInstanceFields before calling " | |||||
+ methodName); | |||||
} | |||||
if (!incompleteBindings.isEmpty()) { | if (!incompleteBindings.isEmpty()) { | ||||
throw new IllegalStateException( | throw new IllegalStateException( | ||||
"All bindings created with forField must be completed before calling " | "All bindings created with forField must be completed before calling " | ||||
+ methodName); | + methodName); | ||||
} | } | ||||
} | } | ||||
/** | |||||
* Binds member fields found in the given object. | |||||
* <p> | |||||
* This method processes all (Java) member fields whose type extends | |||||
* {@link HasValue} and that can be mapped to a property id. Property name | |||||
* mapping is done based on the field name or on a @{@link PropertyId} | |||||
* annotation on the field. All non-null unbound fields for which a property | |||||
* name can be determined are bound to the property name using | |||||
* {@link BindingBuilder#bind(String)}. | |||||
* <p> | |||||
* For example: | |||||
* | |||||
* <pre> | |||||
* public class MyForm extends VerticalLayout { | |||||
* private TextField firstName = new TextField("First name"); | |||||
* @PropertyId("last") | |||||
* private TextField lastName = new TextField("Last name"); | |||||
* | |||||
* MyForm myForm = new MyForm(); | |||||
* ... | |||||
* binder.bindMemberFields(myForm); | |||||
* </pre> | |||||
* | |||||
* This binds the firstName TextField to a "firstName" property in the item, | |||||
* lastName TextField to a "last" property. | |||||
* <p> | |||||
* It's not always possible to bind a field to a property because their | |||||
* types are incompatible. E.g. custom converter is required to bind | |||||
* {@code HasValue<String>} and {@code Integer} property (that would be a | |||||
* case of "age" property). In such case {@link IllegalStateException} will | |||||
* be thrown unless the field has been configured manually before calling | |||||
* the {@link #bindInstanceFields(Object)} method. | |||||
* <p> | |||||
* It's always possible to do custom binding for any field: the | |||||
* {@link #bindInstanceFields(Object)} method doesn't override existing | |||||
* bindings. | |||||
* | |||||
* @param objectWithMemberFields | |||||
* The object that contains (Java) member fields to bind | |||||
* @throws IllegalStateException | |||||
* if there are incompatible HasValue<T> and property | |||||
* types | |||||
*/ | |||||
public void bindInstanceFields(Object objectWithMemberFields) { | |||||
Class<?> objectClass = objectWithMemberFields.getClass(); | |||||
getFieldsInDeclareOrder(objectClass).stream() | |||||
.filter(memberField -> HasValue.class | |||||
.isAssignableFrom(memberField.getType())) | |||||
.forEach(memberField -> handleProperty(memberField, | |||||
objectWithMemberFields, | |||||
(property, type) -> bindProperty(objectWithMemberFields, | |||||
memberField, property, type))); | |||||
} | |||||
@SuppressWarnings("unchecked") | |||||
private BindingBuilder<BEAN, ?> getIncompleteMemberFieldBinding( | |||||
Field memberField, Object objectWithMemberFields) { | |||||
memberField.setAccessible(true); | |||||
try { | |||||
return incompleteMemberFieldBindings | |||||
.get(memberField.get(objectWithMemberFields)); | |||||
} catch (IllegalArgumentException | IllegalAccessException e) { | |||||
throw new RuntimeException(e); | |||||
} finally { | |||||
memberField.setAccessible(false); | |||||
} | |||||
} | |||||
/** | |||||
* Binds {@code property} with {@code propertyType} to the field in the | |||||
* {@code objectWithMemberFields} instance using {@code memberField} as a | |||||
* reference to a member. | |||||
* | |||||
* @param objectWithMemberFields | |||||
* the object that contains (Java) member fields to build and | |||||
* bind | |||||
* @param memberField | |||||
* reference to a member field to bind | |||||
* @param property | |||||
* property name to bind | |||||
* @param propertyType | |||||
* type of the property | |||||
*/ | |||||
private void bindProperty(Object objectWithMemberFields, Field memberField, | |||||
String property, Class<?> propertyType) { | |||||
Type valueType = GenericTypeReflector.getTypeParameter( | |||||
memberField.getGenericType(), | |||||
HasValue.class.getTypeParameters()[0]); | |||||
if (valueType == null) { | |||||
throw new IllegalStateException(String.format( | |||||
"Unable to detect value type for the member '%s' in the " | |||||
+ "class '%s'.", | |||||
memberField.getName(), | |||||
objectWithMemberFields.getClass().getName())); | |||||
} | |||||
if (propertyType.equals(GenericTypeReflector.erase(valueType))) { | |||||
HasValue<?> field; | |||||
// Get the field from the object | |||||
try { | |||||
field = (HasValue<?>) ReflectTools.getJavaFieldValue( | |||||
objectWithMemberFields, memberField, HasValue.class); | |||||
} catch (IllegalArgumentException | IllegalAccessException | |||||
| InvocationTargetException e) { | |||||
// If we cannot determine the value, just skip the field | |||||
return; | |||||
} | |||||
if (field == null) { | |||||
field = makeFieldInstance( | |||||
(Class<? extends HasValue<?>>) memberField.getType()); | |||||
initializeField(objectWithMemberFields, memberField, field); | |||||
} | |||||
forField(field).bind(property); | |||||
} else { | |||||
throw new IllegalStateException(String.format( | |||||
"Property type '%s' doesn't " | |||||
+ "match the field type '%s'. " | |||||
+ "Binding should be configured manually using converter.", | |||||
propertyType.getName(), valueType.getTypeName())); | |||||
} | |||||
} | |||||
/** | |||||
* Makes an instance of the field type {@code fieldClass}. | |||||
* <p> | |||||
* The resulting field instance is used to bind a property to it using the | |||||
* {@link #bindInstanceFields(Object)} method. | |||||
* <p> | |||||
* The default implementation relies on the default constructor of the | |||||
* class. If there is no suitable default constructor or you want to | |||||
* configure the instantiated class then override this method and provide | |||||
* your own implementation. | |||||
* | |||||
* @see #bindInstanceFields(Object) | |||||
* @param fieldClass | |||||
* type of the field | |||||
* @return a {@code fieldClass} instance object | |||||
*/ | |||||
private HasValue<?> makeFieldInstance( | |||||
Class<? extends HasValue<?>> fieldClass) { | |||||
try { | |||||
return fieldClass.newInstance(); | |||||
} catch (InstantiationException | IllegalAccessException e) { | |||||
throw new IllegalStateException( | |||||
String.format("Couldn't create an '%s' type instance", | |||||
fieldClass.getName()), | |||||
e); | |||||
} | |||||
} | |||||
/** | |||||
* Returns an array containing {@link Field} objects reflecting all the | |||||
* fields of the class or interface represented by this Class object. The | |||||
* elements in the array returned are sorted in declare order from sub class | |||||
* to super class. | |||||
* | |||||
* @param searchClass | |||||
* class to introspect | |||||
* @return list of all fields in the class considering hierarchy | |||||
*/ | |||||
private List<Field> getFieldsInDeclareOrder(Class<?> searchClass) { | |||||
ArrayList<Field> memberFieldInOrder = new ArrayList<>(); | |||||
while (searchClass != null) { | |||||
memberFieldInOrder | |||||
.addAll(Arrays.asList(searchClass.getDeclaredFields())); | |||||
searchClass = searchClass.getSuperclass(); | |||||
} | |||||
return memberFieldInOrder; | |||||
} | |||||
private void initializeField(Object objectWithMemberFields, | |||||
Field memberField, HasValue<?> value) { | |||||
try { | |||||
ReflectTools.setJavaFieldValue(objectWithMemberFields, memberField, | |||||
value); | |||||
} catch (IllegalArgumentException | IllegalAccessException | |||||
| InvocationTargetException e) { | |||||
throw new IllegalStateException( | |||||
String.format("Could not assign value to field '%s'", | |||||
memberField.getName()), | |||||
e); | |||||
} | |||||
} | |||||
private void handleProperty(Field field, Object objectWithMemberFields, | |||||
BiConsumer<String, Class<?>> propertyHandler) { | |||||
Optional<BinderPropertyDefinition<BEAN, ?>> descriptor = getPropertyDescriptor( | |||||
field); | |||||
if (!descriptor.isPresent()) { | |||||
return; | |||||
} | |||||
String propertyName = descriptor.get().getName(); | |||||
if (boundProperties.contains(propertyName)) { | |||||
return; | |||||
} | |||||
BindingBuilder<BEAN, ?> tentativeBinding = getIncompleteMemberFieldBinding( | |||||
field, objectWithMemberFields); | |||||
if (tentativeBinding != null) { | |||||
tentativeBinding.bind(propertyName); | |||||
return; | |||||
} | |||||
propertyHandler.accept(propertyName, descriptor.get().getType()); | |||||
boundProperties.add(propertyName); | |||||
} | |||||
private Optional<BinderPropertyDefinition<BEAN, ?>> getPropertyDescriptor( | |||||
Field field) { | |||||
PropertyId propertyIdAnnotation = field.getAnnotation(PropertyId.class); | |||||
String propertyId; | |||||
if (propertyIdAnnotation != null) { | |||||
// @PropertyId(propertyId) always overrides property id | |||||
propertyId = propertyIdAnnotation.value(); | |||||
} else { | |||||
propertyId = field.getName(); | |||||
} | |||||
String minifiedFieldName = minifyFieldName(propertyId); | |||||
return propertySet.getProperties() | |||||
.map(BinderPropertyDefinition::getName) | |||||
.filter(name -> minifyFieldName(name).equals(minifiedFieldName)) | |||||
.findFirst().flatMap(propertySet::getProperty); | |||||
} | |||||
private String minifyFieldName(String fieldName) { | |||||
return fieldName.toLowerCase(Locale.ENGLISH).replace("_", ""); | |||||
} | |||||
} | } |
/* | |||||
* Copyright 2000-2016 Vaadin Ltd. | |||||
* | |||||
* 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 | |||||
* | |||||
* http://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 com.vaadin.data; | |||||
import java.io.Serializable; | |||||
import java.util.Optional; | |||||
import com.vaadin.data.Binder.BindingBuilder; | |||||
import com.vaadin.server.Setter; | |||||
/** | |||||
* A property from a {@link BinderPropertySet}. | |||||
* | |||||
* @author Vaadin Ltd | |||||
* @since | |||||
* | |||||
* @param <T> | |||||
* the type of the binder property set | |||||
* @param <V> | |||||
* the property type | |||||
*/ | |||||
public interface BinderPropertyDefinition<T, V> extends Serializable { | |||||
/** | |||||
* Gets the value provider that is used for finding the value of this | |||||
* property for a bean. | |||||
* | |||||
* @return the getter, not <code>null</code> | |||||
*/ | |||||
public ValueProvider<T, V> getGetter(); | |||||
/** | |||||
* Gets an optional setter for storing a property value in a bean. | |||||
* | |||||
* @return the setter, or an empty optional if this property is read-only | |||||
*/ | |||||
public Optional<Setter<T, V>> getSetter(); | |||||
/** | |||||
* Gets the type of this property. | |||||
* | |||||
* @return the property type. not <code>null</code> | |||||
*/ | |||||
public Class<V> getType(); | |||||
/** | |||||
* Hook for modifying a binding before it is being bound to this property. | |||||
* This method can return the provided {@link BindingBuilder} as-is if no | |||||
* modifications are necessary. | |||||
* | |||||
* @param originalBuilder | |||||
* the original binding builder that is being bound, not | |||||
* <code>null</code> | |||||
* @return the binding builder to use for creating the binding, not | |||||
* <code>null</code> | |||||
*/ | |||||
public BindingBuilder<T, V> beforeBind( | |||||
BindingBuilder<T, V> originalBuilder); | |||||
/** | |||||
* Gets the name of this property. | |||||
* | |||||
* @return the property name, not <code>null</code> | |||||
*/ | |||||
public String getName(); | |||||
/** | |||||
* Gets the {@link BinderPropertySet} that this property belongs to. | |||||
* | |||||
* @return the binder property set, not <code>null</code> | |||||
*/ | |||||
public BinderPropertySet<T> getPropertySet(); | |||||
} |
/* | |||||
* Copyright 2000-2016 Vaadin Ltd. | |||||
* | |||||
* 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 | |||||
* | |||||
* http://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 com.vaadin.data; | |||||
import java.io.Serializable; | |||||
import java.util.Optional; | |||||
import java.util.stream.Stream; | |||||
/** | |||||
* Describes a set of properties that can be used with a {@link Binder}. | |||||
* | |||||
* @author Vaadin Ltd | |||||
* | |||||
* @since | |||||
* | |||||
* @param <T> | |||||
* the type for which the properties are defined | |||||
*/ | |||||
public interface BinderPropertySet<T> extends Serializable { | |||||
/** | |||||
* Gets all known properties as a stream. | |||||
* | |||||
* @return a stream of property names, not <code>null</code> | |||||
*/ | |||||
public Stream<BinderPropertyDefinition<T, ?>> getProperties(); | |||||
/** | |||||
* Gets the definition for the named property, or an empty optional if there | |||||
* is no property with the given name. | |||||
* | |||||
* @param name | |||||
* the property name to look for, not <code>null</code> | |||||
* @return the property definition, or empty optional if property doesn't | |||||
* exist | |||||
*/ | |||||
public Optional<BinderPropertyDefinition<T, ?>> getProperty(String name); | |||||
} |
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import com.vaadin.data.Binder.BindingBuilder; | import com.vaadin.data.Binder.BindingBuilder; | ||||
import com.vaadin.data.validator.BeanValidator; | |||||
/** | /** | ||||
* Binder validation status change. Represents the outcome of binder level | * Binder validation status change. Represents the outcome of binder level | ||||
/** | /** | ||||
* Gets the bean level validation results. | * Gets the bean level validation results. | ||||
* <p> | |||||
* The bean level validators have been added with | |||||
* {@link Binder#withValidator(Validator)} or in case of a | |||||
* {@link BeanBinder} they might be automatically added {@link BeanValidator | |||||
* BeanValidators}. | |||||
* | * | ||||
* @return the bean level validation results | * @return the bean level validation results | ||||
*/ | */ | ||||
/** | /** | ||||
* Gets the failed bean level validation results. | * Gets the failed bean level validation results. | ||||
* <p> | |||||
* The bean level validators have been added with | |||||
* {@link Binder#withValidator(Validator)} or in case of a | |||||
* {@link BeanBinder} they might be automatically added {@link BeanValidator | |||||
* BeanValidators}. | |||||
* | * | ||||
* @return a list of failed bean level validation results | * @return a list of failed bean level validation results | ||||
*/ | */ |
/* | |||||
* Copyright 2000-2016 Vaadin Ltd. | |||||
* | |||||
* 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 | |||||
* | |||||
* http://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 com.vaadin.data; | |||||
import java.io.ByteArrayInputStream; | |||||
import java.io.ByteArrayOutputStream; | |||||
import java.io.ObjectInputStream; | |||||
import java.io.ObjectOutputStream; | |||||
import org.junit.Assert; | |||||
import org.junit.Test; | |||||
import com.vaadin.data.provider.bov.Person; | |||||
public class BeanBinderPropertySetTest { | |||||
@Test | |||||
public void testSerializeDeserialize() throws Exception { | |||||
BinderPropertyDefinition<Person, ?> definition = BeanBinderPropertySet | |||||
.get(Person.class).getProperty("born") | |||||
.orElseThrow(RuntimeException::new); | |||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |||||
ObjectOutputStream out = new ObjectOutputStream(bos); | |||||
out.writeObject(definition); | |||||
out.flush(); | |||||
ObjectInputStream inputStream = new ObjectInputStream( | |||||
new ByteArrayInputStream(bos.toByteArray())); | |||||
BinderPropertyDefinition<Person, ?> deserializedDefinition = (BinderPropertyDefinition<Person, ?>) inputStream | |||||
.readObject(); | |||||
ValueProvider<Person, ?> getter = deserializedDefinition.getGetter(); | |||||
Person person = new Person("Milennial", 2000); | |||||
Integer age = (Integer) getter.apply(person); | |||||
Assert.assertEquals(Integer.valueOf(2000), age); | |||||
} | |||||
} |
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertSame; | import static org.junit.Assert.assertSame; | ||||
import java.lang.reflect.Method; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Set; | import java.util.Set; | ||||
import org.junit.Assert; | |||||
import org.junit.Before; | import org.junit.Before; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import com.vaadin.data.BeanBinder.BeanBindingBuilder; | |||||
import com.vaadin.data.Binder.BindingBuilder; | |||||
import com.vaadin.data.converter.StringToIntegerConverter; | import com.vaadin.data.converter.StringToIntegerConverter; | ||||
import com.vaadin.tests.data.bean.BeanToValidate; | import com.vaadin.tests.data.bean.BeanToValidate; | ||||
import com.vaadin.ui.CheckBoxGroup; | import com.vaadin.ui.CheckBoxGroup; | ||||
import com.vaadin.ui.TextField; | import com.vaadin.ui.TextField; | ||||
public class BeanBinderTest | public class BeanBinderTest | ||||
extends BinderTestBase<BeanBinder<BeanToValidate>, BeanToValidate> { | |||||
extends BinderTestBase<Binder<BeanToValidate>, BeanToValidate> { | |||||
private enum TestEnum { | private enum TestEnum { | ||||
} | } | ||||
@Before | @Before | ||||
public void setUp() { | public void setUp() { | ||||
binder = new BeanBinder<>(BeanToValidate.class); | |||||
binder = new Binder<>(BeanToValidate.class); | |||||
item = new BeanToValidate(); | item = new BeanToValidate(); | ||||
item.setFirstname("Johannes"); | item.setFirstname("Johannes"); | ||||
item.setAge(32); | item.setAge(32); | ||||
@Test | @Test | ||||
public void bindInstanceFields_parameters_type_erased() { | public void bindInstanceFields_parameters_type_erased() { | ||||
BeanBinder<TestBean> otherBinder = new BeanBinder<>(TestBean.class); | |||||
Binder<TestBean> otherBinder = new Binder<>(TestBean.class); | |||||
TestClass testClass = new TestClass(); | TestClass testClass = new TestClass(); | ||||
otherBinder.forField(testClass.number) | otherBinder.forField(testClass.number) | ||||
.withConverter(new StringToIntegerConverter("")) | |||||
.bind("number"); | |||||
.withConverter(new StringToIntegerConverter("")).bind("number"); | |||||
// Should correctly bind the enum field without throwing | // Should correctly bind the enum field without throwing | ||||
otherBinder.bindInstanceFields(testClass); | otherBinder.bindInstanceFields(testClass); | ||||
@Test | @Test | ||||
public void bindInstanceFields_automatically_binds_incomplete_forMemberField_bindings() { | public void bindInstanceFields_automatically_binds_incomplete_forMemberField_bindings() { | ||||
BeanBinder<TestBean> otherBinder = new BeanBinder<>(TestBean.class); | |||||
Binder<TestBean> otherBinder = new Binder<>(TestBean.class); | |||||
TestClass testClass = new TestClass(); | TestClass testClass = new TestClass(); | ||||
otherBinder.forMemberField(testClass.number) | otherBinder.forMemberField(testClass.number) | ||||
@Test(expected = IllegalStateException.class) | @Test(expected = IllegalStateException.class) | ||||
public void bindInstanceFields_does_not_automatically_bind_incomplete_forField_bindings() { | public void bindInstanceFields_does_not_automatically_bind_incomplete_forField_bindings() { | ||||
BeanBinder<TestBean> otherBinder = new BeanBinder<>(TestBean.class); | |||||
Binder<TestBean> otherBinder = new Binder<>(TestBean.class); | |||||
TestClass testClass = new TestClass(); | TestClass testClass = new TestClass(); | ||||
otherBinder.forField(testClass.number) | otherBinder.forField(testClass.number) | ||||
@Test(expected = IllegalStateException.class) | @Test(expected = IllegalStateException.class) | ||||
public void incomplete_forMemberField_bindings() { | public void incomplete_forMemberField_bindings() { | ||||
BeanBinder<TestBean> otherBinder = new BeanBinder<>(TestBean.class); | |||||
Binder<TestBean> otherBinder = new Binder<>(TestBean.class); | |||||
TestClass testClass = new TestClass(); | TestClass testClass = new TestClass(); | ||||
otherBinder.forMemberField(testClass.number) | otherBinder.forMemberField(testClass.number) | ||||
assertSame(field, errors.get(0).getField()); | assertSame(field, errors.get(0).getField()); | ||||
assertEquals(message, errors.get(0).getMessage().get()); | assertEquals(message, errors.get(0).getMessage().get()); | ||||
} | } | ||||
@Test | |||||
public void beanBindingChainingMethods() { | |||||
Method[] methods = BeanBindingBuilder.class.getMethods(); | |||||
for (int i = 0; i < methods.length; i++) { | |||||
Method method = methods[i]; | |||||
try { | |||||
Method actualMethod = BeanBindingBuilder.class.getMethod( | |||||
method.getName(), method.getParameterTypes()); | |||||
Assert.assertNotSame( | |||||
actualMethod + " should be overridden in " | |||||
+ BeanBindingBuilder.class | |||||
+ " with more specific return type ", | |||||
BindingBuilder.class, actualMethod.getReturnType()); | |||||
} catch (NoSuchMethodException | SecurityException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | |||||
} | |||||
} | } |
@Test | @Test | ||||
public void binder_saveIfValid() { | public void binder_saveIfValid() { | ||||
BeanBinder<BookPerson> binder = new BeanBinder<>(BookPerson.class); | |||||
Binder<BookPerson> binder = new Binder<>(BookPerson.class); | |||||
// Phone or email has to be specified for the bean | // Phone or email has to be specified for the bean | ||||
Validator<BookPerson> phoneOrEmail = Validator.from( | Validator<BookPerson> phoneOrEmail = Validator.from( | ||||
public void withBinderStatusLabelExample() { | public void withBinderStatusLabelExample() { | ||||
Label formStatusLabel = new Label(); | Label formStatusLabel = new Label(); | ||||
BeanBinder<BookPerson> binder = new BeanBinder<>(BookPerson.class); | |||||
Binder<BookPerson> binder = new Binder<>(BookPerson.class); | |||||
binder.setStatusLabel(formStatusLabel); | binder.setStatusLabel(formStatusLabel); | ||||
/* | |||||
* Copyright 2000-2016 Vaadin Ltd. | |||||
* | |||||
* 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 | |||||
* | |||||
* http://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 com.vaadin.data; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import java.util.Optional; | |||||
import java.util.stream.Stream; | |||||
import org.junit.Assert; | |||||
import org.junit.Test; | |||||
import com.vaadin.data.Binder.BindingBuilder; | |||||
import com.vaadin.server.Setter; | |||||
import com.vaadin.ui.TextField; | |||||
public class BinderCustomPropertySetTest { | |||||
public static class MapPropertyDefinition | |||||
implements BinderPropertyDefinition<Map<String, String>, String> { | |||||
private MapPropertySet propertySet; | |||||
private String name; | |||||
public MapPropertyDefinition(MapPropertySet propertySet, String name) { | |||||
this.propertySet = propertySet; | |||||
this.name = name; | |||||
} | |||||
@Override | |||||
public ValueProvider<Map<String, String>, String> getGetter() { | |||||
return map -> map.get(name); | |||||
} | |||||
@Override | |||||
public Optional<Setter<Map<String, String>, String>> getSetter() { | |||||
return Optional.of((map, value) -> { | |||||
if (value == null) { | |||||
map.remove(name); | |||||
} else { | |||||
map.put(name, value); | |||||
} | |||||
}); | |||||
} | |||||
@Override | |||||
public Class<String> getType() { | |||||
return String.class; | |||||
} | |||||
@Override | |||||
public BindingBuilder<Map<String, String>, String> beforeBind( | |||||
BindingBuilder<Map<String, String>, String> originalBuilder) { | |||||
return originalBuilder; | |||||
} | |||||
@Override | |||||
public String getName() { | |||||
return name; | |||||
} | |||||
@Override | |||||
public BinderPropertySet<Map<String, String>> getPropertySet() { | |||||
return propertySet; | |||||
} | |||||
} | |||||
public static class MapPropertySet | |||||
implements BinderPropertySet<Map<String, String>> { | |||||
@Override | |||||
public Stream<BinderPropertyDefinition<Map<String, String>, ?>> getProperties() { | |||||
return Stream.of("one", "two", "three").map(this::createProperty); | |||||
} | |||||
@Override | |||||
public Optional<BinderPropertyDefinition<Map<String, String>, ?>> getProperty( | |||||
String name) { | |||||
return Optional.of(createProperty(name)); | |||||
} | |||||
private BinderPropertyDefinition<Map<String, String>, ?> createProperty( | |||||
String name) { | |||||
return new MapPropertyDefinition(this, name); | |||||
} | |||||
} | |||||
public static class InstanceFields { | |||||
private TextField one; | |||||
private TextField another; | |||||
} | |||||
@Test | |||||
public void testBindByString() { | |||||
TextField field = new TextField(); | |||||
Map<String, String> map = new HashMap<>(); | |||||
Binder<Map<String, String>> binder = Binder | |||||
.withPropertySet(new MapPropertySet()); | |||||
binder.bind(field, "key"); | |||||
binder.setBean(map); | |||||
field.setValue("value"); | |||||
Assert.assertEquals( | |||||
"Field value should propagate to the corresponding key in the map", | |||||
"value", map.get("key")); | |||||
} | |||||
@Test | |||||
public void testBindInstanceFields() { | |||||
Map<String, String> map = new HashMap<>(); | |||||
Binder<Map<String, String>> binder = Binder | |||||
.withPropertySet(new MapPropertySet()); | |||||
InstanceFields instanceFields = new InstanceFields(); | |||||
binder.bindInstanceFields(instanceFields); | |||||
Assert.assertNotNull( | |||||
"Field corresponding to supported property name should be bound", | |||||
instanceFields.one); | |||||
Assert.assertNull( | |||||
"Field corresponding to unsupported property name should be ignored", | |||||
instanceFields.another); | |||||
binder.setBean(map); | |||||
instanceFields.one.setValue("value"); | |||||
Assert.assertEquals( | |||||
"Field value should propagate to the corresponding key in the map", | |||||
"value", map.get("one")); | |||||
} | |||||
} |
import com.vaadin.ui.FormLayout; | import com.vaadin.ui.FormLayout; | ||||
import com.vaadin.ui.TextField; | import com.vaadin.ui.TextField; | ||||
/** | |||||
* Unit tests for {@link BeanBinder#bindInstanceFields(Object)} method. | |||||
* | |||||
* @author Vaadin Ltd | |||||
* | |||||
*/ | |||||
public class BeanBinderInstanceFieldTest { | |||||
public class BinderInstanceFieldTest { | |||||
public static class BindAllFields extends FormLayout { | public static class BindAllFields extends FormLayout { | ||||
private TextField firstName; | private TextField firstName; | ||||
@Test | @Test | ||||
public void bindInstanceFields_bindAllFields() { | public void bindInstanceFields_bindAllFields() { | ||||
BindAllFields form = new BindAllFields(); | BindAllFields form = new BindAllFields(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.bindInstanceFields(form); | binder.bindInstanceFields(form); | ||||
Person person = new Person(); | Person person = new Person(); | ||||
Assert.assertEquals(form.birthDate.getValue(), person.getBirthDate()); | Assert.assertEquals(form.birthDate.getValue(), person.getBirthDate()); | ||||
} | } | ||||
@Test(expected = IllegalStateException.class) | |||||
public void bind_instanceFields_noArgsConstructor() { | |||||
BindAllFields form = new BindAllFields(); | |||||
Binder<Person> binder = new Binder<>(); | |||||
binder.bindInstanceFields(form); | |||||
} | |||||
@Test | @Test | ||||
public void bindInstanceFields_bindOnlyOneFields() { | public void bindInstanceFields_bindOnlyOneFields() { | ||||
BindOnlyOneField form = new BindOnlyOneField(); | BindOnlyOneField form = new BindOnlyOneField(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.bindInstanceFields(form); | binder.bindInstanceFields(form); | ||||
Person person = new Person(); | Person person = new Person(); | ||||
@Test | @Test | ||||
public void bindInstanceFields_bindNotHasValueField_fieldIsNull() { | public void bindInstanceFields_bindNotHasValueField_fieldIsNull() { | ||||
BindNoHasValueField form = new BindNoHasValueField(); | BindNoHasValueField form = new BindNoHasValueField(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.bindInstanceFields(form); | binder.bindInstanceFields(form); | ||||
Person person = new Person(); | Person person = new Person(); | ||||
@Test | @Test | ||||
public void bindInstanceFields_genericField() { | public void bindInstanceFields_genericField() { | ||||
BindGenericField form = new BindGenericField(); | BindGenericField form = new BindGenericField(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.bindInstanceFields(form); | binder.bindInstanceFields(form); | ||||
Person person = new Person(); | Person person = new Person(); | ||||
@Test(expected = IllegalStateException.class) | @Test(expected = IllegalStateException.class) | ||||
public void bindInstanceFields_genericFieldWithWrongTypeParameter() { | public void bindInstanceFields_genericFieldWithWrongTypeParameter() { | ||||
BindGenericWrongTypeParameterField form = new BindGenericWrongTypeParameterField(); | BindGenericWrongTypeParameterField form = new BindGenericWrongTypeParameterField(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.bindInstanceFields(form); | binder.bindInstanceFields(form); | ||||
} | } | ||||
@Test(expected = IllegalStateException.class) | @Test(expected = IllegalStateException.class) | ||||
public void bindInstanceFields_generic() { | public void bindInstanceFields_generic() { | ||||
BindGeneric<String> form = new BindGeneric<>(); | BindGeneric<String> form = new BindGeneric<>(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.bindInstanceFields(form); | binder.bindInstanceFields(form); | ||||
} | } | ||||
@Test(expected = IllegalStateException.class) | @Test(expected = IllegalStateException.class) | ||||
public void bindInstanceFields_rawFieldType() { | public void bindInstanceFields_rawFieldType() { | ||||
BindRaw form = new BindRaw(); | BindRaw form = new BindRaw(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.bindInstanceFields(form); | binder.bindInstanceFields(form); | ||||
} | } | ||||
@Test(expected = IllegalStateException.class) | @Test(expected = IllegalStateException.class) | ||||
public void bindInstanceFields_abstractFieldType() { | public void bindInstanceFields_abstractFieldType() { | ||||
BindAbstract form = new BindAbstract(); | BindAbstract form = new BindAbstract(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.bindInstanceFields(form); | binder.bindInstanceFields(form); | ||||
} | } | ||||
@Test(expected = IllegalStateException.class) | @Test(expected = IllegalStateException.class) | ||||
public void bindInstanceFields_noInstantiatableFieldType() { | public void bindInstanceFields_noInstantiatableFieldType() { | ||||
BindNonInstantiatableType form = new BindNonInstantiatableType(); | BindNonInstantiatableType form = new BindNonInstantiatableType(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.bindInstanceFields(form); | binder.bindInstanceFields(form); | ||||
} | } | ||||
@Test(expected = IllegalStateException.class) | @Test(expected = IllegalStateException.class) | ||||
public void bindInstanceFields_wrongFieldType() { | public void bindInstanceFields_wrongFieldType() { | ||||
BindWrongTypeParameterField form = new BindWrongTypeParameterField(); | BindWrongTypeParameterField form = new BindWrongTypeParameterField(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.bindInstanceFields(form); | binder.bindInstanceFields(form); | ||||
} | } | ||||
@Test | @Test | ||||
public void bindInstanceFields_complexGenericHierarchy() { | public void bindInstanceFields_complexGenericHierarchy() { | ||||
BindComplextHierarchyGenericType form = new BindComplextHierarchyGenericType(); | BindComplextHierarchyGenericType form = new BindComplextHierarchyGenericType(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.bindInstanceFields(form); | binder.bindInstanceFields(form); | ||||
Person person = new Person(); | Person person = new Person(); | ||||
@Test | @Test | ||||
public void bindInstanceFields_bindNotHasValueField_fieldIsNotReplaced() { | public void bindInstanceFields_bindNotHasValueField_fieldIsNotReplaced() { | ||||
BindNoHasValueField form = new BindNoHasValueField(); | BindNoHasValueField form = new BindNoHasValueField(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
String name = "foo"; | String name = "foo"; | ||||
form.firstName = name; | form.firstName = name; | ||||
@Test | @Test | ||||
public void bindInstanceFields_bindAllFieldsUsingAnnotations() { | public void bindInstanceFields_bindAllFieldsUsingAnnotations() { | ||||
BindFieldsUsingAnnotation form = new BindFieldsUsingAnnotation(); | BindFieldsUsingAnnotation form = new BindFieldsUsingAnnotation(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.bindInstanceFields(form); | binder.bindInstanceFields(form); | ||||
Person person = new Person(); | Person person = new Person(); | ||||
@Test | @Test | ||||
public void bindInstanceFields_bindNotBoundFieldsOnly_customBindingIsNotReplaced() { | public void bindInstanceFields_bindNotBoundFieldsOnly_customBindingIsNotReplaced() { | ||||
BindAllFields form = new BindAllFields(); | BindAllFields form = new BindAllFields(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
TextField name = new TextField(); | TextField name = new TextField(); | ||||
form.firstName = name; | form.firstName = name; | ||||
@Test | @Test | ||||
public void bindInstanceFields_fieldsAreConfigured_customBindingIsNotReplaced() { | public void bindInstanceFields_fieldsAreConfigured_customBindingIsNotReplaced() { | ||||
BindOnlyOneField form = new BindOnlyOneField(); | BindOnlyOneField form = new BindOnlyOneField(); | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
TextField name = new TextField(); | TextField name = new TextField(); | ||||
form.firstName = name; | form.firstName = name; |
@Test | @Test | ||||
public void beanBinder_nullRepresentationIsNotDisabled() { | public void beanBinder_nullRepresentationIsNotDisabled() { | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.forField(nameField).bind("firstName"); | binder.forField(nameField).bind("firstName"); | ||||
Person person = new Person(); | Person person = new Person(); | ||||
@Test | @Test | ||||
public void beanBinder_withConverter_nullRepresentationIsNotDisabled() { | public void beanBinder_withConverter_nullRepresentationIsNotDisabled() { | ||||
String customNullPointerRepresentation = "foo"; | String customNullPointerRepresentation = "foo"; | ||||
BeanBinder<Person> binder = new BeanBinder<>(Person.class); | |||||
Binder<Person> binder = new Binder<>(Person.class); | |||||
binder.forField(nameField) | binder.forField(nameField) | ||||
.withConverter(value -> value, value -> value == null | .withConverter(value -> value, value -> value == null | ||||
? customNullPointerRepresentation : value) | ? customNullPointerRepresentation : value) | ||||
firstNameField.setValue(""); | firstNameField.setValue(""); | ||||
Assert.assertEquals(6, invokes.get()); | Assert.assertEquals(6, invokes.get()); | ||||
} | } | ||||
@Test(expected = IllegalStateException.class) | |||||
public void noArgsConstructor_stringBind_throws() { | |||||
binder.bind(new TextField(), "firstName"); | |||||
} | |||||
} | } |
public void execute() { | public void execute() { | ||||
Assert.assertFalse(BeanUtil.checkBeanValidationAvailable()); | Assert.assertFalse(BeanUtil.checkBeanValidationAvailable()); | ||||
BeanBinder<BeanToValidate> binder = new BeanBinder<>( | |||||
BeanToValidate.class); | |||||
Binder<BeanToValidate> binder = new Binder<>(BeanToValidate.class); | |||||
BeanToValidate item = new BeanToValidate(); | BeanToValidate item = new BeanToValidate(); | ||||
String name = "Johannes"; | String name = "Johannes"; | ||||
item.setFirstname(name); | item.setFirstname(name); |