]> source.dussan.org Git - vaadin-framework.git/commitdiff
Renamed FieldBinder -> FieldGroup, BeanFieldBinder -> BeanFieldGroup
authorArtur Signell <artur@vaadin.com>
Thu, 22 Dec 2011 08:20:57 +0000 (10:20 +0200)
committerArtur Signell <artur@vaadin.com>
Thu, 22 Dec 2011 08:22:17 +0000 (10:22 +0200)
based on API review meeting

src/com/vaadin/data/fieldbinder/BeanFieldBinder.java [deleted file]
src/com/vaadin/data/fieldbinder/BeanFieldGroup.java [new file with mode: 0644]
src/com/vaadin/data/fieldbinder/FieldBinder.java [deleted file]
src/com/vaadin/data/fieldbinder/FieldGroup.java [new file with mode: 0644]
src/com/vaadin/data/fieldbinder/FormBuilder.java
tests/testbench/com/vaadin/tests/fieldbinder/AbstractBeanFieldBinderTest.java
tests/testbench/com/vaadin/tests/fieldbinder/BasicPersonForm.java
tests/testbench/com/vaadin/tests/fieldbinder/FieldBinderWithBeanValidation.java
tests/testbench/com/vaadin/tests/fieldbinder/FormBuilderWithNestedProperties.java
tests/testbench/com/vaadin/tests/fieldbinder/FormWithNestedProperties.java

