summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDenis Anisimov <denis@vaadin.com>2016-08-26 15:04:05 +0300
committerDenis Anisimov <denis@vaadin.com>2016-10-25 09:12:41 +0300
commit0decd87411d98cf0d03db35fb6e5d70637ff864c (patch)
tree80af2fd0a2cd35c8ed613987f70c66c049088d4b
parenteb39bd3df37fe0704c7bf2de95a85a7bf1e627a8 (diff)
downloadvaadin-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
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/fieldgroup/FieldGroup.java1
-rw-r--r--compatibility-server/src/test/java/com/vaadin/v7/data/util/ReflectToolsGetSuperFieldTest.java2
-rw-r--r--compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/fieldgroup/BeanFieldGroupTest.java2
-rw-r--r--compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/fieldgroup/FieldNamedDescriptionTest.java2
-rw-r--r--server/pom.xml7
-rw-r--r--server/src/main/java/com/vaadin/annotations/PropertyId.java (renamed from compatibility-server/src/main/java/com/vaadin/v7/data/fieldgroup/PropertyId.java)26
-rw-r--r--server/src/main/java/com/vaadin/data/BeanBinder.java233
-rw-r--r--server/src/main/java/com/vaadin/data/Binder.java2
-rw-r--r--server/src/test/java/com/vaadin/data/BeanBinderInstanceFieldTest.java387
-rw-r--r--server/src/test/java/com/vaadin/tests/data/bean/Person.java8
-rw-r--r--uitest/src/main/java/com/vaadin/tests/declarative/PotusForm.java2
-rw-r--r--uitest/src/main/java/com/vaadin/tests/fieldgroup/AbstractBasicCrud.java2
-rw-r--r--uitest/src/main/java/com/vaadin/tests/fieldgroup/DateForm.java2
-rw-r--r--uitest/src/main/java/com/vaadin/tests/fieldgroup/FormBuilderWithNestedProperties.java2
-rw-r--r--uitest/src/main/java/com/vaadin/tests/fieldgroup/FormWithNestedProperties.java2
-rw-r--r--uitest/src/main/java/com/vaadin/tests/minitutorials/v7a1/FormUsingExistingLayout.java2
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");
+ * &#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,
+ (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;