diff options
author | Denis Anisimov <denis@vaadin.com> | 2016-08-26 15:04:05 +0300 |
---|---|---|
committer | Denis Anisimov <denis@vaadin.com> | 2016-10-25 09:12:41 +0300 |
commit | 0decd87411d98cf0d03db35fb6e5d70637ff864c (patch) | |
tree | 80af2fd0a2cd35c8ed613987f70c66c049088d4b | |
parent | eb39bd3df37fe0704c7bf2de95a85a7bf1e627a8 (diff) | |
download | vaadin-framework-0decd87411d98cf0d03db35fb6e5d70637ff864c.tar.gz vaadin-framework-0decd87411d98cf0d03db35fb6e5d70637ff864c.zip |
Binder.bindInstanceFields(Object) method implementation (#47).
Binds class instance fields using reflection using Binder.forField()
Change-Id: I597f3832d112cfa69c73fb185f1564c482e4eb15
16 files changed, 656 insertions, 26 deletions
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/fieldgroup/FieldGroup.java b/compatibility-server/src/main/java/com/vaadin/v7/data/fieldgroup/FieldGroup.java index 50ad9a843a..1055d1921d 100644 --- a/compatibility-server/src/main/java/com/vaadin/v7/data/fieldgroup/FieldGroup.java +++ b/compatibility-server/src/main/java/com/vaadin/v7/data/fieldgroup/FieldGroup.java @@ -25,6 +25,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import com.vaadin.annotations.PropertyId; import com.vaadin.util.ReflectTools; import com.vaadin.v7.data.Item; import com.vaadin.v7.data.Property; diff --git a/compatibility-server/src/test/java/com/vaadin/v7/data/util/ReflectToolsGetSuperFieldTest.java b/compatibility-server/src/test/java/com/vaadin/v7/data/util/ReflectToolsGetSuperFieldTest.java index 2876181b90..4341bb000f 100644 --- a/compatibility-server/src/test/java/com/vaadin/v7/data/util/ReflectToolsGetSuperFieldTest.java +++ b/compatibility-server/src/test/java/com/vaadin/v7/data/util/ReflectToolsGetSuperFieldTest.java @@ -4,8 +4,8 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; +import com.vaadin.annotations.PropertyId; import com.vaadin.v7.data.fieldgroup.FieldGroup; -import com.vaadin.v7.data.fieldgroup.PropertyId; import com.vaadin.v7.ui.TextField; public class ReflectToolsGetSuperFieldTest { diff --git a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/fieldgroup/BeanFieldGroupTest.java b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/fieldgroup/BeanFieldGroupTest.java index cae04a28f5..e8ed7a979c 100644 --- a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/fieldgroup/BeanFieldGroupTest.java +++ b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/fieldgroup/BeanFieldGroupTest.java @@ -5,10 +5,10 @@ import static org.junit.Assert.assertEquals; import org.junit.Assert; import org.junit.Test; +import com.vaadin.annotations.PropertyId; import com.vaadin.v7.data.Item; import com.vaadin.v7.data.fieldgroup.BeanFieldGroup; import com.vaadin.v7.data.fieldgroup.FieldGroup.CommitException; -import com.vaadin.v7.data.fieldgroup.PropertyId; import com.vaadin.v7.data.util.BeanItem; import com.vaadin.v7.ui.Field; import com.vaadin.v7.ui.RichTextArea; diff --git a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/fieldgroup/FieldNamedDescriptionTest.java b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/fieldgroup/FieldNamedDescriptionTest.java index bd9f206751..194dd1d350 100644 --- a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/fieldgroup/FieldNamedDescriptionTest.java +++ b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/fieldgroup/FieldNamedDescriptionTest.java @@ -4,9 +4,9 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; +import com.vaadin.annotations.PropertyId; import com.vaadin.ui.FormLayout; import com.vaadin.v7.data.fieldgroup.FieldGroup; -import com.vaadin.v7.data.fieldgroup.PropertyId; import com.vaadin.v7.data.util.ObjectProperty; import com.vaadin.v7.data.util.PropertysetItem; import com.vaadin.v7.ui.TextField; diff --git a/server/pom.xml b/server/pom.xml index 616558cdea..4d65f1046c 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -80,6 +80,13 @@ <artifactId>jsoup</artifactId> </dependency> + <!-- Small reflection library --> + <dependency> + <groupId>com.googlecode.gentyref</groupId> + <artifactId>gentyref</artifactId> + <version>1.2.0</version> + </dependency> + <!-- TESTING DEPENDENCIES --> <!-- Test dependencies --> diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/fieldgroup/PropertyId.java b/server/src/main/java/com/vaadin/annotations/PropertyId.java index f9b22074f0..b914699edf 100644 --- a/compatibility-server/src/main/java/com/vaadin/v7/data/fieldgroup/PropertyId.java +++ b/server/src/main/java/com/vaadin/annotations/PropertyId.java @@ -13,25 +13,27 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.vaadin.v7.data.fieldgroup; +package com.vaadin.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import com.vaadin.v7.ui.Field; +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 FieldGroup} or {@link BeanFieldGroup}. + * {@link Binder} or {@link BeanBinder}. * <p> - * The automatic data binding in FieldGroup and BeanFieldGroup 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 com.vaadin.client.ui.Field}, - * 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 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. * <p> * In following usage example, the text field would be bound to property "foo" * in the Entity class. <code> @@ -46,8 +48,9 @@ import com.vaadin.v7.ui.Field; } { - Editor e = new Editor(); - BeanFieldGroup.bindFieldsUnbuffered(new Entity(), e); + Editor editor = new Editor(); + BeanBinder<Entity> binder = new BeanBinder(Entity.class); + binder.bindInstanceFields(editor); } </pre> * </code> @@ -57,7 +60,6 @@ import com.vaadin.v7.ui.Field; */ @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) -@Deprecated public @interface PropertyId { String value(); } diff --git a/server/src/main/java/com/vaadin/data/BeanBinder.java b/server/src/main/java/com/vaadin/data/BeanBinder.java index a57082357f..cc45e93c2d 100644 --- a/server/src/main/java/com/vaadin/data/BeanBinder.java +++ b/server/src/main/java/com/vaadin/data/BeanBinder.java @@ -18,10 +18,21 @@ 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.HashSet; +import java.util.List; +import java.util.Locale; 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.util.converter.Converter; import com.vaadin.data.validator.BeanValidator; @@ -184,6 +195,7 @@ public class BeanBinder<BEAN> extends Binder<BEAN> { getter = descriptor.getReadMethod(); setter = descriptor.getWriteMethod(); finalBinding.bind(this::getValue, this::setValue); + getBinder().boundProperties.add(propertyName); } @Override @@ -253,6 +265,7 @@ public class BeanBinder<BEAN> extends Binder<BEAN> { } private final Class<? extends BEAN> beanType; + private final Set<String> boundProperties; /** * Creates a new {@code BeanBinder} supporting beans of the given type. @@ -263,6 +276,7 @@ public class BeanBinder<BEAN> extends Binder<BEAN> { public BeanBinder(Class<? extends BEAN> beanType) { BeanUtil.checkBeanValidationAvailable(); this.beanType = beanType; + boundProperties = new HashSet<>(); } @Override @@ -317,4 +331,223 @@ public class BeanBinder<BEAN> extends Binder<BEAN> { 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, + (property, type) -> bindProperty(objectWithMemberFields, + memberField, property, type))); + } + + /** + * 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(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 manulaly 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) { + for (Field memberField : searchClass.getDeclaredFields()) { + memberFieldInOrder.add(memberField); + } + 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, + BiConsumer<String, Class<?>> propertyHandler) { + Optional<PropertyDescriptor> descriptor = getPropertyDescriptor(field); + + if (!descriptor.isPresent()) { + return; + } + + String propertyName = descriptor.get().getName(); + if (boundProperties.contains(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("_", ""); + } + } diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java index d3c2befefb..7cd4c4a873 100644 --- a/server/src/main/java/com/vaadin/data/Binder.java +++ b/server/src/main/java/com/vaadin/data/Binder.java @@ -1200,7 +1200,7 @@ public class Binder<BEAN> implements Serializable { * the handler to notify of status changes, not null * @return the new incomplete binding */ - protected <FIELDVALUE, TARGET> BindingImpl<BEAN, FIELDVALUE, TARGET> createBinding( + protected <FIELDVALUE, TARGET> Binding<BEAN, FIELDVALUE, TARGET> createBinding( HasValue<FIELDVALUE> field, Converter<FIELDVALUE, TARGET> converter, ValidationStatusHandler handler) { return new BindingImpl<>(this, field, converter, handler); diff --git a/server/src/test/java/com/vaadin/data/BeanBinderInstanceFieldTest.java b/server/src/test/java/com/vaadin/data/BeanBinderInstanceFieldTest.java new file mode 100644 index 0000000000..37b328f97c --- /dev/null +++ b/server/src/test/java/com/vaadin/data/BeanBinderInstanceFieldTest.java @@ -0,0 +1,387 @@ +/* + * 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.time.LocalDate; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.annotations.PropertyId; +import com.vaadin.data.util.converter.StringToIntegerConverter; +import com.vaadin.data.validator.StringLengthValidator; +import com.vaadin.tests.data.bean.Person; +import com.vaadin.ui.AbstractField; +import com.vaadin.ui.AbstractTextField; +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 static class BindAllFields extends FormLayout { + private TextField firstName; + private DateField birthDate; + } + + public static class BindFieldsUsingAnnotation extends FormLayout { + @PropertyId("firstName") + private TextField nameField; + + @PropertyId("birthDate") + private DateField birthDateField; + } + + public static class BindOnlyOneField extends FormLayout { + private TextField firstName; + private TextField noFieldInPerson; + } + + public static class BindNoHasValueField extends FormLayout { + private String firstName; + } + + public static class BindGenericField extends FormLayout { + private CustomField<String> firstName; + } + + public static class BindGenericWrongTypeParameterField extends FormLayout { + private CustomField<Boolean> firstName; + } + + public static class BindWrongTypeParameterField extends FormLayout { + private IntegerTextField firstName; + } + + public static class BindGeneric<T> extends FormLayout { + private CustomField<T> firstName; + } + + public static class BindRaw extends FormLayout { + private CustomField firstName; + } + + public static class BindAbstract extends FormLayout { + private AbstractTextField firstName; + } + + public static class BindNonInstantiatableType extends FormLayout { + private NoDefaultCtor firstName; + } + + public static class BindComplextHierarchyGenericType extends FormLayout { + private ComplexHierarchy firstName; + } + + public static class NoDefaultCtor extends TextField { + public NoDefaultCtor(int arg) { + } + } + + public static class IntegerTextField extends CustomField<Integer> { + + } + + public static class ComplexHierarchy extends Generic<Long> { + + } + + public static class Generic<T> extends ComplexGeneric<Boolean, String, T> { + + } + + public static class ComplexGeneric<U, V, S> extends CustomField<V> { + + } + + public static class CustomField<T> extends AbstractField<T> { + + private T value; + + @Override + public T getValue() { + return value; + } + + @Override + protected void doSetValue(T value) { + this.value = value; + } + + } + + @Test + public void bindInstanceFields_bindAllFields() { + BindAllFields form = new BindAllFields(); + BeanBinder<Person> binder = new BeanBinder<>(Person.class); + binder.bindInstanceFields(form); + + Person person = new Person(); + person.setFirstName("foo"); + person.setBirthDate(LocalDate.now()); + + binder.bind(person); + + Assert.assertEquals(person.getFirstName(), form.firstName.getValue()); + Assert.assertEquals(person.getBirthDate(), form.birthDate.getValue()); + + form.firstName.setValue("bar"); + form.birthDate.setValue(person.getBirthDate().plusDays(345)); + + Assert.assertEquals(form.firstName.getValue(), person.getFirstName()); + Assert.assertEquals(form.birthDate.getValue(), person.getBirthDate()); + } + + @Test + public void bindInstanceFields_bindOnlyOneFields() { + BindOnlyOneField form = new BindOnlyOneField(); + BeanBinder<Person> binder = new BeanBinder<>(Person.class); + binder.bindInstanceFields(form); + + Person person = new Person(); + person.setFirstName("foo"); + + binder.bind(person); + + Assert.assertEquals(person.getFirstName(), form.firstName.getValue()); + + Assert.assertNull(form.noFieldInPerson); + + form.firstName.setValue("bar"); + + Assert.assertEquals(form.firstName.getValue(), person.getFirstName()); + } + + @Test + public void bindInstanceFields_bindNotHasValueField_fieldIsNull() { + BindNoHasValueField form = new BindNoHasValueField(); + BeanBinder<Person> binder = new BeanBinder<>(Person.class); + binder.bindInstanceFields(form); + + Person person = new Person(); + person.setFirstName("foo"); + + binder.bind(person); + + Assert.assertNull(form.firstName); + } + + @Test + public void bindInstanceFields_genericField() { + BindGenericField form = new BindGenericField(); + BeanBinder<Person> binder = new BeanBinder<>(Person.class); + binder.bindInstanceFields(form); + + Person person = new Person(); + person.setFirstName("foo"); + + binder.bind(person); + + Assert.assertEquals(person.getFirstName(), form.firstName.getValue()); + + form.firstName.setValue("bar"); + + Assert.assertEquals(form.firstName.getValue(), person.getFirstName()); + } + + @Test(expected = IllegalStateException.class) + public void bindInstanceFields_genericFieldWithWrongTypeParameter() { + BindGenericWrongTypeParameterField form = new BindGenericWrongTypeParameterField(); + BeanBinder<Person> binder = new BeanBinder<>(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.bindInstanceFields(form); + } + + @Test(expected = IllegalStateException.class) + public void bindInstanceFields_rawFieldType() { + BindRaw form = new BindRaw(); + BeanBinder<Person> binder = new BeanBinder<>(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.bindInstanceFields(form); + } + + @Test(expected = IllegalStateException.class) + public void bindInstanceFields_noInstantiatableFieldType() { + BindNonInstantiatableType form = new BindNonInstantiatableType(); + BeanBinder<Person> binder = new BeanBinder<>(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.bindInstanceFields(form); + } + + @Test + public void bindInstanceFields_complexGenericHierarchy() { + BindComplextHierarchyGenericType form = new BindComplextHierarchyGenericType(); + BeanBinder<Person> binder = new BeanBinder<>(Person.class); + binder.bindInstanceFields(form); + + Person person = new Person(); + person.setFirstName("foo"); + + binder.bind(person); + + Assert.assertEquals(person.getFirstName(), form.firstName.getValue()); + + form.firstName.setValue("bar"); + + Assert.assertEquals(form.firstName.getValue(), person.getFirstName()); + } + + @Test + public void bindInstanceFields_bindNotHasValueField_fieldIsNotReplaced() { + BindNoHasValueField form = new BindNoHasValueField(); + BeanBinder<Person> binder = new BeanBinder<>(Person.class); + + String name = "foo"; + form.firstName = name; + + binder.bindInstanceFields(form); + + Person person = new Person(); + person.setFirstName("foo"); + + binder.bind(person); + + Assert.assertEquals(name, form.firstName); + } + + @Test + public void bindInstanceFields_bindAllFieldsUsingAnnotations() { + BindFieldsUsingAnnotation form = new BindFieldsUsingAnnotation(); + BeanBinder<Person> binder = new BeanBinder<>(Person.class); + binder.bindInstanceFields(form); + + Person person = new Person(); + person.setFirstName("foo"); + person.setBirthDate(LocalDate.now()); + + binder.bind(person); + + Assert.assertEquals(person.getFirstName(), form.nameField.getValue()); + Assert.assertEquals(person.getBirthDate(), + form.birthDateField.getValue()); + + form.nameField.setValue("bar"); + form.birthDateField.setValue(person.getBirthDate().plusDays(345)); + + Assert.assertEquals(form.nameField.getValue(), person.getFirstName()); + Assert.assertEquals(form.birthDateField.getValue(), + person.getBirthDate()); + } + + @Test + public void bindInstanceFields_bindNotBoundFieldsOnly_customBindingIsNotReplaced() { + BindAllFields form = new BindAllFields(); + BeanBinder<Person> binder = new BeanBinder<>(Person.class); + + TextField name = new TextField(); + form.firstName = name; + binder.forField(form.firstName) + .withValidator( + new StringLengthValidator("Name is invalid", 3, 10)) + .bind("firstName"); + + binder.bindInstanceFields(form); + + Person person = new Person(); + String personName = "foo"; + person.setFirstName(personName); + person.setBirthDate(LocalDate.now()); + + binder.bind(person); + + Assert.assertEquals(person.getFirstName(), form.firstName.getValue()); + Assert.assertEquals(person.getBirthDate(), form.birthDate.getValue()); + // the instance is not overridden + Assert.assertEquals(name, form.firstName); + + form.firstName.setValue("aa"); + form.birthDate.setValue(person.getBirthDate().plusDays(345)); + + Assert.assertEquals(personName, person.getFirstName()); + Assert.assertEquals(form.birthDate.getValue(), person.getBirthDate()); + + Assert.assertFalse(binder.validate().isOk()); + } + + @Test + public void bindInstanceFields_fieldsAreConfigured_customBindingIsNotReplaced() { + BindOnlyOneField form = new BindOnlyOneField(); + BeanBinder<Person> binder = new BeanBinder<>(Person.class); + + TextField name = new TextField(); + form.firstName = name; + binder.forField(form.firstName) + .withValidator( + new StringLengthValidator("Name is invalid", 3, 10)) + .bind("firstName"); + TextField ageField = new TextField(); + form.noFieldInPerson = ageField; + binder.forField(form.noFieldInPerson) + .withConverter(new StringToIntegerConverter("")) + .bind(Person::getAge, Person::setAge); + + binder.bindInstanceFields(form); + + Person person = new Person(); + String personName = "foo"; + int age = 11; + person.setFirstName(personName); + person.setAge(age); + + binder.bind(person); + + Assert.assertEquals(person.getFirstName(), form.firstName.getValue()); + Assert.assertEquals(String.valueOf(person.getAge()), + form.noFieldInPerson.getValue()); + // the instances are not overridden + Assert.assertEquals(name, form.firstName); + Assert.assertEquals(ageField, form.noFieldInPerson); + + form.firstName.setValue("aa"); + age = age + 56; + form.noFieldInPerson.setValue(String.valueOf(age)); + + Assert.assertEquals(personName, person.getFirstName()); + Assert.assertEquals(form.noFieldInPerson.getValue(), + String.valueOf(person.getAge())); + + Assert.assertFalse(binder.validate().isOk()); + } +} diff --git a/server/src/test/java/com/vaadin/tests/data/bean/Person.java b/server/src/test/java/com/vaadin/tests/data/bean/Person.java index 00d28a1cd8..861006318f 100644 --- a/server/src/test/java/com/vaadin/tests/data/bean/Person.java +++ b/server/src/test/java/com/vaadin/tests/data/bean/Person.java @@ -1,7 +1,7 @@ package com.vaadin.tests.data.bean; import java.math.BigDecimal; -import java.util.Date; +import java.time.LocalDate; public class Person { private String firstName; @@ -11,7 +11,7 @@ public class Person { private Sex sex; private Address address; private boolean deceased; - private Date birthDate; + private LocalDate birthDate; private Integer salary; // null if unknown private Double salaryDouble; // null if unknown @@ -122,11 +122,11 @@ public class Person { this.salaryDouble = salaryDouble; } - public Date getBirthDate() { + public LocalDate getBirthDate() { return birthDate; } - public void setBirthDate(Date birthDate) { + public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; } diff --git a/uitest/src/main/java/com/vaadin/tests/declarative/PotusForm.java b/uitest/src/main/java/com/vaadin/tests/declarative/PotusForm.java index 28525efc0f..c14b409a73 100644 --- a/uitest/src/main/java/com/vaadin/tests/declarative/PotusForm.java +++ b/uitest/src/main/java/com/vaadin/tests/declarative/PotusForm.java @@ -16,11 +16,11 @@ package com.vaadin.tests.declarative; import com.vaadin.annotations.DesignRoot; +import com.vaadin.annotations.PropertyId; import com.vaadin.ui.Button; import com.vaadin.ui.DateField; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.declarative.Design; -import com.vaadin.v7.data.fieldgroup.PropertyId; import com.vaadin.v7.ui.ComboBox; import com.vaadin.v7.ui.TextField; diff --git a/uitest/src/main/java/com/vaadin/tests/fieldgroup/AbstractBasicCrud.java b/uitest/src/main/java/com/vaadin/tests/fieldgroup/AbstractBasicCrud.java index f57d033112..7709ae4795 100644 --- a/uitest/src/main/java/com/vaadin/tests/fieldgroup/AbstractBasicCrud.java +++ b/uitest/src/main/java/com/vaadin/tests/fieldgroup/AbstractBasicCrud.java @@ -18,6 +18,7 @@ package com.vaadin.tests.fieldgroup; import java.util.Iterator; import java.util.Map; +import com.vaadin.annotations.PropertyId; import com.vaadin.server.VaadinRequest; import com.vaadin.shared.ui.label.ContentMode; import com.vaadin.shared.util.SharedUtil; @@ -39,7 +40,6 @@ import com.vaadin.v7.data.Property.ValueChangeListener; import com.vaadin.v7.data.Validator.InvalidValueException; import com.vaadin.v7.data.fieldgroup.BeanFieldGroup; import com.vaadin.v7.data.fieldgroup.FieldGroup.CommitException; -import com.vaadin.v7.data.fieldgroup.PropertyId; import com.vaadin.v7.data.util.BeanItem; import com.vaadin.v7.data.util.BeanItemContainer; import com.vaadin.v7.data.validator.IntegerRangeValidator; diff --git a/uitest/src/main/java/com/vaadin/tests/fieldgroup/DateForm.java b/uitest/src/main/java/com/vaadin/tests/fieldgroup/DateForm.java index af8f5b4692..7bf59a3889 100644 --- a/uitest/src/main/java/com/vaadin/tests/fieldgroup/DateForm.java +++ b/uitest/src/main/java/com/vaadin/tests/fieldgroup/DateForm.java @@ -3,6 +3,7 @@ package com.vaadin.tests.fieldgroup; import java.util.Date; import java.util.Locale; +import com.vaadin.annotations.PropertyId; import com.vaadin.server.VaadinRequest; import com.vaadin.tests.components.AbstractTestUIWithLog; import com.vaadin.tests.data.bean.Person; @@ -12,7 +13,6 @@ import com.vaadin.ui.Notification; import com.vaadin.v7.data.fieldgroup.BeanFieldGroup; import com.vaadin.v7.data.fieldgroup.FieldGroup; import com.vaadin.v7.data.fieldgroup.FieldGroup.CommitException; -import com.vaadin.v7.data.fieldgroup.PropertyId; import com.vaadin.v7.data.util.BeanItem; import com.vaadin.v7.ui.DateField; import com.vaadin.v7.ui.InlineDateField; diff --git a/uitest/src/main/java/com/vaadin/tests/fieldgroup/FormBuilderWithNestedProperties.java b/uitest/src/main/java/com/vaadin/tests/fieldgroup/FormBuilderWithNestedProperties.java index dd8b78510c..488c180d8b 100644 --- a/uitest/src/main/java/com/vaadin/tests/fieldgroup/FormBuilderWithNestedProperties.java +++ b/uitest/src/main/java/com/vaadin/tests/fieldgroup/FormBuilderWithNestedProperties.java @@ -1,5 +1,6 @@ package com.vaadin.tests.fieldgroup; +import com.vaadin.annotations.PropertyId; import com.vaadin.tests.components.TestBase; import com.vaadin.tests.data.bean.Address; import com.vaadin.tests.data.bean.Country; @@ -7,7 +8,6 @@ import com.vaadin.tests.data.bean.Person; import com.vaadin.tests.data.bean.Sex; import com.vaadin.v7.data.fieldgroup.BeanFieldGroup; import com.vaadin.v7.data.fieldgroup.FieldGroup; -import com.vaadin.v7.data.fieldgroup.PropertyId; import com.vaadin.v7.data.util.BeanItem; import com.vaadin.v7.ui.TextField; diff --git a/uitest/src/main/java/com/vaadin/tests/fieldgroup/FormWithNestedProperties.java b/uitest/src/main/java/com/vaadin/tests/fieldgroup/FormWithNestedProperties.java index fb57932caa..8605c76f46 100644 --- a/uitest/src/main/java/com/vaadin/tests/fieldgroup/FormWithNestedProperties.java +++ b/uitest/src/main/java/com/vaadin/tests/fieldgroup/FormWithNestedProperties.java @@ -1,5 +1,6 @@ package com.vaadin.tests.fieldgroup; +import com.vaadin.annotations.PropertyId; import com.vaadin.tests.data.bean.Address; import com.vaadin.tests.data.bean.Country; import com.vaadin.tests.data.bean.Person; @@ -7,7 +8,6 @@ import com.vaadin.tests.data.bean.Sex; import com.vaadin.tests.util.Log; import com.vaadin.ui.CheckBox; import com.vaadin.v7.data.fieldgroup.BeanFieldGroup; -import com.vaadin.v7.data.fieldgroup.PropertyId; import com.vaadin.v7.ui.NativeSelect; import com.vaadin.v7.ui.TextField; diff --git a/uitest/src/main/java/com/vaadin/tests/minitutorials/v7a1/FormUsingExistingLayout.java b/uitest/src/main/java/com/vaadin/tests/minitutorials/v7a1/FormUsingExistingLayout.java index 6b8cd4b758..e6ae8e9168 100644 --- a/uitest/src/main/java/com/vaadin/tests/minitutorials/v7a1/FormUsingExistingLayout.java +++ b/uitest/src/main/java/com/vaadin/tests/minitutorials/v7a1/FormUsingExistingLayout.java @@ -1,10 +1,10 @@ package com.vaadin.tests.minitutorials.v7a1; +import com.vaadin.annotations.PropertyId; import com.vaadin.server.VaadinRequest; import com.vaadin.tests.components.AbstractReindeerTestUI; import com.vaadin.ui.GridLayout; import com.vaadin.v7.data.fieldgroup.FieldGroup; -import com.vaadin.v7.data.fieldgroup.PropertyId; import com.vaadin.v7.data.util.BeanItem; import com.vaadin.v7.ui.TextArea; import com.vaadin.v7.ui.TextField; |