diff --git a/src/com/vaadin/data/fieldbinder/BeanFieldBinder.java b/src/com/vaadin/data/fieldbinder/BeanFieldBinder.java
deleted file mode 100644 (file)
index 2b15058..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-/* \r
-@VaadinApache2LicenseForJavaFiles@\r
- */\r
-package com.vaadin.data.fieldbinder;\r
-\r
-import com.vaadin.data.Item;\r
-import com.vaadin.data.util.BeanItem;\r
-import com.vaadin.data.validator.BeanValidationValidator;\r
-import com.vaadin.ui.Field;\r
-\r
-public class BeanFieldBinder<T> extends FieldBinder {\r
-\r
-    private Class<T> beanType;\r
-\r
-    public BeanFieldBinder(Class<T> beanType) {\r
-        this.beanType = beanType;\r
-    }\r
-\r
-    @Override\r
-    protected Class<?> getPropertyType(Object propertyId) {\r
-        if (getItemDataSource() != null) {\r
-            return super.getPropertyType(propertyId);\r
-        } else {\r
-            // Data source not set so we need to figure out the type manually\r
-            /*\r
-             * toString should never really be needed as propertyId should be of\r
-             * form "fieldName" or "fieldName.subField[.subField2]" but the\r
-             * method declaration comes from parent.\r
-             */\r
-            java.lang.reflect.Field f;\r
-            try {\r
-                f = getField(beanType, propertyId.toString());\r
-                return f.getType();\r
-            } catch (SecurityException e) {\r
-                throw new BindException("Cannot determine type of propertyId '"\r
-                        + propertyId + "'.", e);\r
-            } catch (NoSuchFieldException e) {\r
-                throw new BindException("Cannot determine type of propertyId '"\r
-                        + propertyId + "'. The propertyId was not found in "\r
-                        + beanType.getName(), e);\r
-            }\r
-        }\r
-    }\r
-\r
-    private static java.lang.reflect.Field getField(Class<?> cls,\r
-            String propertyId) throws SecurityException, NoSuchFieldException {\r
-        if (propertyId.contains(".")) {\r
-            String[] parts = propertyId.split("\\.", 2);\r
-            // Get the type of the field in the "cls" class\r
-            java.lang.reflect.Field field1 = getField(cls, parts[0]);\r
-            // Find the rest from the sub type\r
-            return getField(field1.getType(), parts[1]);\r
-        } else {\r
-            try {\r
-                // Try to find the field directly in the given class\r
-                java.lang.reflect.Field field1 = cls\r
-                        .getDeclaredField(propertyId);\r
-                return field1;\r
-            } catch (NoSuchFieldError e) {\r
-                // Try super classes until we reach Object\r
-                Class<?> superClass = cls.getSuperclass();\r
-                if (superClass != Object.class) {\r
-                    return getField(superClass, propertyId);\r
-                } else {\r
-                    throw e;\r
-                }\r
-            }\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Helper method for setting the data source directly using a bean. This\r
-     * method wraps the bean in a {@link BeanItem} and calls\r
-     * {@link #setItemDataSource(Item)}.\r
-     * \r
-     * @param bean\r
-     *            The bean to use as data source.\r
-     */\r
-    public void setItemDataSource(T bean) {\r
-        setItemDataSource(new BeanItem(bean));\r
-    }\r
-\r
-    @Override\r
-    public void setItemDataSource(Item item) {\r
-        if (!(item instanceof BeanItem)) {\r
-            throw new RuntimeException(getClass().getSimpleName()\r
-                    + " only supports BeanItems as item data source");\r
-        }\r
-        super.setItemDataSource(item);\r
-    }\r
-\r
-    @Override\r
-    public BeanItem<T> getItemDataSource() {\r
-        return (BeanItem<T>) super.getItemDataSource();\r
-    }\r
-\r
-    @Override\r
-    public void bind(Field field, Object propertyId) {\r
-        if (getItemDataSource() != null) {\r
-            // The data source is set so the property must be found in the item.\r
-            // If it is not we try to add it.\r
-            try {\r
-                getItemProperty(propertyId);\r
-            } catch (BindException e) {\r
-                // Not found, try to add a nested property;\r
-                // BeanItem property ids are always strings so this is safe\r
-                getItemDataSource().addNestedProperty((String) propertyId);\r
-            }\r
-        }\r
-\r
-        super.bind(field, propertyId);\r
-    }\r
-\r
-    @Override\r
-    protected void configureField(Field<?> field) {\r
-        super.configureField(field);\r
-        // Add Bean validators if there are annotations\r
-        if (BeanValidationValidator.isImplementationAvailable()) {\r
-            BeanValidationValidator validator = new BeanValidationValidator(\r
-                    beanType, getPropertyIdForField(field).toString());\r
-            field.addValidator(validator);\r
-            if (field.getLocale() != null) {\r
-                validator.setLocale(field.getLocale());\r
-            }\r
-        }\r
-    }\r
-\r
-}
\ No newline at end of file
diff --git a/src/com/vaadin/data/fieldbinder/BeanFieldGroup.java b/src/com/vaadin/data/fieldbinder/BeanFieldGroup.java
new file mode 100644 (file)
index 0000000..a01c6e0
--- /dev/null
@@ -0,0 +1,128 @@
+/* \r
+@VaadinApache2LicenseForJavaFiles@\r
+ */\r
+package com.vaadin.data.fieldbinder;\r
+\r
+import com.vaadin.data.Item;\r
+import com.vaadin.data.util.BeanItem;\r
+import com.vaadin.data.validator.BeanValidationValidator;\r
+import com.vaadin.ui.Field;\r
+\r
+public class BeanFieldGroup<T> extends FieldGroup {\r
+\r
+    private Class<T> beanType;\r
+\r
+    public BeanFieldGroup(Class<T> beanType) {\r
+        this.beanType = beanType;\r
+    }\r
+\r
+    @Override\r
+    protected Class<?> getPropertyType(Object propertyId) {\r
+        if (getItemDataSource() != null) {\r
+            return super.getPropertyType(propertyId);\r
+        } else {\r
+            // Data source not set so we need to figure out the type manually\r
+            /*\r
+             * toString should never really be needed as propertyId should be of\r
+             * form "fieldName" or "fieldName.subField[.subField2]" but the\r
+             * method declaration comes from parent.\r
+             */\r
+            java.lang.reflect.Field f;\r
+            try {\r
+                f = getField(beanType, propertyId.toString());\r
+                return f.getType();\r
+            } catch (SecurityException e) {\r
+                throw new BindException("Cannot determine type of propertyId '"\r
+                        + propertyId + "'.", e);\r
+            } catch (NoSuchFieldException e) {\r
+                throw new BindException("Cannot determine type of propertyId '"\r
+                        + propertyId + "'. The propertyId was not found in "\r
+                        + beanType.getName(), e);\r
+            }\r
+        }\r
+    }\r
+\r
+    private static java.lang.reflect.Field getField(Class<?> cls,\r
+            String propertyId) throws SecurityException, NoSuchFieldException {\r
+        if (propertyId.contains(".")) {\r
+            String[] parts = propertyId.split("\\.", 2);\r
+            // Get the type of the field in the "cls" class\r
+            java.lang.reflect.Field field1 = getField(cls, parts[0]);\r
+            // Find the rest from the sub type\r
+            return getField(field1.getType(), parts[1]);\r
+        } else {\r
+            try {\r
+                // Try to find the field directly in the given class\r
+                java.lang.reflect.Field field1 = cls\r
+                        .getDeclaredField(propertyId);\r
+                return field1;\r
+            } catch (NoSuchFieldError e) {\r
+                // Try super classes until we reach Object\r
+                Class<?> superClass = cls.getSuperclass();\r
+                if (superClass != Object.class) {\r
+                    return getField(superClass, propertyId);\r
+                } else {\r
+                    throw e;\r
+                }\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Helper method for setting the data source directly using a bean. This\r
+     * method wraps the bean in a {@link BeanItem} and calls\r
+     * {@link #setItemDataSource(Item)}.\r
+     * \r
+     * @param bean\r
+     *            The bean to use as data source.\r
+     */\r
+    public void setItemDataSource(T bean) {\r
+        setItemDataSource(new BeanItem(bean));\r
+    }\r
+\r
+    @Override\r
+    public void setItemDataSource(Item item) {\r
+        if (!(item instanceof BeanItem)) {\r
+            throw new RuntimeException(getClass().getSimpleName()\r
+                    + " only supports BeanItems as item data source");\r
+        }\r
+        super.setItemDataSource(item);\r
+    }\r
+\r
+    @Override\r
+    public BeanItem<T> getItemDataSource() {\r
+        return (BeanItem<T>) super.getItemDataSource();\r
+    }\r
+\r
+    @Override\r
+    public void bind(Field field, Object propertyId) {\r
+        if (getItemDataSource() != null) {\r
+            // The data source is set so the property must be found in the item.\r
+            // If it is not we try to add it.\r
+            try {\r
+                getItemProperty(propertyId);\r
+            } catch (BindException e) {\r
+                // Not found, try to add a nested property;\r
+                // BeanItem property ids are always strings so this is safe\r
+                getItemDataSource().addNestedProperty((String) propertyId);\r
+            }\r
+        }\r
+\r
+        super.bind(field, propertyId);\r
+    }\r
+\r
+    @Override\r
+    protected void configureField(Field<?> field) {\r
+        super.configureField(field);\r
+        // Add Bean validators if there are annotations\r
+        if (BeanValidationValidator.isImplementationAvailable()) {\r
+            BeanValidationValidator validator = new BeanValidationValidator(\r
+                    beanType, getPropertyIdForField(field).toString());\r
+            field.addValidator(validator);\r
+            if (field.getLocale() != null) {\r
+                validator.setLocale(field.getLocale());\r
+            }\r
+        }\r
+    }\r
+\r
+}
\ No newline at end of file
diff --git a/src/com/vaadin/data/fieldbinder/FieldBinder.java b/src/com/vaadin/data/fieldbinder/FieldBinder.java
deleted file mode 100644 (file)
index 3df8794..0000000
+++ /dev/null
@@ -1,722 +0,0 @@
-/* \r
-@VaadinApache2LicenseForJavaFiles@\r
- */\r
-package com.vaadin.data.fieldbinder;\r
-\r
-import java.io.Serializable;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.HashMap;\r
-import java.util.LinkedHashMap;\r
-import java.util.List;\r
-import java.util.logging.Logger;\r
-\r
-import com.vaadin.data.Item;\r
-import com.vaadin.data.Property;\r
-import com.vaadin.data.TransactionalProperty;\r
-import com.vaadin.data.Validator.InvalidValueException;\r
-import com.vaadin.data.fieldbinder.FormBuilder.FormBuilderException;\r
-import com.vaadin.data.util.TransactionalPropertyWrapper;\r
-import com.vaadin.tools.ReflectTools;\r
-import com.vaadin.ui.Field;\r
-\r
-/**\r
- * FIXME Javadoc\r
- * \r
- * See also {@link BeanFieldBinder} which makes binding fields easier when your\r
- * data is in a bean.\r
- * \r
- * @author Vaadin Ltd\r
- * @version @version@\r
- * @since 7.0\r
- */\r
-public class FieldBinder implements Serializable {\r
-\r
-    private static final Logger logger = Logger.getLogger(FieldBinder.class\r
-            .getName());\r
-\r
-    private Item itemDataSource;\r
-    private boolean fieldsBuffered = true;\r
-\r
-    private boolean fieldsEnabled = true;\r
-    private boolean fieldsReadOnly = false;\r
-\r
-    private HashMap<Object, Field<?>> propertyIdToField = new HashMap<Object, Field<?>>();\r
-    private LinkedHashMap<Field<?>, Object> fieldToPropertyId = new LinkedHashMap<Field<?>, Object>();\r
-    private List<CommitHandler> commitHandlers = new ArrayList<CommitHandler>();\r
-\r
-    /**\r
-     * Constructs a field binder. Use {@link #setItemDataSource(Item)} to set a\r
-     * data source for the field binder.\r
-     * \r
-     */\r
-    public FieldBinder() {\r
-\r
-    }\r
-\r
-    /**\r
-     * Constructs a field binder that uses the given data source.\r
-     * \r
-     * @param itemDataSource\r
-     *            The data source to bind the fields to\r
-     */\r
-    public FieldBinder(Item itemDataSource) {\r
-        setItemDataSource(itemDataSource);\r
-    }\r
-\r
-    /**\r
-     * Updates the item that is used by this FieldBinder. Rebinds all fields to\r
-     * the properties in the new item.\r
-     * \r
-     * @param itemDataSource\r
-     *            The new item to use\r
-     */\r
-    public void setItemDataSource(Item itemDataSource) {\r
-        this.itemDataSource = itemDataSource;\r
-\r
-        for (Field<?> f : fieldToPropertyId.keySet()) {\r
-            bind(f, fieldToPropertyId.get(f));\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Gets the item used by this FieldBinder. Note that you must call\r
-     * {@link #commit()} for the item to be updated unless buffered mode has\r
-     * been switched off.\r
-     * \r
-     * @see #setFieldsBuffered(boolean)\r
-     * @see #commit()\r
-     * \r
-     * @return The item used by this FieldBinder\r
-     */\r
-    public Item getItemDataSource() {\r
-        return itemDataSource;\r
-    }\r
-\r
-    /**\r
-     * Checks the buffered mode for the bound fields.\r
-     * <p>\r
-     * \r
-     * @see #setFieldsBuffered(boolean) for more details on buffered mode\r
-     * \r
-     * @see Field#isFieldsBuffered()\r
-     * @return true if buffered mode is on, false otherwise\r
-     * \r
-     */\r
-    public boolean isFieldsBuffered() {\r
-        return fieldsBuffered;\r
-    }\r
-\r
-    /**\r
-     * Sets the buffered mode for the bound fields.\r
-     * <p>\r
-     * When buffered mode is on the item will not be updated until\r
-     * {@link #commit()} is called. If buffered mode is off the item will be\r
-     * updated once the fields are updated.\r
-     * </p>\r
-     * <p>\r
-     * The default is to use buffered mode.\r
-     * </p>\r
-     * \r
-     * @see Field#setFieldsBuffered(boolean)\r
-     * @param fieldsBuffered\r
-     *            true to turn on buffered mode, false otherwise\r
-     */\r
-    public void setFieldsBuffered(boolean fieldsBuffered) {\r
-        if (fieldsBuffered == this.fieldsBuffered) {\r
-            return;\r
-        }\r
-\r
-        this.fieldsBuffered = fieldsBuffered;\r
-        for (Field<?> field : getFields()) {\r
-            field.setBuffered(fieldsBuffered);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Returns the enabled status for the fields.\r
-     * <p>\r
-     * Note that this will not accurately represent the enabled status of all\r
-     * fields if you change the enabled status of the fields through some other\r
-     * method than {@link #setFieldsEnabled(boolean)}.\r
-     * \r
-     * @return true if the fields are enabled, false otherwise\r
-     */\r
-    public boolean isFieldsEnabled() {\r
-        return fieldsEnabled;\r
-    }\r
-\r
-    /**\r
-     * Updates the enabled state of all bound fields.\r
-     * \r
-     * @param fieldsEnabled\r
-     *            true to enable all bound fields, false to disable them\r
-     */\r
-    public void setFieldsEnabled(boolean fieldsEnabled) {\r
-        this.fieldsEnabled = fieldsEnabled;\r
-        for (Field<?> field : getFields()) {\r
-            field.setEnabled(fieldsEnabled);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Returns the read only status for the fields.\r
-     * <p>\r
-     * Note that this will not accurately represent the read only status of all\r
-     * fields if you change the read only status of the fields through some\r
-     * other method than {@link #setFieldsReadOnly(boolean)}.\r
-     * \r
-     * @return true if the fields are set to read only, false otherwise\r
-     */\r
-    public boolean isFieldsReadOnly() {\r
-        return fieldsReadOnly;\r
-    }\r
-\r
-    /**\r
-     * Updates the read only state of all bound fields.\r
-     * \r
-     * @param fieldsReadOnly\r
-     *            true to set all bound fields to read only, false to set them\r
-     *            to read write\r
-     */\r
-    public void setFieldsReadOnly(boolean fieldsReadOnly) {\r
-        this.fieldsReadOnly = fieldsReadOnly;\r
-    }\r
-\r
-    /**\r
-     * Returns a collection of all fields that have been bound.\r
-     * <p>\r
-     * The fields are not returned in any specific order.\r
-     * </p>\r
-     * \r
-     * @return A collection with all bound Fields\r
-     */\r
-    public Collection<Field<?>> getFields() {\r
-        return fieldToPropertyId.keySet();\r
-    }\r
-\r
-    /**\r
-     * Binds the field with the given propertyId from the current item. If an\r
-     * item has not been set then the binding is postponed until the item is set\r
-     * using {@link #setItemDataSource(Item)}.\r
-     * <p>\r
-     * This method also adds validators when applicable.\r
-     * </p>\r
-     * \r
-     * @param field\r
-     *            The field to bind\r
-     * @param propertyId\r
-     *            The propertyId to bind to the field\r
-     * @throws BindException\r
-     *             If the property id is already bound to another field by this\r
-     *             field binder\r
-     */\r
-    public void bind(Field<?> field, Object propertyId) throws BindException {\r
-        if (propertyIdToField.containsKey(propertyId)\r
-                && propertyIdToField.get(propertyId) != field) {\r
-            throw new BindException("Property id " + propertyId\r
-                    + " is already bound to another field");\r
-        }\r
-        fieldToPropertyId.put(field, propertyId);\r
-        propertyIdToField.put(propertyId, field);\r
-        if (itemDataSource == null) {\r
-            // Will be bound when data source is set\r
-            return;\r
-        }\r
-\r
-        field.setPropertyDataSource(wrapInTransactionalProperty(getItemProperty(propertyId)));\r
-        configureField(field);\r
-    }\r
-\r
-    private <T> TransactionalProperty<T> wrapInTransactionalProperty(\r
-            Property<T> itemProperty) {\r
-        return new TransactionalPropertyWrapper<T>(itemProperty);\r
-    }\r
-\r
-    /**\r
-     * Gets the property with the given property id from the item.\r
-     * \r
-     * @param propertyId\r
-     *            The id if the property to find\r
-     * @return The property with the given id from the item\r
-     * @throws BindException\r
-     *             If the property was not found in the item or no item has been\r
-     *             set\r
-     */\r
-    protected Property<?> getItemProperty(Object propertyId)\r
-            throws BindException {\r
-        Item item = getItemDataSource();\r
-        if (item == null) {\r
-            throw new BindException("Could not lookup property with id "\r
-                    + propertyId + " as no item has been set");\r
-        }\r
-        Property<?> p = item.getItemProperty(propertyId);\r
-        if (p == null) {\r
-            throw new BindException("A property with id " + propertyId\r
-                    + " was not found in the item");\r
-        }\r
-        return p;\r
-    }\r
-\r
-    /**\r
-     * Detaches the field from its property id and removes it from this\r
-     * FieldBinder.\r
-     * <p>\r
-     * Note that the field is not detached from its property data source if it\r
-     * is no longer connected to the same property id it was bound to using this\r
-     * FieldBinder.\r
-     * \r
-     * @param field\r
-     *            The field to detach\r
-     * @throws BindException\r
-     *             If the field is not bound by this field binder or not bound\r
-     *             to the correct property id\r
-     */\r
-    public void remove(Field<?> field) throws BindException {\r
-        Object propertyId = fieldToPropertyId.get(field);\r
-        if (propertyId == null) {\r
-            throw new BindException(\r
-                    "The given field is not part of this FieldBinder");\r
-        }\r
-\r
-        Property fieldDataSource = field.getPropertyDataSource();\r
-        if (fieldDataSource instanceof TransactionalPropertyWrapper) {\r
-            fieldDataSource = ((TransactionalPropertyWrapper) fieldDataSource)\r
-                    .getWrappedProperty();\r
-        }\r
-        if (fieldDataSource == getItemProperty(propertyId)) {\r
-            field.setPropertyDataSource(null);\r
-        }\r
-        fieldToPropertyId.remove(field);\r
-        propertyIdToField.remove(propertyId);\r
-    }\r
-\r
-    /**\r
-     * Configures a field with the settings set for this FieldBinder.\r
-     * <p>\r
-     * By default this updates the buffered, read only and enabled state of the\r
-     * field. Also adds validators when applicable.\r
-     * \r
-     * @param field\r
-     *            The field to update\r
-     */\r
-    protected void configureField(Field<?> field) {\r
-        field.setBuffered(isFieldsBuffered());\r
-\r
-        field.setEnabled(isFieldsEnabled());\r
-        field.setReadOnly(isFieldsReadOnly());\r
-    }\r
-\r
-    /**\r
-     * Gets the type of the property with the given property id.\r
-     * \r
-     * @param propertyId\r
-     *            The propertyId. Must be find\r
-     * @return The type of the property\r
-     */\r
-    protected Class<?> getPropertyType(Object propertyId) throws BindException {\r
-        if (getItemDataSource() == null) {\r
-            throw new BindException(\r
-                    "Property type for '"\r
-                            + propertyId\r
-                            + "' could not be determined. No item data source has been set.");\r
-        }\r
-        Property<?> p = getItemDataSource().getItemProperty(propertyId);\r
-        if (p == null) {\r
-            throw new BindException(\r
-                    "Property type for '"\r
-                            + propertyId\r
-                            + "' could not be determined. No property with that id was found.");\r
-        }\r
-\r
-        return p.getType();\r
-    }\r
-\r
-    /**\r
-     * Returns a collection of all property ids that have been bound to fields.\r
-     * <p>\r
-     * Note that this will return property ids even before the item has been\r
-     * set. In that case it returns the property ids that will be bound once the\r
-     * item is set.\r
-     * </p>\r
-     * <p>\r
-     * No guarantee is given for the order of the property ids\r
-     * </p>\r
-     * \r
-     * @return A collection of bound property ids\r
-     */\r
-    public Collection<Object> getBoundPropertyIds() {\r
-        return Collections.unmodifiableCollection(propertyIdToField.keySet());\r
-    }\r
-\r
-    /**\r
-     * Returns a collection of all property ids that exist in the item set using\r
-     * {@link #setItemDataSource(Item)} but have not been bound to fields.\r
-     * <p>\r
-     * Will always return an empty collection before an item has been set using\r
-     * {@link #setItemDataSource(Item)}.\r
-     * </p>\r
-     * <p>\r
-     * No guarantee is given for the order of the property ids\r
-     * </p>\r
-     * \r
-     * @return A collection of property ids that have not been bound to fields\r
-     */\r
-    protected Collection<Object> getUnboundPropertyIds() {\r
-        if (getItemDataSource() == null) {\r
-            return new ArrayList<Object>();\r
-        }\r
-        List<Object> unboundPropertyIds = new ArrayList<Object>();\r
-        unboundPropertyIds.addAll(getItemDataSource().getItemPropertyIds());\r
-        unboundPropertyIds.removeAll(propertyIdToField.keySet());\r
-        return unboundPropertyIds;\r
-    }\r
-\r
-    /**\r
-     * Commits all changes done to the bound fields.\r
-     * <p>\r
-     * Calls all {@link CommitHandler}s before and after committing the field\r
-     * changes to the item data source. The whole commit is aborted and state is\r
-     * restored to what it was before commit was called if any\r
-     * {@link CommitHandler} throws a CommitException or there is a problem\r
-     * committing the fields\r
-     * \r
-     * @throws CommitException\r
-     *             If the commit was aborted\r
-     */\r
-    public void commit() throws CommitException {\r
-        if (!isFieldsBuffered()) {\r
-            // Not using buffered mode, nothing to do\r
-            return;\r
-        }\r
-        for (Field<?> f : fieldToPropertyId.keySet()) {\r
-            ((TransactionalProperty<?>) f.getPropertyDataSource())\r
-                    .startTransaction();\r
-        }\r
-        try {\r
-            firePreCommitEvent();\r
-            // Commit the field values to the properties\r
-            for (Field<?> f : fieldToPropertyId.keySet()) {\r
-                f.commit();\r
-            }\r
-            firePostCommitEvent();\r
-\r
-            // Commit the properties\r
-            for (Field<?> f : fieldToPropertyId.keySet()) {\r
-                ((TransactionalProperty<?>) f.getPropertyDataSource()).commit();\r
-            }\r
-\r
-        } catch (Exception e) {\r
-            for (Field<?> f : fieldToPropertyId.keySet()) {\r
-                try {\r
-                    ((TransactionalProperty<?>) f.getPropertyDataSource())\r
-                            .rollback();\r
-                } catch (Exception rollbackException) {\r
-                    // FIXME: What to do ?\r
-                }\r
-            }\r
-\r
-            throw new CommitException("Commit failed", e);\r
-        }\r
-\r
-    }\r
-\r
-    /**\r
-     * Sends a preCommit event to all registered commit handlers\r
-     * \r
-     * @throws CommitException\r
-     *             If the commit should be aborted\r
-     */\r
-    private void firePreCommitEvent() throws CommitException {\r
-        CommitHandler[] handlers = commitHandlers\r
-                .toArray(new CommitHandler[commitHandlers.size()]);\r
-\r
-        for (CommitHandler handler : handlers) {\r
-            handler.preCommit(new CommitEvent(this));\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Sends a postCommit event to all registered commit handlers\r
-     * \r
-     * @throws CommitException\r
-     *             If the commit should be aborted\r
-     */\r
-    private void firePostCommitEvent() throws CommitException {\r
-        CommitHandler[] handlers = commitHandlers\r
-                .toArray(new CommitHandler[commitHandlers.size()]);\r
-\r
-        for (CommitHandler handler : handlers) {\r
-            handler.postCommit(new CommitEvent(this));\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Discards all changes done to the bound fields.\r
-     * <p>\r
-     * Only has effect if buffered mode is used.\r
-     * \r
-     */\r
-    public void discard() {\r
-        for (Field<?> f : fieldToPropertyId.keySet()) {\r
-            try {\r
-                f.discard();\r
-            } catch (Exception e) {\r
-                // TODO: handle exception\r
-                // What can we do if discard fails other than try to discard all\r
-                // other fields?\r
-            }\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Returns the field that is bound to the given property id\r
-     * \r
-     * @param propertyId\r
-     *            The property id to use to lookup the field\r
-     * @return The field that is bound to the property id or null if no field is\r
-     *         bound to that property id\r
-     */\r
-    public Field<?> getFieldForPropertyId(Object propertyId) {\r
-        return propertyIdToField.get(propertyId);\r
-    }\r
-\r
-    /**\r
-     * Returns the property id that is bound to the given field\r
-     * \r
-     * @param field\r
-     *            The field to use to lookup the property id\r
-     * @return The property id that is bound to the field or null if the field\r
-     *         is not bound to any property id by this FieldBinder\r
-     */\r
-    public Object getPropertyIdForField(Field field) {\r
-        return fieldToPropertyId.get(field);\r
-    }\r
-\r
-    /**\r
-     * Adds a commit handler.\r
-     * <p>\r
-     * The commit handler is called before the field values are committed to the\r
-     * item ( {@link CommitHandler#preCommit(CommitEvent)}) and after the item\r
-     * has been updated ({@link CommitHandler#postCommit(CommitEvent)}). If a\r
-     * {@link CommitHandler} throws a CommitException the whole commit is\r
-     * aborted and the fields retain their old values.\r
-     * \r
-     * @param commitHandler\r
-     *            The commit handler to add\r
-     */\r
-    public void addCommitHandler(CommitHandler commitHandler) {\r
-        commitHandlers.add(commitHandler);\r
-    }\r
-\r
-    /**\r
-     * Removes the given commit handler.\r
-     * \r
-     * @see #addCommitHandler(CommitHandler)\r
-     * \r
-     * @param commitHandler\r
-     *            The commit handler to remove\r
-     */\r
-    public void removeCommitHandler(CommitHandler commitHandler) {\r
-        commitHandlers.remove(commitHandler);\r
-    }\r
-\r
-    /**\r
-     * Returns a list of all commit handlers for this {@link FieldBinder}.\r
-     * <p>\r
-     * Use {@link #addCommitHandler(CommitHandler)} and\r
-     * {@link #removeCommitHandler(CommitHandler)} to register or unregister a\r
-     * commit handler.\r
-     * \r
-     * @return A collection of commit handlers\r
-     */\r
-    protected Collection<CommitHandler> getCommitHandlers() {\r
-        return Collections.unmodifiableCollection(commitHandlers);\r
-    }\r
-\r
-    /**\r
-     * FIXME Javadoc\r
-     * \r
-     */\r
-    public interface CommitHandler extends Serializable {\r
-        /**\r
-         * Called before changes are committed to the field and the item is\r
-         * updated.\r
-         * <p>\r
-         * Throw a {@link CommitException} to abort the commit.\r
-         * \r
-         * @param commitEvent\r
-         *            An event containing information regarding the commit\r
-         * @throws CommitException\r
-         *             if the commit should be aborted\r
-         */\r
-        public void preCommit(CommitEvent commitEvent) throws CommitException;\r
-\r
-        /**\r
-         * Called after changes are committed to the fields and the item is\r
-         * updated..\r
-         * <p>\r
-         * Throw a {@link CommitException} to abort the commit.\r
-         * \r
-         * @param commitEvent\r
-         *            An event containing information regarding the commit\r
-         * @throws CommitException\r
-         *             if the commit should be aborted\r
-         */\r
-        public void postCommit(CommitEvent commitEvent) throws CommitException;\r
-    }\r
-\r
-    /**\r
-     * FIXME javadoc\r
-     * \r
-     */\r
-    public static class CommitEvent implements Serializable {\r
-        private FieldBinder fieldBinder;\r
-\r
-        private CommitEvent(FieldBinder fieldBinder) {\r
-            this.fieldBinder = fieldBinder;\r
-        }\r
-\r
-        /**\r
-         * Returns the field binder that this commit relates to\r
-         * \r
-         * @return The FieldBinder that is being committed.\r
-         */\r
-        public FieldBinder getFieldBinder() {\r
-            return fieldBinder;\r
-        }\r
-\r
-    }\r
-\r
-    /**\r
-     * Checks the validity of the bound fields.\r
-     * <p>\r
-     * Call the {@link Field#validate()} for the fields to get the individual\r
-     * error messages.\r
-     * \r
-     * @return true if all bound fields are valid, false otherwise.\r
-     */\r
-    public boolean isAllFieldsValid() {\r
-        try {\r
-            for (Field<?> field : getFields()) {\r
-                field.validate();\r
-            }\r
-            return true;\r
-        } catch (InvalidValueException e) {\r
-            return false;\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Checks if any bound field has been modified.\r
-     * \r
-     * @return true if at least on field has been modified, false otherwise\r
-     */\r
-    public boolean isAnyFieldModified() {\r
-        for (Field<?> field : getFields()) {\r
-            if (field.isModified()) {\r
-                return true;\r
-            }\r
-        }\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * Binds fields for the given class.\r
-     * <p>\r
-     * This method processes all fields whose type extends {@link Field} and\r
-     * that can be mapped to a property id. Property id mapping is done based on\r
-     * the field name or on a {@link PropertyId} annotation on the field. All\r
-     * non-null fields for which a property id can be determined are bound to\r
-     * the property id.\r
-     * \r
-     * @param object\r
-     *            The object to process\r
-     * @throws FormBuilderException\r
-     *             If there is a problem building or binding a field\r
-     */\r
-    public void bindFields(Object object) throws BindException {\r
-        Class<?> objectClass = object.getClass();\r
-\r
-        for (java.lang.reflect.Field f : objectClass.getDeclaredFields()) {\r
-\r
-            if (!Field.class.isAssignableFrom(f.getType())) {\r
-                // Process next field\r
-                continue;\r
-            }\r
-\r
-            PropertyId propertyIdAnnotation = f.getAnnotation(PropertyId.class);\r
-\r
-            Class<? extends Field> fieldType = (Class<? extends Field>) f\r
-                    .getType();\r
-\r
-            Object propertyId = null;\r
-            if (propertyIdAnnotation != null) {\r
-                // @PropertyId(propertyId) always overrides property id\r
-                propertyId = propertyIdAnnotation.value();\r
-            } else {\r
-                propertyId = f.getName();\r
-            }\r
-\r
-            // Ensure that the property id exists\r
-            Class<?> propertyType;\r
-\r
-            try {\r
-                propertyType = getPropertyType(propertyId);\r
-            } catch (BindException e) {\r
-                // Property id was not found, skip this field\r
-                continue;\r
-            }\r
-\r
-            try {\r
-                // Get the field from the object\r
-                Field<?> field = (Field<?>) ReflectTools.getJavaFieldValue(\r
-                        object, f);\r
-                // Bind it to the property id\r
-                bind(field, propertyId);\r
-            } catch (Exception e) {\r
-                // If we cannot determine the value, just skip the field and try\r
-                // the next one\r
-                continue;\r
-            }\r
-\r
-        }\r
-    }\r
-\r
-    public static class CommitException extends Exception {\r
-\r
-        public CommitException() {\r
-            super();\r
-            // TODO Auto-generated constructor stub\r
-        }\r
-\r
-        public CommitException(String message, Throwable cause) {\r
-            super(message, cause);\r
-            // TODO Auto-generated constructor stub\r
-        }\r
-\r
-        public CommitException(String message) {\r
-            super(message);\r
-            // TODO Auto-generated constructor stub\r
-        }\r
-\r
-        public CommitException(Throwable cause) {\r
-            super(cause);\r
-            // TODO Auto-generated constructor stub\r
-        }\r
-\r
-    }\r
-\r
-    public static class BindException extends RuntimeException {\r
-\r
-        public BindException(String message) {\r
-            super(message);\r
-        }\r
-\r
-        public BindException(String message, Throwable t) {\r
-            super(message, t);\r
-        }\r
-\r
-    }\r
-}
\ No newline at end of file
diff --git a/src/com/vaadin/data/fieldbinder/FieldGroup.java b/src/com/vaadin/data/fieldbinder/FieldGroup.java
new file mode 100644 (file)
index 0000000..a5cf6b9
--- /dev/null
@@ -0,0 +1,733 @@
+/* \r
+@VaadinApache2LicenseForJavaFiles@\r
+ */\r
+package com.vaadin.data.fieldbinder;\r
+\r
+import java.io.Serializable;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.LinkedHashMap;\r
+import java.util.List;\r
+import java.util.logging.Logger;\r
+\r
+import com.vaadin.data.Item;\r
+import com.vaadin.data.Property;\r
+import com.vaadin.data.TransactionalProperty;\r
+import com.vaadin.data.Validator.InvalidValueException;\r
+import com.vaadin.data.fieldbinder.FormBuilder.FormBuilderException;\r
+import com.vaadin.data.util.TransactionalPropertyWrapper;\r
+import com.vaadin.tools.ReflectTools;\r
+import com.vaadin.ui.Field;\r
+import com.vaadin.ui.Form;\r
+\r
+/**\r
+ * FieldGroup provides an easy way of binding fields to data and handling\r
+ * commits of these fields.\r
+ * <p>\r
+ * The functionality of FieldGroup is similar to {@link Form} but\r
+ * {@link FieldGroup} does not handle layouts in any way. The typical use case\r
+ * is to create a layout outside the FieldGroup and then use FieldGroup to bind\r
+ * the fields to a data source.\r
+ * </p>\r
+ * <p>\r
+ * {@link FieldGroup} is not a UI component so it cannot be added to a layout.\r
+ * Using the buildAndBind methods {@link FieldGroup} can create fields for you\r
+ * using a FieldGroupFieldFactory but you still have to add them to the correct\r
+ * position in your layout.\r
+ * </p>\r
+ * \r
+ * @author Vaadin Ltd\r
+ * @version @version@\r
+ * @since 7.0\r
+ */\r
+public class FieldGroup implements Serializable {\r
+\r
+    private static final Logger logger = Logger.getLogger(FieldGroup.class\r
+            .getName());\r
+\r
+    private Item itemDataSource;\r
+    private boolean fieldsBuffered = true;\r
+\r
+    private boolean fieldsEnabled = true;\r
+    private boolean fieldsReadOnly = false;\r
+\r
+    private HashMap<Object, Field<?>> propertyIdToField = new HashMap<Object, Field<?>>();\r
+    private LinkedHashMap<Field<?>, Object> fieldToPropertyId = new LinkedHashMap<Field<?>, Object>();\r
+    private List<CommitHandler> commitHandlers = new ArrayList<CommitHandler>();\r
+\r
+    /**\r
+     * Constructs a field binder. Use {@link #setItemDataSource(Item)} to set a\r
+     * data source for the field binder.\r
+     * \r
+     */\r
+    public FieldGroup() {\r
+\r
+    }\r
+\r
+    /**\r
+     * Constructs a field binder that uses the given data source.\r
+     * \r
+     * @param itemDataSource\r
+     *            The data source to bind the fields to\r
+     */\r
+    public FieldGroup(Item itemDataSource) {\r
+        setItemDataSource(itemDataSource);\r
+    }\r
+\r
+    /**\r
+     * Updates the item that is used by this FieldBinder. Rebinds all fields to\r
+     * the properties in the new item.\r
+     * \r
+     * @param itemDataSource\r
+     *            The new item to use\r
+     */\r
+    public void setItemDataSource(Item itemDataSource) {\r
+        this.itemDataSource = itemDataSource;\r
+\r
+        for (Field<?> f : fieldToPropertyId.keySet()) {\r
+            bind(f, fieldToPropertyId.get(f));\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Gets the item used by this FieldBinder. Note that you must call\r
+     * {@link #commit()} for the item to be updated unless buffered mode has\r
+     * been switched off.\r
+     * \r
+     * @see #setFieldsBuffered(boolean)\r
+     * @see #commit()\r
+     * \r
+     * @return The item used by this FieldBinder\r
+     */\r
+    public Item getItemDataSource() {\r
+        return itemDataSource;\r
+    }\r
+\r
+    /**\r
+     * Checks the buffered mode for the bound fields.\r
+     * <p>\r
+     * \r
+     * @see #setFieldsBuffered(boolean) for more details on buffered mode\r
+     * \r
+     * @see Field#isFieldsBuffered()\r
+     * @return true if buffered mode is on, false otherwise\r
+     * \r
+     */\r
+    public boolean isFieldsBuffered() {\r
+        return fieldsBuffered;\r
+    }\r
+\r
+    /**\r
+     * Sets the buffered mode for the bound fields.\r
+     * <p>\r
+     * When buffered mode is on the item will not be updated until\r
+     * {@link #commit()} is called. If buffered mode is off the item will be\r
+     * updated once the fields are updated.\r
+     * </p>\r
+     * <p>\r
+     * The default is to use buffered mode.\r
+     * </p>\r
+     * \r
+     * @see Field#setFieldsBuffered(boolean)\r
+     * @param fieldsBuffered\r
+     *            true to turn on buffered mode, false otherwise\r
+     */\r
+    public void setFieldsBuffered(boolean fieldsBuffered) {\r
+        if (fieldsBuffered == this.fieldsBuffered) {\r
+            return;\r
+        }\r
+\r
+        this.fieldsBuffered = fieldsBuffered;\r
+        for (Field<?> field : getFields()) {\r
+            field.setBuffered(fieldsBuffered);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Returns the enabled status for the fields.\r
+     * <p>\r
+     * Note that this will not accurately represent the enabled status of all\r
+     * fields if you change the enabled status of the fields through some other\r
+     * method than {@link #setFieldsEnabled(boolean)}.\r
+     * \r
+     * @return true if the fields are enabled, false otherwise\r
+     */\r
+    public boolean isFieldsEnabled() {\r
+        return fieldsEnabled;\r
+    }\r
+\r
+    /**\r
+     * Updates the enabled state of all bound fields.\r
+     * \r
+     * @param fieldsEnabled\r
+     *            true to enable all bound fields, false to disable them\r
+     */\r
+    public void setFieldsEnabled(boolean fieldsEnabled) {\r
+        this.fieldsEnabled = fieldsEnabled;\r
+        for (Field<?> field : getFields()) {\r
+            field.setEnabled(fieldsEnabled);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Returns the read only status for the fields.\r
+     * <p>\r
+     * Note that this will not accurately represent the read only status of all\r
+     * fields if you change the read only status of the fields through some\r
+     * other method than {@link #setFieldsReadOnly(boolean)}.\r
+     * \r
+     * @return true if the fields are set to read only, false otherwise\r
+     */\r
+    public boolean isFieldsReadOnly() {\r
+        return fieldsReadOnly;\r
+    }\r
+\r
+    /**\r
+     * Updates the read only state of all bound fields.\r
+     * \r
+     * @param fieldsReadOnly\r
+     *            true to set all bound fields to read only, false to set them\r
+     *            to read write\r
+     */\r
+    public void setFieldsReadOnly(boolean fieldsReadOnly) {\r
+        this.fieldsReadOnly = fieldsReadOnly;\r
+    }\r
+\r
+    /**\r
+     * Returns a collection of all fields that have been bound.\r
+     * <p>\r
+     * The fields are not returned in any specific order.\r
+     * </p>\r
+     * \r
+     * @return A collection with all bound Fields\r
+     */\r
+    public Collection<Field<?>> getFields() {\r
+        return fieldToPropertyId.keySet();\r
+    }\r
+\r
+    /**\r
+     * Binds the field with the given propertyId from the current item. If an\r
+     * item has not been set then the binding is postponed until the item is set\r
+     * using {@link #setItemDataSource(Item)}.\r
+     * <p>\r
+     * This method also adds validators when applicable.\r
+     * </p>\r
+     * \r
+     * @param field\r
+     *            The field to bind\r
+     * @param propertyId\r
+     *            The propertyId to bind to the field\r
+     * @throws BindException\r
+     *             If the property id is already bound to another field by this\r
+     *             field binder\r
+     */\r
+    public void bind(Field<?> field, Object propertyId) throws BindException {\r
+        if (propertyIdToField.containsKey(propertyId)\r
+                && propertyIdToField.get(propertyId) != field) {\r
+            throw new BindException("Property id " + propertyId\r
+                    + " is already bound to another field");\r
+        }\r
+        fieldToPropertyId.put(field, propertyId);\r
+        propertyIdToField.put(propertyId, field);\r
+        if (itemDataSource == null) {\r
+            // Will be bound when data source is set\r
+            return;\r
+        }\r
+\r
+        field.setPropertyDataSource(wrapInTransactionalProperty(getItemProperty(propertyId)));\r
+        configureField(field);\r
+    }\r
+\r
+    private <T> TransactionalProperty<T> wrapInTransactionalProperty(\r
+            Property<T> itemProperty) {\r
+        return new TransactionalPropertyWrapper<T>(itemProperty);\r
+    }\r
+\r
+    /**\r
+     * Gets the property with the given property id from the item.\r
+     * \r
+     * @param propertyId\r
+     *            The id if the property to find\r
+     * @return The property with the given id from the item\r
+     * @throws BindException\r
+     *             If the property was not found in the item or no item has been\r
+     *             set\r
+     */\r
+    protected Property<?> getItemProperty(Object propertyId)\r
+            throws BindException {\r
+        Item item = getItemDataSource();\r
+        if (item == null) {\r
+            throw new BindException("Could not lookup property with id "\r
+                    + propertyId + " as no item has been set");\r
+        }\r
+        Property<?> p = item.getItemProperty(propertyId);\r
+        if (p == null) {\r
+            throw new BindException("A property with id " + propertyId\r
+                    + " was not found in the item");\r
+        }\r
+        return p;\r
+    }\r
+\r
+    /**\r
+     * Detaches the field from its property id and removes it from this\r
+     * FieldBinder.\r
+     * <p>\r
+     * Note that the field is not detached from its property data source if it\r
+     * is no longer connected to the same property id it was bound to using this\r
+     * FieldBinder.\r
+     * \r
+     * @param field\r
+     *            The field to detach\r
+     * @throws BindException\r
+     *             If the field is not bound by this field binder or not bound\r
+     *             to the correct property id\r
+     */\r
+    public void remove(Field<?> field) throws BindException {\r
+        Object propertyId = fieldToPropertyId.get(field);\r
+        if (propertyId == null) {\r
+            throw new BindException(\r
+                    "The given field is not part of this FieldBinder");\r
+        }\r
+\r
+        Property fieldDataSource = field.getPropertyDataSource();\r
+        if (fieldDataSource instanceof TransactionalPropertyWrapper) {\r
+            fieldDataSource = ((TransactionalPropertyWrapper) fieldDataSource)\r
+                    .getWrappedProperty();\r
+        }\r
+        if (fieldDataSource == getItemProperty(propertyId)) {\r
+            field.setPropertyDataSource(null);\r
+        }\r
+        fieldToPropertyId.remove(field);\r
+        propertyIdToField.remove(propertyId);\r
+    }\r
+\r
+    /**\r
+     * Configures a field with the settings set for this FieldBinder.\r
+     * <p>\r
+     * By default this updates the buffered, read only and enabled state of the\r
+     * field. Also adds validators when applicable.\r
+     * \r
+     * @param field\r
+     *            The field to update\r
+     */\r
+    protected void configureField(Field<?> field) {\r
+        field.setBuffered(isFieldsBuffered());\r
+\r
+        field.setEnabled(isFieldsEnabled());\r
+        field.setReadOnly(isFieldsReadOnly());\r
+    }\r
+\r
+    /**\r
+     * Gets the type of the property with the given property id.\r
+     * \r
+     * @param propertyId\r
+     *            The propertyId. Must be find\r
+     * @return The type of the property\r
+     */\r
+    protected Class<?> getPropertyType(Object propertyId) throws BindException {\r
+        if (getItemDataSource() == null) {\r
+            throw new BindException(\r
+                    "Property type for '"\r
+                            + propertyId\r
+                            + "' could not be determined. No item data source has been set.");\r
+        }\r
+        Property<?> p = getItemDataSource().getItemProperty(propertyId);\r
+        if (p == null) {\r
+            throw new BindException(\r
+                    "Property type for '"\r
+                            + propertyId\r
+                            + "' could not be determined. No property with that id was found.");\r
+        }\r
+\r
+        return p.getType();\r
+    }\r
+\r
+    /**\r
+     * Returns a collection of all property ids that have been bound to fields.\r
+     * <p>\r
+     * Note that this will return property ids even before the item has been\r
+     * set. In that case it returns the property ids that will be bound once the\r
+     * item is set.\r
+     * </p>\r
+     * <p>\r
+     * No guarantee is given for the order of the property ids\r
+     * </p>\r
+     * \r
+     * @return A collection of bound property ids\r
+     */\r
+    public Collection<Object> getBoundPropertyIds() {\r
+        return Collections.unmodifiableCollection(propertyIdToField.keySet());\r
+    }\r
+\r
+    /**\r
+     * Returns a collection of all property ids that exist in the item set using\r
+     * {@link #setItemDataSource(Item)} but have not been bound to fields.\r
+     * <p>\r
+     * Will always return an empty collection before an item has been set using\r
+     * {@link #setItemDataSource(Item)}.\r
+     * </p>\r
+     * <p>\r
+     * No guarantee is given for the order of the property ids\r
+     * </p>\r
+     * \r
+     * @return A collection of property ids that have not been bound to fields\r
+     */\r
+    protected Collection<Object> getUnboundPropertyIds() {\r
+        if (getItemDataSource() == null) {\r
+            return new ArrayList<Object>();\r
+        }\r
+        List<Object> unboundPropertyIds = new ArrayList<Object>();\r
+        unboundPropertyIds.addAll(getItemDataSource().getItemPropertyIds());\r
+        unboundPropertyIds.removeAll(propertyIdToField.keySet());\r
+        return unboundPropertyIds;\r
+    }\r
+\r
+    /**\r
+     * Commits all changes done to the bound fields.\r
+     * <p>\r
+     * Calls all {@link CommitHandler}s before and after committing the field\r
+     * changes to the item data source. The whole commit is aborted and state is\r
+     * restored to what it was before commit was called if any\r
+     * {@link CommitHandler} throws a CommitException or there is a problem\r
+     * committing the fields\r
+     * \r
+     * @throws CommitException\r
+     *             If the commit was aborted\r
+     */\r
+    public void commit() throws CommitException {\r
+        if (!isFieldsBuffered()) {\r
+            // Not using buffered mode, nothing to do\r
+            return;\r
+        }\r
+        for (Field<?> f : fieldToPropertyId.keySet()) {\r
+            ((TransactionalProperty<?>) f.getPropertyDataSource())\r
+                    .startTransaction();\r
+        }\r
+        try {\r
+            firePreCommitEvent();\r
+            // Commit the field values to the properties\r
+            for (Field<?> f : fieldToPropertyId.keySet()) {\r
+                f.commit();\r
+            }\r
+            firePostCommitEvent();\r
+\r
+            // Commit the properties\r
+            for (Field<?> f : fieldToPropertyId.keySet()) {\r
+                ((TransactionalProperty<?>) f.getPropertyDataSource()).commit();\r
+            }\r
+\r
+        } catch (Exception e) {\r
+            for (Field<?> f : fieldToPropertyId.keySet()) {\r
+                try {\r
+                    ((TransactionalProperty<?>) f.getPropertyDataSource())\r
+                            .rollback();\r
+                } catch (Exception rollbackException) {\r
+                    // FIXME: What to do ?\r
+                }\r
+            }\r
+\r
+            throw new CommitException("Commit failed", e);\r
+        }\r
+\r
+    }\r
+\r
+    /**\r
+     * Sends a preCommit event to all registered commit handlers\r
+     * \r
+     * @throws CommitException\r
+     *             If the commit should be aborted\r
+     */\r
+    private void firePreCommitEvent() throws CommitException {\r
+        CommitHandler[] handlers = commitHandlers\r
+                .toArray(new CommitHandler[commitHandlers.size()]);\r
+\r
+        for (CommitHandler handler : handlers) {\r
+            handler.preCommit(new CommitEvent(this));\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Sends a postCommit event to all registered commit handlers\r
+     * \r
+     * @throws CommitException\r
+     *             If the commit should be aborted\r
+     */\r
+    private void firePostCommitEvent() throws CommitException {\r
+        CommitHandler[] handlers = commitHandlers\r
+                .toArray(new CommitHandler[commitHandlers.size()]);\r
+\r
+        for (CommitHandler handler : handlers) {\r
+            handler.postCommit(new CommitEvent(this));\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Discards all changes done to the bound fields.\r
+     * <p>\r
+     * Only has effect if buffered mode is used.\r
+     * \r
+     */\r
+    public void discard() {\r
+        for (Field<?> f : fieldToPropertyId.keySet()) {\r
+            try {\r
+                f.discard();\r
+            } catch (Exception e) {\r
+                // TODO: handle exception\r
+                // What can we do if discard fails other than try to discard all\r
+                // other fields?\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Returns the field that is bound to the given property id\r
+     * \r
+     * @param propertyId\r
+     *            The property id to use to lookup the field\r
+     * @return The field that is bound to the property id or null if no field is\r
+     *         bound to that property id\r
+     */\r
+    public Field<?> getFieldForPropertyId(Object propertyId) {\r
+        return propertyIdToField.get(propertyId);\r
+    }\r
+\r
+    /**\r
+     * Returns the property id that is bound to the given field\r
+     * \r
+     * @param field\r
+     *            The field to use to lookup the property id\r
+     * @return The property id that is bound to the field or null if the field\r
+     *         is not bound to any property id by this FieldBinder\r
+     */\r
+    public Object getPropertyIdForField(Field field) {\r
+        return fieldToPropertyId.get(field);\r
+    }\r
+\r
+    /**\r
+     * Adds a commit handler.\r
+     * <p>\r
+     * The commit handler is called before the field values are committed to the\r
+     * item ( {@link CommitHandler#preCommit(CommitEvent)}) and after the item\r
+     * has been updated ({@link CommitHandler#postCommit(CommitEvent)}). If a\r
+     * {@link CommitHandler} throws a CommitException the whole commit is\r
+     * aborted and the fields retain their old values.\r
+     * \r
+     * @param commitHandler\r
+     *            The commit handler to add\r
+     */\r
+    public void addCommitHandler(CommitHandler commitHandler) {\r
+        commitHandlers.add(commitHandler);\r
+    }\r
+\r
+    /**\r
+     * Removes the given commit handler.\r
+     * \r
+     * @see #addCommitHandler(CommitHandler)\r
+     * \r
+     * @param commitHandler\r
+     *            The commit handler to remove\r
+     */\r
+    public void removeCommitHandler(CommitHandler commitHandler) {\r
+        commitHandlers.remove(commitHandler);\r
+    }\r
+\r
+    /**\r
+     * Returns a list of all commit handlers for this {@link FieldGroup}.\r
+     * <p>\r
+     * Use {@link #addCommitHandler(CommitHandler)} and\r
+     * {@link #removeCommitHandler(CommitHandler)} to register or unregister a\r
+     * commit handler.\r
+     * \r
+     * @return A collection of commit handlers\r
+     */\r
+    protected Collection<CommitHandler> getCommitHandlers() {\r
+        return Collections.unmodifiableCollection(commitHandlers);\r
+    }\r
+\r
+    /**\r
+     * FIXME Javadoc\r
+     * \r
+     */\r
+    public interface CommitHandler extends Serializable {\r
+        /**\r
+         * Called before changes are committed to the field and the item is\r
+         * updated.\r
+         * <p>\r
+         * Throw a {@link CommitException} to abort the commit.\r
+         * \r
+         * @param commitEvent\r
+         *            An event containing information regarding the commit\r
+         * @throws CommitException\r
+         *             if the commit should be aborted\r
+         */\r
+        public void preCommit(CommitEvent commitEvent) throws CommitException;\r
+\r
+        /**\r
+         * Called after changes are committed to the fields and the item is\r
+         * updated..\r
+         * <p>\r
+         * Throw a {@link CommitException} to abort the commit.\r
+         * \r
+         * @param commitEvent\r
+         *            An event containing information regarding the commit\r
+         * @throws CommitException\r
+         *             if the commit should be aborted\r
+         */\r
+        public void postCommit(CommitEvent commitEvent) throws CommitException;\r
+    }\r
+\r
+    /**\r
+     * FIXME javadoc\r
+     * \r
+     */\r
+    public static class CommitEvent implements Serializable {\r
+        private FieldGroup fieldBinder;\r
+\r
+        private CommitEvent(FieldGroup fieldBinder) {\r
+            this.fieldBinder = fieldBinder;\r
+        }\r
+\r
+        /**\r
+         * Returns the field binder that this commit relates to\r
+         * \r
+         * @return The FieldBinder that is being committed.\r
+         */\r
+        public FieldGroup getFieldBinder() {\r
+            return fieldBinder;\r
+        }\r
+\r
+    }\r
+\r
+    /**\r
+     * Checks the validity of the bound fields.\r
+     * <p>\r
+     * Call the {@link Field#validate()} for the fields to get the individual\r
+     * error messages.\r
+     * \r
+     * @return true if all bound fields are valid, false otherwise.\r
+     */\r
+    public boolean isAllFieldsValid() {\r
+        try {\r
+            for (Field<?> field : getFields()) {\r
+                field.validate();\r
+            }\r
+            return true;\r
+        } catch (InvalidValueException e) {\r
+            return false;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Checks if any bound field has been modified.\r
+     * \r
+     * @return true if at least on field has been modified, false otherwise\r
+     */\r
+    public boolean isAnyFieldModified() {\r
+        for (Field<?> field : getFields()) {\r
+            if (field.isModified()) {\r
+                return true;\r
+            }\r
+        }\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * Binds fields for the given class.\r
+     * <p>\r
+     * This method processes all fields whose type extends {@link Field} and\r
+     * that can be mapped to a property id. Property id mapping is done based on\r
+     * the field name or on a {@link PropertyId} annotation on the field. All\r
+     * non-null fields for which a property id can be determined are bound to\r
+     * the property id.\r
+     * \r
+     * @param object\r
+     *            The object to process\r
+     * @throws FormBuilderException\r
+     *             If there is a problem building or binding a field\r
+     */\r
+    public void bindFields(Object object) throws BindException {\r
+        Class<?> objectClass = object.getClass();\r
+\r
+        for (java.lang.reflect.Field f : objectClass.getDeclaredFields()) {\r
+\r
+            if (!Field.class.isAssignableFrom(f.getType())) {\r
+                // Process next field\r
+                continue;\r
+            }\r
+\r
+            PropertyId propertyIdAnnotation = f.getAnnotation(PropertyId.class);\r
+\r
+            Class<? extends Field> fieldType = (Class<? extends Field>) f\r
+                    .getType();\r
+\r
+            Object propertyId = null;\r
+            if (propertyIdAnnotation != null) {\r
+                // @PropertyId(propertyId) always overrides property id\r
+                propertyId = propertyIdAnnotation.value();\r
+            } else {\r
+                propertyId = f.getName();\r
+            }\r
+\r
+            // Ensure that the property id exists\r
+            Class<?> propertyType;\r
+\r
+            try {\r
+                propertyType = getPropertyType(propertyId);\r
+            } catch (BindException e) {\r
+                // Property id was not found, skip this field\r
+                continue;\r
+            }\r
+\r
+            try {\r
+                // Get the field from the object\r
+                Field<?> field = (Field<?>) ReflectTools.getJavaFieldValue(\r
+                        object, f);\r
+                // Bind it to the property id\r
+                bind(field, propertyId);\r
+            } catch (Exception e) {\r
+                // If we cannot determine the value, just skip the field and try\r
+                // the next one\r
+                continue;\r
+            }\r
+\r
+        }\r
+    }\r
+\r
+    public static class CommitException extends Exception {\r
+\r
+        public CommitException() {\r
+            super();\r
+            // TODO Auto-generated constructor stub\r
+        }\r
+\r
+        public CommitException(String message, Throwable cause) {\r
+            super(message, cause);\r
+            // TODO Auto-generated constructor stub\r
+        }\r
+\r
+        public CommitException(String message) {\r
+            super(message);\r
+            // TODO Auto-generated constructor stub\r
+        }\r
+\r
+        public CommitException(Throwable cause) {\r
+            super(cause);\r
+            // TODO Auto-generated constructor stub\r
+        }\r
+\r
+    }\r
+\r
+    public static class BindException extends RuntimeException {\r
+\r
+        public BindException(String message) {\r
+            super(message);\r
+        }\r
+\r
+        public BindException(String message, Throwable t) {\r
+            super(message, t);\r
+        }\r
+\r
+    }\r
+}
\ No newline at end of file
index 4597d520b3420b8543fb7fba7e1db60a89ef6b93..ccf060ec975a443458bf547cddb6932e9044c6e7 100644 (file)
@@ -7,7 +7,7 @@ import java.io.Serializable;
 import java.lang.reflect.InvocationTargetException;\r
 import java.util.logging.Logger;\r
 \r
-import com.vaadin.data.fieldbinder.FieldBinder.BindException;\r
+import com.vaadin.data.fieldbinder.FieldGroup.BindException;\r
 import com.vaadin.tools.ReflectTools;\r
 import com.vaadin.ui.DefaultFieldFactory;\r
 import com.vaadin.ui.Field;\r
@@ -21,7 +21,7 @@ import com.vaadin.ui.Field;
 public class FormBuilder implements Serializable {\r
 \r
     private FormBuilderFieldFactory fieldFactory = new DefaultFormBuilderFieldFactory();\r
-    private FieldBinder fieldBinder;\r
+    private FieldGroup fieldBinder;\r
     private static final Logger logger = Logger.getLogger(FormBuilder.class\r
             .getName());\r
 \r
@@ -32,14 +32,14 @@ public class FormBuilder implements Serializable {
      *            The FieldBinder to use for binding the fields to the data\r
      *            source\r
      */\r
-    public FormBuilder(FieldBinder fieldBinder) {\r
+    public FormBuilder(FieldGroup fieldBinder) {\r
         this.fieldBinder = fieldBinder;\r
     }\r
 \r
     /**\r
      * TODO: javadoc\r
      */\r
-    protected FieldBinder getFieldBinder() {\r
+    protected FieldGroup getFieldBinder() {\r
         return fieldBinder;\r
     }\r
 \r
index b35d85995cc4b24c5e98424a931d209dd63946c5..8b4dac2d6f6d57f216c82ba8361d23f94403ceba 100644 (file)
@@ -1,7 +1,7 @@
 package com.vaadin.tests.fieldbinder;\r
 \r
-import com.vaadin.data.fieldbinder.BeanFieldBinder;\r
-import com.vaadin.data.fieldbinder.FieldBinder.CommitException;\r
+import com.vaadin.data.fieldbinder.BeanFieldGroup;\r
+import com.vaadin.data.fieldbinder.FieldGroup.CommitException;\r
 import com.vaadin.tests.components.TestBase;\r
 import com.vaadin.tests.util.Log;\r
 import com.vaadin.ui.Button;\r
@@ -16,7 +16,7 @@ public abstract class AbstractBeanFieldBinderTest extends TestBase {
 \r
     private Button discardButton;\r
     private Button showBeanButton;\r
-    private BeanFieldBinder fieldBinder;\r
+    private BeanFieldGroup fieldBinder;\r
 \r
     @Override\r
     protected void setup() {\r
@@ -73,11 +73,11 @@ public abstract class AbstractBeanFieldBinderTest extends TestBase {
         return commitButton;\r
     }\r
 \r
-    protected BeanFieldBinder getFieldBinder() {\r
+    protected BeanFieldGroup getFieldBinder() {\r
         return fieldBinder;\r
     }\r
 \r
-    protected void setFieldBinder(BeanFieldBinder beanFieldBinder) {\r
+    protected void setFieldBinder(BeanFieldGroup beanFieldBinder) {\r
         fieldBinder = beanFieldBinder;\r
     }\r
 \r
index de7193d84919293b3e047597ee97812f09bb4e2a..24901e655cf25b9daeb59b995c567879c929ba44 100644 (file)
@@ -1,10 +1,10 @@
 package com.vaadin.tests.fieldbinder;\r
 \r
-import com.vaadin.data.fieldbinder.BeanFieldBinder;\r
-import com.vaadin.data.fieldbinder.FieldBinder;\r
-import com.vaadin.data.fieldbinder.FieldBinder.CommitEvent;\r
-import com.vaadin.data.fieldbinder.FieldBinder.CommitException;\r
-import com.vaadin.data.fieldbinder.FieldBinder.CommitHandler;\r
+import com.vaadin.data.fieldbinder.BeanFieldGroup;\r
+import com.vaadin.data.fieldbinder.FieldGroup;\r
+import com.vaadin.data.fieldbinder.FieldGroup.CommitEvent;\r
+import com.vaadin.data.fieldbinder.FieldGroup.CommitException;\r
+import com.vaadin.data.fieldbinder.FieldGroup.CommitHandler;\r
 import com.vaadin.data.fieldbinder.FormBuilder;\r
 import com.vaadin.data.util.BeanItem;\r
 import com.vaadin.data.util.converter.StringToBooleanConverter;\r
@@ -65,7 +65,7 @@ public class BasicPersonForm extends TestBase {
             super("Configuration");\r
             BeanItem<Configuration> bi = new BeanItem<BasicPersonForm.Configuration>(\r
                     configuration);\r
-            FieldBinder confBinder = new FieldBinder(bi);\r
+            FieldGroup confBinder = new FieldGroup(bi);\r
             confBinder.setItemDataSource(bi);\r
             confBinder.setFieldsBuffered(false);\r
 \r
@@ -83,7 +83,7 @@ public class BasicPersonForm extends TestBase {
         Panel confPanel = new ConfigurationPanel();\r
         addComponent(confPanel);\r
 \r
-        final FieldBinder binder = new BeanFieldBinder<Person>(Person.class);\r
+        final FieldGroup binder = new BeanFieldGroup<Person>(Person.class);\r
         binder.addCommitHandler(new CommitHandler() {\r
 \r
             public void preCommit(CommitEvent commitEvent)\r
@@ -176,7 +176,7 @@ public class BasicPersonForm extends TestBase {
         binder.setItemDataSource(new BeanItem<Person>(p));\r
     }\r
 \r
-    public static Person getPerson(FieldBinder binder) {\r
+    public static Person getPerson(FieldGroup binder) {\r
         return ((BeanItem<Person>) binder.getItemDataSource()).getBean();\r
     }\r
 \r
index 43ed23e5ad481875eb196ab6b7e2fe9e596b89db..9af14cb9fd63db6312f9d6b3677fb3d70c5be5e5 100644 (file)
@@ -1,8 +1,8 @@
 package com.vaadin.tests.fieldbinder;\r
 \r
-import com.vaadin.data.fieldbinder.BeanFieldBinder;\r
-import com.vaadin.data.fieldbinder.FieldBinder;\r
-import com.vaadin.data.fieldbinder.FieldBinder.CommitException;\r
+import com.vaadin.data.fieldbinder.BeanFieldGroup;\r
+import com.vaadin.data.fieldbinder.FieldGroup;\r
+import com.vaadin.data.fieldbinder.FieldGroup.CommitException;\r
 import com.vaadin.data.fieldbinder.FormBuilder;\r
 import com.vaadin.data.util.BeanItem;\r
 import com.vaadin.tests.components.TestBase;\r
@@ -33,7 +33,7 @@ public class FieldBinderWithBeanValidation extends TestBase {
     protected void setup() {\r
         addComponent(log);\r
 \r
-        final BeanFieldBinder<PersonWithBeanValidationAnnotations> binder = new BeanFieldBinder<PersonWithBeanValidationAnnotations>(\r
+        final BeanFieldGroup<PersonWithBeanValidationAnnotations> binder = new BeanFieldGroup<PersonWithBeanValidationAnnotations>(\r
                 PersonWithBeanValidationAnnotations.class);\r
 \r
         FormBuilder builder = new FormBuilder(binder);\r
@@ -88,7 +88,7 @@ public class FieldBinderWithBeanValidation extends TestBase {
                 p));\r
     }\r
 \r
-    public static Person getPerson(FieldBinder binder) {\r
+    public static Person getPerson(FieldGroup binder) {\r
         return ((BeanItem<Person>) binder.getItemDataSource()).getBean();\r
     }\r
 \r
index 72f3d4f343f508d4ba8146f6030a783eeb89a7d0..284d69aa810bfba5293f5f3d6c893414b95d7cbd 100644 (file)
@@ -1,7 +1,7 @@
 package com.vaadin.tests.fieldbinder;\r
 \r
-import com.vaadin.data.fieldbinder.BeanFieldBinder;\r
-import com.vaadin.data.fieldbinder.FieldBinder;\r
+import com.vaadin.data.fieldbinder.BeanFieldGroup;\r
+import com.vaadin.data.fieldbinder.FieldGroup;\r
 import com.vaadin.data.fieldbinder.FormBuilder;\r
 import com.vaadin.data.fieldbinder.PropertyId;\r
 import com.vaadin.data.util.BeanItem;\r
@@ -21,7 +21,7 @@ public class FormBuilderWithNestedProperties extends TestBase {
 \r
     @Override\r
     protected void setup() {\r
-        FieldBinder fieldBinder = new BeanFieldBinder<Person>(Person.class);\r
+        FieldGroup fieldBinder = new BeanFieldGroup<Person>(Person.class);\r
         FormBuilder b = new FormBuilder(fieldBinder);\r
         b.buildAndBindFields(this);\r
 \r
index 0d29cc3004b55cb4040908a68257175033c0c4c8..69eac04d482d69e862c9dcc6cc0bb08e598fb424 100644 (file)
@@ -1,6 +1,6 @@
 package com.vaadin.tests.fieldbinder;\r
 \r
-import com.vaadin.data.fieldbinder.BeanFieldBinder;\r
+import com.vaadin.data.fieldbinder.BeanFieldGroup;\r
 import com.vaadin.data.fieldbinder.FormBuilder;\r
 import com.vaadin.data.fieldbinder.PropertyId;\r
 import com.vaadin.tests.data.bean.Address;\r
@@ -31,7 +31,7 @@ public class FormWithNestedProperties extends AbstractBeanFieldBinderTest {
     protected void setup() {\r
         super.setup();\r
 \r
-        setFieldBinder(new BeanFieldBinder<Person>(Person.class));\r
+        setFieldBinder(new BeanFieldGroup<Person>(Person.class));\r
         getFieldBinder().bindFields(this);\r
         country = new FormBuilder(getFieldBinder()).buildAndBind("country",\r
                 "address.country", NativeSelect.class);\r