Kaynağa Gözat

Integrate BeanBinder functionality into Binder (#8096)

* Integrate BeanBinder functionality into Binder
tags/8.0.0.beta2
Leif Åstrand 7 yıl önce
ebeveyn
işleme
2fd7e13c42

+ 1
- 1
all/src/main/templates/release-notes.html Dosyayı Görüntüle

@@ -120,7 +120,7 @@
<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>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><tt>DataProvider</tt> is the replacement of <tt>Container</tt></li>
<ul>

+ 0
- 1
documentation/components/components-grid.asciidoc Dosyayı Görüntüle

@@ -765,7 +765,6 @@ grid.getEditor().setSaveCaption("Tallenna");
grid.getEditor().setCancelCaption("Peruuta"));
----


[[components.grid.editing.validation]]
=== Handling Validation Errors


+ 11
- 10
documentation/datamodel/datamodel-forms.asciidoc Dosyayı Görüntüle

@@ -417,12 +417,13 @@ If some other part of the application is also using the same instance, then that
== Binding Beans to Forms

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]
----
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);

// Bind based on property name
binder.bind(nameField, "name");
@@ -436,9 +437,9 @@ binder.forField(yearOfBirthField)
----

[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.

[source, java]
@@ -461,7 +462,7 @@ Constraint annotations can also be defined on the bean level instead of being de
[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`.

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.

@@ -469,7 +470,7 @@ Similarly to how the [methodname]#withStatusLabel# method can be used for defini
----
Label formStatusLabel = new Label();

BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);

binder.setStatusLabel(formStatusLabel);

@@ -541,14 +542,14 @@ public class PersonFormDesign extends FormLayout {
}
----

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.

[source, java]
----
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) {
binder.bindInstanceFields(this);

+ 8
- 10
server/src/main/java/com/vaadin/annotations/PropertyId.java Dosyayı Görüntüle

@@ -20,20 +20,18 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.vaadin.data.BeanBinder;
import com.vaadin.data.Binder;
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>
* 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>
* In following usage example, the text field would be bound to property "foo"
* in the Entity class. <code>
@@ -49,7 +47,7 @@ import com.vaadin.data.HasValue;

{
Editor editor = new Editor();
BeanBinder<Entity> binder = new BeanBinder(Entity.class);
Binder&lt;Entity&gt; binder = new Binder(Entity.class);
binder.bindInstanceFields(editor);
}
</pre>

+ 0
- 679
server/src/main/java/com/vaadin/data/BeanBinder.java Dosyayı Görüntüle

@@ -1,679 +0,0 @@
/*
* 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");
* &#64;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("_", "");
}

}

+ 217
- 0
server/src/main/java/com/vaadin/data/BeanBinderPropertySet.java Dosyayı Görüntüle

@@ -0,0 +1,217 @@
/*
* 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();
}
}

+ 480
- 8
server/src/main/java/com/vaadin/data/Binder.java Dosyayı Görüntüle

@@ -16,10 +16,14 @@
package com.vaadin.data;

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.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -28,10 +32,15 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
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.converter.StringToIntegerConverter;
import com.vaadin.data.validator.BeanValidator;
import com.vaadin.event.EventRouter;
import com.vaadin.server.ErrorMessage;
import com.vaadin.server.SerializableFunction;
@@ -43,6 +52,7 @@ import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI;
import com.vaadin.util.ReflectTools;

/**
* Connects one or more {@code Field} components to properties of a backing data
@@ -181,6 +191,37 @@ public class Binder<BEAN> implements Serializable {
public Binding<BEAN, TARGET> bind(ValueProvider<BEAN, TARGET> getter,
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
* registration order, when the field value is written to the backing
@@ -559,6 +600,46 @@ public class Binder<BEAN> implements Serializable {
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
public BindingBuilder<BEAN, TARGET> withValidator(
Validator<? super TARGET> validator) {
@@ -960,6 +1041,15 @@ public class Binder<BEAN> implements Serializable {
}
}

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 final Set<BindingImpl<BEAN, ?, ?>> bindings = new LinkedHashSet<>();
@@ -978,6 +1068,84 @@ public class Binder<BEAN> implements Serializable {

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
* bean is not currently bound.
@@ -1020,6 +1188,29 @@ public class Binder<BEAN> implements Serializable {
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
* setter pair. The functions are used to update the field value from the
@@ -1034,7 +1225,7 @@ public class Binder<BEAN> implements Serializable {
* {@link HasValue#getEmptyValue()}. This conversion is one-way only, if you
* want to have a two-way mapping back to {@code null}, use
* {@link #forField(HasValue)} and
* {@link BindingBuilder#withNullRepresentation(Object))}.
* {@link BindingBuilder#withNullRepresentation(Object)}.
* <p>
* 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
@@ -1078,6 +1269,42 @@ public class Binder<BEAN> implements Serializable {
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
* {@code null} value removes a currently bound bean.
@@ -1400,9 +1627,9 @@ public class Binder<BEAN> implements Serializable {
* Only the one validation error message is shown in this label at a time.
* <p>
* 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
* the status label to set
@@ -1463,8 +1690,8 @@ public class Binder<BEAN> implements Serializable {
* Gets the status handler of this form.
* <p>
* 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>
* @see #setValidationStatusHandler(BinderValidationStatusHandler)
@@ -1529,6 +1756,9 @@ public class Binder<BEAN> implements Serializable {
BindingValidationStatusHandler handler) {
BindingBuilder<BEAN, TARGET> newBinding = doCreateBinding(field,
converter, handler);
if (incompleteMemberFieldBindings.containsKey(field)) {
incompleteMemberFieldBindings.put(field, newBinding);
}
incompleteBindings.put(field, newBinding);
return newBinding;
}
@@ -1722,17 +1952,259 @@ public class Binder<BEAN> implements Serializable {

/**
* Throws if this binder has incomplete bindings.
*
*
* @param methodName
* name of the method where this call is originated from
* @throws IllegalStateException
* 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()) {
throw new IllegalStateException(
"All bindings created with forField must be completed before calling "
+ 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");
* &#64;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&lt;T&gt; 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("_", "");
}

}

+ 85
- 0
server/src/main/java/com/vaadin/data/BinderPropertyDefinition.java Dosyayı Görüntüle

@@ -0,0 +1,85 @@
/*
* 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();
}

+ 50
- 0
server/src/main/java/com/vaadin/data/BinderPropertySet.java Dosyayı Görüntüle

@@ -0,0 +1,50 @@
/*
* 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);
}

+ 0
- 11
server/src/main/java/com/vaadin/data/BinderValidationStatus.java Dosyayı Görüntüle

@@ -23,7 +23,6 @@ import java.util.Objects;
import java.util.stream.Collectors;

import com.vaadin.data.Binder.BindingBuilder;
import com.vaadin.data.validator.BeanValidator;

/**
* Binder validation status change. Represents the outcome of binder level
@@ -160,11 +159,6 @@ public class BinderValidationStatus<BEAN> implements Serializable {

/**
* 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
*/
@@ -187,11 +181,6 @@ public class BinderValidationStatus<BEAN> implements Serializable {

/**
* 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
*/

+ 52
- 0
server/src/test/java/com/vaadin/data/BeanBinderPropertySetTest.java Dosyayı Görüntüle

@@ -0,0 +1,52 @@
/*
* 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);
}
}

+ 7
- 32
server/src/test/java/com/vaadin/data/BeanBinderTest.java Dosyayı Görüntüle

@@ -3,23 +3,19 @@ package com.vaadin.data;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

import org.junit.Assert;
import org.junit.Before;
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.tests.data.bean.BeanToValidate;
import com.vaadin.ui.CheckBoxGroup;
import com.vaadin.ui.TextField;

public class BeanBinderTest
extends BinderTestBase<BeanBinder<BeanToValidate>, BeanToValidate> {
extends BinderTestBase<Binder<BeanToValidate>, BeanToValidate> {

private enum TestEnum {
}
@@ -52,7 +48,7 @@ public class BeanBinderTest

@Before
public void setUp() {
binder = new BeanBinder<>(BeanToValidate.class);
binder = new Binder<>(BeanToValidate.class);
item = new BeanToValidate();
item.setFirstname("Johannes");
item.setAge(32);
@@ -60,11 +56,10 @@ public class BeanBinderTest

@Test
public void bindInstanceFields_parameters_type_erased() {
BeanBinder<TestBean> otherBinder = new BeanBinder<>(TestBean.class);
Binder<TestBean> otherBinder = new Binder<>(TestBean.class);
TestClass testClass = new TestClass();
otherBinder.forField(testClass.number)
.withConverter(new StringToIntegerConverter(""))
.bind("number");
.withConverter(new StringToIntegerConverter("")).bind("number");

// Should correctly bind the enum field without throwing
otherBinder.bindInstanceFields(testClass);
@@ -72,7 +67,7 @@ public class BeanBinderTest

@Test
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();

otherBinder.forMemberField(testClass.number)
@@ -87,7 +82,7 @@ public class BeanBinderTest

@Test(expected = IllegalStateException.class)
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();

otherBinder.forField(testClass.number)
@@ -100,7 +95,7 @@ public class BeanBinderTest

@Test(expected = IllegalStateException.class)
public void incomplete_forMemberField_bindings() {
BeanBinder<TestBean> otherBinder = new BeanBinder<>(TestBean.class);
Binder<TestBean> otherBinder = new Binder<>(TestBean.class);
TestClass testClass = new TestClass();

otherBinder.forMemberField(testClass.number)
@@ -262,24 +257,4 @@ public class BeanBinderTest
assertSame(field, errors.get(0).getField());
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);
}
}
}
}

+ 2
- 2
server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java Dosyayı Görüntüle

@@ -461,7 +461,7 @@ public class BinderBookOfVaadinTest {

@Test
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
Validator<BookPerson> phoneOrEmail = Validator.from(
@@ -585,7 +585,7 @@ public class BinderBookOfVaadinTest {
public void withBinderStatusLabelExample() {
Label formStatusLabel = new Label();

BeanBinder<BookPerson> binder = new BeanBinder<>(BookPerson.class);
Binder<BookPerson> binder = new Binder<>(BookPerson.class);

binder.setStatusLabel(formStatusLabel);


+ 144
- 0
server/src/test/java/com/vaadin/data/BinderCustomPropertySetTest.java Dosyayı Görüntüle

@@ -0,0 +1,144 @@
/*
* 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"));
}
}

server/src/test/java/com/vaadin/data/BeanBinderInstanceFieldTest.java → server/src/test/java/com/vaadin/data/BinderInstanceFieldTest.java Dosyayı Görüntüle

@@ -30,13 +30,7 @@ import com.vaadin.ui.DateField;
import com.vaadin.ui.FormLayout;
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 {
private TextField firstName;
@@ -132,7 +126,7 @@ public class BeanBinderInstanceFieldTest {
@Test
public void bindInstanceFields_bindAllFields() {
BindAllFields form = new BindAllFields();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.bindInstanceFields(form);

Person person = new Person();
@@ -151,10 +145,17 @@ public class BeanBinderInstanceFieldTest {
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
public void bindInstanceFields_bindOnlyOneFields() {
BindOnlyOneField form = new BindOnlyOneField();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.bindInstanceFields(form);

Person person = new Person();
@@ -174,7 +175,7 @@ public class BeanBinderInstanceFieldTest {
@Test
public void bindInstanceFields_bindNotHasValueField_fieldIsNull() {
BindNoHasValueField form = new BindNoHasValueField();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.bindInstanceFields(form);

Person person = new Person();
@@ -188,7 +189,7 @@ public class BeanBinderInstanceFieldTest {
@Test
public void bindInstanceFields_genericField() {
BindGenericField form = new BindGenericField();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.bindInstanceFields(form);

Person person = new Person();
@@ -206,49 +207,49 @@ public class BeanBinderInstanceFieldTest {
@Test(expected = IllegalStateException.class)
public void bindInstanceFields_genericFieldWithWrongTypeParameter() {
BindGenericWrongTypeParameterField form = new BindGenericWrongTypeParameterField();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.bindInstanceFields(form);
}

@Test(expected = IllegalStateException.class)
public void bindInstanceFields_generic() {
BindGeneric<String> form = new BindGeneric<>();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.bindInstanceFields(form);
}

@Test(expected = IllegalStateException.class)
public void bindInstanceFields_rawFieldType() {
BindRaw form = new BindRaw();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.bindInstanceFields(form);
}

@Test(expected = IllegalStateException.class)
public void bindInstanceFields_abstractFieldType() {
BindAbstract form = new BindAbstract();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.bindInstanceFields(form);
}

@Test(expected = IllegalStateException.class)
public void bindInstanceFields_noInstantiatableFieldType() {
BindNonInstantiatableType form = new BindNonInstantiatableType();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.bindInstanceFields(form);
}

@Test(expected = IllegalStateException.class)
public void bindInstanceFields_wrongFieldType() {
BindWrongTypeParameterField form = new BindWrongTypeParameterField();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.bindInstanceFields(form);
}

@Test
public void bindInstanceFields_complexGenericHierarchy() {
BindComplextHierarchyGenericType form = new BindComplextHierarchyGenericType();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.bindInstanceFields(form);

Person person = new Person();
@@ -266,7 +267,7 @@ public class BeanBinderInstanceFieldTest {
@Test
public void bindInstanceFields_bindNotHasValueField_fieldIsNotReplaced() {
BindNoHasValueField form = new BindNoHasValueField();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);

String name = "foo";
form.firstName = name;
@@ -284,7 +285,7 @@ public class BeanBinderInstanceFieldTest {
@Test
public void bindInstanceFields_bindAllFieldsUsingAnnotations() {
BindFieldsUsingAnnotation form = new BindFieldsUsingAnnotation();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.bindInstanceFields(form);

Person person = new Person();
@@ -308,7 +309,7 @@ public class BeanBinderInstanceFieldTest {
@Test
public void bindInstanceFields_bindNotBoundFieldsOnly_customBindingIsNotReplaced() {
BindAllFields form = new BindAllFields();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);

TextField name = new TextField();
form.firstName = name;
@@ -343,7 +344,7 @@ public class BeanBinderInstanceFieldTest {
@Test
public void bindInstanceFields_fieldsAreConfigured_customBindingIsNotReplaced() {
BindOnlyOneField form = new BindOnlyOneField();
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);

TextField name = new TextField();
form.firstName = name;

+ 7
- 2
server/src/test/java/com/vaadin/data/BinderTest.java Dosyayı Görüntüle

@@ -285,7 +285,7 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {

@Test
public void beanBinder_nullRepresentationIsNotDisabled() {
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.forField(nameField).bind("firstName");

Person person = new Person();
@@ -297,7 +297,7 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
@Test
public void beanBinder_withConverter_nullRepresentationIsNotDisabled() {
String customNullPointerRepresentation = "foo";
BeanBinder<Person> binder = new BeanBinder<>(Person.class);
Binder<Person> binder = new Binder<>(Person.class);
binder.forField(nameField)
.withConverter(value -> value, value -> value == null
? customNullPointerRepresentation : value)
@@ -426,4 +426,9 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
firstNameField.setValue("");
Assert.assertEquals(6, invokes.get());
}

@Test(expected = IllegalStateException.class)
public void noArgsConstructor_stringBind_throws() {
binder.bind(new TextField(), "firstName");
}
}

+ 1
- 2
server/src/test/java/com/vaadin/data/Jsr303Test.java Dosyayı Görüntüle

@@ -84,8 +84,7 @@ public class Jsr303Test {
public void execute() {
Assert.assertFalse(BeanUtil.checkBeanValidationAvailable());

BeanBinder<BeanToValidate> binder = new BeanBinder<>(
BeanToValidate.class);
Binder<BeanToValidate> binder = new Binder<>(BeanToValidate.class);
BeanToValidate item = new BeanToValidate();
String name = "Johannes";
item.setFirstname(name);

Loading…
İptal
Kaydet