]> source.dussan.org Git - vaadin-framework.git/commitdiff
Binder.bindInstanceFields(Object) method implementation (#47).
authorDenis Anisimov <denis@vaadin.com>
Fri, 26 Aug 2016 12:04:05 +0000 (15:04 +0300)
committerDenis Anisimov <denis@vaadin.com>
Tue, 25 Oct 2016 06:12:41 +0000 (09:12 +0300)
Binds class instance fields using reflection using Binder.forField()

Change-Id: I597f3832d112cfa69c73fb185f1564c482e4eb15

17 files changed:
compatibility-server/src/main/java/com/vaadin/v7/data/fieldgroup/FieldGroup.java
compatibility-server/src/main/java/com/vaadin/v7/data/fieldgroup/PropertyId.java [deleted file]
compatibility-server/src/test/java/com/vaadin/v7/data/util/ReflectToolsGetSuperFieldTest.java
compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/fieldgroup/BeanFieldGroupTest.java
compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/fieldgroup/FieldNamedDescriptionTest.java
server/pom.xml
server/src/main/java/com/vaadin/annotations/PropertyId.java [new file with mode: 0644]
server/src/main/java/com/vaadin/data/BeanBinder.java
server/src/main/java/com/vaadin/data/Binder.java
server/src/test/java/com/vaadin/data/BeanBinderInstanceFieldTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/tests/data/bean/Person.java
uitest/src/main/java/com/vaadin/tests/declarative/PotusForm.java
uitest/src/main/java/com/vaadin/tests/fieldgroup/AbstractBasicCrud.java
uitest/src/main/java/com/vaadin/tests/fieldgroup/DateForm.java
uitest/src/main/java/com/vaadin/tests/fieldgroup/FormBuilderWithNestedProperties.java
uitest/src/main/java/com/vaadin/tests/fieldgroup/FormWithNestedProperties.java
uitest/src/main/java/com/vaadin/tests/minitutorials/v7a1/FormUsingExistingLayout.java

index 50ad9a843a52c5229acf7cb03080c8db41171c69..1055d1921dbc99189e722d87fd5d2d866e8759bd 100644 (file)
@@ -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/main/java/com/vaadin/v7/data/fieldgroup/PropertyId.java b/compatibility-server/src/main/java/com/vaadin/v7/data/fieldgroup/PropertyId.java
deleted file mode 100644 (file)
index f9b2207..0000000
+++ /dev/null
@@ -1,63 +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.v7.data.fieldgroup;
-
-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;
-
-/**
- * Defines the custom property name to be bound to a {@link Field} using
- * {@link FieldGroup} or {@link BeanFieldGroup}.
- * <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.
- * <p>
- * In following usage example, the text field would be bound to property "foo"
- * in the Entity class. <code>
- * <pre>
- *    class Editor extends FormLayout {
-        &#64;PropertyId("foo")
-        TextField myField = new TextField();
-    }
-
-    class Entity {
-        String foo;
-    }
-
-    {
-        Editor e = new Editor();
-        BeanFieldGroup.bindFieldsUnbuffered(new Entity(), e);
-    }
-   </pre>
- * </code>
- *
- * @since 7.0
- * @author Vaadin Ltd
- */
-@Target({ ElementType.FIELD })
-@Retention(RetentionPolicy.RUNTIME)
-@Deprecated
-public @interface PropertyId {
-    String value();
-}
index 2876181b90cb7ee2a1a40811cd464c7006dafd04..4341bb000ff0b5e3e826befbe34b51b34a35e12d 100644 (file)
@@ -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 {
index cae04a28f556bee42b6b0dd5f607932e38b2960c..e8ed7a979c60209b24549086b54e44d6469270e3 100644 (file)
@@ -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;
index bd9f2067512816937cffb29accd7cae5a17af90d..194dd1d350089fa6666abd54c2ee58744fe7e0b9 100644 (file)
@@ -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;
index 616558cdead2995712aa269ec1821c0d56034785..4d65f1046c95807a96d813faf64f8aa0e9697591 100644 (file)
             <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/server/src/main/java/com/vaadin/annotations/PropertyId.java b/server/src/main/java/com/vaadin/annotations/PropertyId.java
new file mode 100644 (file)
index 0000000..b914699
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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.annotations;
+
+import java.lang.annotation.ElementType;
+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}.
+ * <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.
+ * <p>
+ * In following usage example, the text field would be bound to property "foo"
+ * in the Entity class. <code>
+ * <pre>
+ *    class Editor extends FormLayout {
+        &#64;PropertyId("foo")
+        TextField myField = new TextField();
+    }
+
+    class Entity {
+        String foo;
+    }
+
+    {
+        Editor editor = new Editor();
+        BeanBinder<Entity> binder = new BeanBinder(Entity.class);
+        binder.bindInstanceFields(editor);
+    }
+   </pre>
+ * </code>
+ *
+ * @since 7.0
+ * @author Vaadin Ltd
+ */
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PropertyId {
+    String value();
+}
index a57082357fab016d602c35c79924799e166cac75..cc45e93c2d7605aa5b0c47694d521f622625e11e 100644 (file)
@@ -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("_", "");
+    }
+
 }
index d3c2befefb5d87b3cc1498a09b2a434b214ba3d9..7cd4c4a8731b1de5f7adf16a6c15503810a15afe 100644 (file)
@@ -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 (file)
index 0000000..37b328f
--- /dev/null
@@ -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());
+    }
+}
index 00d28a1cd8f3dae094c8e499294dde2d8baf47f2..861006318fd00a9f63228d34ea456fdb701b379b 100644 (file)
@@ -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;
     }
 
index 28525efc0f6eba8df0a523d7d4ddaa4add4525f1..c14b409a73ef4652215223d35e6facb759dd621e 100644 (file)
 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;
 
index f57d033112f5df43821808e56ce0a6332856e69f..7709ae47951e8719a1f4bdd1ea588164489b6062 100644 (file)
@@ -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;
index af8f5b46921640cc0a5bdc28353b1ceba86227fc..7bf59a388902c93739d77e50d21592641859ae97 100644 (file)
@@ -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;
index dd8b78510c840f5bcc783cf2687a9549af3fcacd..488c180d8bd79dfbdbbc8fb72288130882077199 100644 (file)
@@ -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;
 
index fb57932caad683d1a2579d48655b2c9bf10e99d1..8605c76f46e979e4baa6926f479b9e77c56e3913 100644 (file)
@@ -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;
 
index 6b8cd4b758568bed1c6afe74ad852e6fa0d6ba18..e6ae8e916845f7314cb8082c27af3f2341a6bb39 100644 (file)
@@ -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;