]> source.dussan.org Git - vaadin-framework.git/commitdiff
com.vaadin.data.fieldbinder -> com.vaadin.data.fieldgroup
authorArtur Signell <artur@vaadin.com>
Thu, 22 Dec 2011 11:40:14 +0000 (13:40 +0200)
committerArtur Signell <artur@vaadin.com>
Thu, 22 Dec 2011 11:40:41 +0000 (13:40 +0200)
17 files changed:
src/com/vaadin/data/fieldbinder/BeanFieldGroup.java [deleted file]
src/com/vaadin/data/fieldbinder/Caption.java [deleted file]
src/com/vaadin/data/fieldbinder/DefaultFieldGroupFieldFactory.java [deleted file]
src/com/vaadin/data/fieldbinder/FieldGroup.java [deleted file]
src/com/vaadin/data/fieldbinder/FieldGroupFieldFactory.java [deleted file]
src/com/vaadin/data/fieldbinder/PropertyId.java [deleted file]
src/com/vaadin/data/fieldgroup/BeanFieldGroup.java [new file with mode: 0644]
src/com/vaadin/data/fieldgroup/Caption.java [new file with mode: 0644]
src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java [new file with mode: 0644]
src/com/vaadin/data/fieldgroup/FieldGroup.java [new file with mode: 0644]
src/com/vaadin/data/fieldgroup/FieldGroupFieldFactory.java [new file with mode: 0644]
src/com/vaadin/data/fieldgroup/PropertyId.java [new file with mode: 0644]
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/BeanFieldGroup.java b/src/com/vaadin/data/fieldbinder/BeanFieldGroup.java
deleted file mode 100644 (file)
index 3cb295b..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-/* \r
-@VaadinApache2LicenseForJavaFiles@\r
- */\r
-package com.vaadin.data.fieldbinder;\r
-\r
-import java.lang.reflect.Method;\r
-\r
-import com.vaadin.data.Item;\r
-import com.vaadin.data.util.BeanItem;\r
-import com.vaadin.data.validator.BeanValidator;\r
-import com.vaadin.ui.Field;\r
-\r
-public class BeanFieldGroup<T> extends FieldGroup {\r
-\r
-    private Class<T> beanType;\r
-\r
-    private static Boolean beanValidationImplementationAvailable = null;\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 (isBeanValidationImplementationAvailable()) {\r
-            BeanValidator validator = new BeanValidator(\r
-                    beanType, getPropertyId(field).toString());\r
-            field.addValidator(validator);\r
-            if (field.getLocale() != null) {\r
-                validator.setLocale(field.getLocale());\r
-            }\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Checks whether a bean validation implementation (e.g. Hibernate Validator\r
-     * or Apache Bean Validation) is available.\r
-     * \r
-     * TODO move this method to some more generic location\r
-     * \r
-     * @return true if a JSR-303 bean validation implementation is available\r
-     */\r
-    protected static boolean isBeanValidationImplementationAvailable() {\r
-        if (beanValidationImplementationAvailable != null) {\r
-            return beanValidationImplementationAvailable;\r
-        }\r
-        try {\r
-            Class<?> validationClass = Class\r
-                    .forName("javax.validation.Validation");\r
-            Method buildFactoryMethod = validationClass\r
-                    .getMethod("buildDefaultValidatorFactory");\r
-            Object factory = buildFactoryMethod.invoke(null);\r
-            beanValidationImplementationAvailable = (factory != null);\r
-        } catch (Exception e) {\r
-            // no bean validation implementation available\r
-            beanValidationImplementationAvailable = false;\r
-        }\r
-        return beanValidationImplementationAvailable;\r
-    }\r
-}
\ No newline at end of file
diff --git a/src/com/vaadin/data/fieldbinder/Caption.java b/src/com/vaadin/data/fieldbinder/Caption.java
deleted file mode 100644 (file)
index 7f45f64..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* \r
-@VaadinApache2LicenseForJavaFiles@\r
- */\r
-package com.vaadin.data.fieldbinder;\r
-\r
-import java.lang.annotation.ElementType;\r
-import java.lang.annotation.Retention;\r
-import java.lang.annotation.RetentionPolicy;\r
-import java.lang.annotation.Target;\r
-\r
-@Target({ ElementType.FIELD })\r
-@Retention(RetentionPolicy.RUNTIME)\r
-public @interface Caption {\r
-    String value();\r
-}\r
diff --git a/src/com/vaadin/data/fieldbinder/DefaultFieldGroupFieldFactory.java b/src/com/vaadin/data/fieldbinder/DefaultFieldGroupFieldFactory.java
deleted file mode 100644 (file)
index 28eff13..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-/* \r
-@VaadinApache2LicenseForJavaFiles@\r
- */\r
-package com.vaadin.data.fieldbinder;\r
-\r
-import java.util.EnumSet;\r
-\r
-import com.vaadin.data.Item;\r
-import com.vaadin.data.fieldbinder.FieldGroup.BindException;\r
-import com.vaadin.ui.AbstractSelect;\r
-import com.vaadin.ui.AbstractTextField;\r
-import com.vaadin.ui.CheckBox;\r
-import com.vaadin.ui.ComboBox;\r
-import com.vaadin.ui.Field;\r
-import com.vaadin.ui.ListSelect;\r
-import com.vaadin.ui.NativeSelect;\r
-import com.vaadin.ui.OptionGroup;\r
-import com.vaadin.ui.RichTextArea;\r
-import com.vaadin.ui.Table;\r
-import com.vaadin.ui.TextField;\r
-\r
-public class DefaultFieldGroupFieldFactory implements FieldGroupFieldFactory {\r
-\r
-    public static final Object CAPTION_PROPERTY_ID = "Caption";\r
-\r
-    public <T extends Field> T createField(Class<?> type, Class<T> fieldType) {\r
-        if (Enum.class.isAssignableFrom(type)) {\r
-            return createEnumField(type, fieldType);\r
-        } else if (Boolean.class.isAssignableFrom(type)\r
-                || boolean.class.isAssignableFrom(type)) {\r
-            return createBooleanField(fieldType);\r
-        }\r
-        if (AbstractTextField.class.isAssignableFrom(fieldType)) {\r
-            return fieldType.cast(createAbstractTextField(fieldType\r
-                    .asSubclass(AbstractTextField.class)));\r
-        } else if (fieldType == RichTextArea.class) {\r
-            return fieldType.cast(createRichTextArea());\r
-        }\r
-        return createDefaultField(type, fieldType);\r
-    }\r
-\r
-    protected RichTextArea createRichTextArea() {\r
-        RichTextArea rta = new RichTextArea();\r
-        rta.setImmediate(true);\r
-\r
-        return rta;\r
-    }\r
-\r
-    private <T extends Field> T createEnumField(Class<?> type,\r
-            Class<T> fieldType) {\r
-        if (AbstractSelect.class.isAssignableFrom(fieldType)) {\r
-            AbstractSelect s = createCompatibleSelect((Class<? extends AbstractSelect>) fieldType);\r
-            populateWithEnumData(s, (Class<? extends Enum>) type);\r
-            return (T) s;\r
-        }\r
-\r
-        return null;\r
-    }\r
-\r
-    protected AbstractSelect createCompatibleSelect(\r
-            Class<? extends AbstractSelect> fieldType) {\r
-        AbstractSelect select;\r
-        if (fieldType.isAssignableFrom(ListSelect.class)) {\r
-            select = new ListSelect();\r
-            select.setMultiSelect(false);\r
-        } else if (fieldType.isAssignableFrom(NativeSelect.class)) {\r
-            select = new NativeSelect();\r
-        } else if (fieldType.isAssignableFrom(OptionGroup.class)) {\r
-            select = new OptionGroup();\r
-            select.setMultiSelect(false);\r
-        } else if (fieldType.isAssignableFrom(Table.class)) {\r
-            Table t = new Table();\r
-            t.setSelectable(true);\r
-            select = t;\r
-        } else {\r
-            select = new ComboBox(null);\r
-        }\r
-        select.setImmediate(true);\r
-        select.setNullSelectionAllowed(false);\r
-\r
-        return select;\r
-    }\r
-\r
-    protected <T extends Field> T createBooleanField(Class<T> fieldType) {\r
-        if (fieldType.isAssignableFrom(CheckBox.class)) {\r
-            CheckBox cb = new CheckBox(null);\r
-            cb.setImmediate(true);\r
-            return (T) cb;\r
-        } else if (AbstractTextField.class.isAssignableFrom(fieldType)) {\r
-            return (T) createAbstractTextField((Class<? extends AbstractTextField>) fieldType);\r
-        }\r
-\r
-        return null;\r
-    }\r
-\r
-    protected <T extends AbstractTextField> T createAbstractTextField(\r
-            Class<T> fieldType) {\r
-        if (fieldType == AbstractTextField.class) {\r
-            fieldType = (Class<T>) TextField.class;\r
-        }\r
-        try {\r
-            T field = fieldType.newInstance();\r
-            field.setImmediate(true);\r
-            return field;\r
-        } catch (Exception e) {\r
-            throw new BindException("Could not create a field of type "\r
-                    + fieldType, e);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Fallback when no specific field has been created. Typically returns a\r
-     * TextField.\r
-     * \r
-     * @param <T>\r
-     *            The type of field to create\r
-     * @param type\r
-     *            The type of data that should be edited\r
-     * @param fieldType\r
-     *            The type of field to create\r
-     * @return A field capable of editing the data or null if no field could be\r
-     *         created\r
-     */\r
-    protected <T extends Field> T createDefaultField(Class<?> type,\r
-            Class<T> fieldType) {\r
-        if (fieldType.isAssignableFrom(TextField.class)) {\r
-            return fieldType.cast(createAbstractTextField(TextField.class));\r
-        }\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * Populates the given select with all the enums in the given {@link Enum}\r
-     * class. Uses {@link Enum}.toString() for caption.\r
-     * \r
-     * @param select\r
-     *            The select to populate\r
-     * @param enumClass\r
-     *            The Enum class to use\r
-     */\r
-    protected void populateWithEnumData(AbstractSelect select,\r
-            Class<? extends Enum> enumClass) {\r
-        select.removeAllItems();\r
-        for (Object p : select.getContainerPropertyIds()) {\r
-            select.removeContainerProperty(p);\r
-        }\r
-        select.addContainerProperty(CAPTION_PROPERTY_ID, String.class, "");\r
-        select.setItemCaptionPropertyId(CAPTION_PROPERTY_ID);\r
-        @SuppressWarnings("unchecked")\r
-        EnumSet<?> enumSet = EnumSet.allOf(enumClass);\r
-        for (Object r : enumSet) {\r
-            Item newItem = select.addItem(r);\r
-            newItem.getItemProperty(CAPTION_PROPERTY_ID).setValue(r.toString());\r
-        }\r
-    }\r
-}\r
diff --git a/src/com/vaadin/data/fieldbinder/FieldGroup.java b/src/com/vaadin/data/fieldbinder/FieldGroup.java
deleted file mode 100644 (file)
index 8f5a507..0000000
+++ /dev/null
@@ -1,978 +0,0 @@
-/* \r
-@VaadinApache2LicenseForJavaFiles@\r
- */\r
-package com.vaadin.data.fieldbinder;\r
-\r
-import java.io.Serializable;\r
-import java.lang.reflect.InvocationTargetException;\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.util.TransactionalPropertyWrapper;\r
-import com.vaadin.tools.ReflectTools;\r
-import com.vaadin.ui.DefaultFieldFactory;\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 buffered = true;\r
-\r
-    private boolean enabled = true;\r
-    private boolean readOnly = 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
-     * The field factory used by builder methods.\r
-     */\r
-    private FieldGroupFieldFactory fieldFactory;\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 #setBuffered(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 #setBuffered(boolean) for more details on buffered mode\r
-     * \r
-     * @see Field#isBuffered()\r
-     * @return true if buffered mode is on, false otherwise\r
-     * \r
-     */\r
-    public boolean isBuffered() {\r
-        return buffered;\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#setBuffered(boolean)\r
-     * @param buffered\r
-     *            true to turn on buffered mode, false otherwise\r
-     */\r
-    public void setBuffered(boolean buffered) {\r
-        if (buffered == this.buffered) {\r
-            return;\r
-        }\r
-\r
-        this.buffered = buffered;\r
-        for (Field<?> field : getFields()) {\r
-            field.setBuffered(buffered);\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 #setEnabled(boolean)}.\r
-     * \r
-     * @return true if the fields are enabled, false otherwise\r
-     */\r
-    public boolean isEnabled() {\r
-        return enabled;\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 setEnabled(boolean fieldsEnabled) {\r
-        enabled = 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 #setReadOnly(boolean)}.\r
-     * \r
-     * @return true if the fields are set to read only, false otherwise\r
-     */\r
-    public boolean isReadOnly() {\r
-        return readOnly;\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 setReadOnly(boolean fieldsReadOnly) {\r
-        readOnly = 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> Property.Transactional<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 unbind(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(isBuffered());\r
-\r
-        field.setEnabled(isEnabled());\r
-        field.setReadOnly(isReadOnly());\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
-    public 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 (!isBuffered()) {\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<?> getField(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 getPropertyId(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
-     * CommitHandlers are used by {@link FieldGroup#commit()} as part of the\r
-     * commit transactions. CommitHandlers can perform custom operations as part\r
-     * of the commit and cause the commit to be aborted by throwing a\r
-     * {@link CommitException}.\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 isValid() {\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 isModified() {\r
-        for (Field<?> field : getFields()) {\r
-            if (field.isModified()) {\r
-                return true;\r
-            }\r
-        }\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * Gets the field factory for the {@link FieldGroup}. The field factory is\r
-     * only used when {@link FieldGroup} creates a new field.\r
-     * \r
-     * @return The field factory in use\r
-     * \r
-     */\r
-    public FieldGroupFieldFactory getFieldFactory() {\r
-        return fieldFactory;\r
-    }\r
-\r
-    /**\r
-     * Sets the field factory for the {@link FieldGroup}. The field factory is\r
-     * only used when {@link FieldGroup} creates a new field.\r
-     * \r
-     * @param fieldFactory\r
-     *            The field factory to use\r
-     */\r
-    public void setFieldFactory(FieldGroupFieldFactory fieldFactory) {\r
-        this.fieldFactory = fieldFactory;\r
-    }\r
-\r
-    /**\r
-     * Binds member fields found in the given object.\r
-     * <p>\r
-     * This method processes all (Java) member fields whose type extends\r
-     * {@link Field} and that can be mapped to a property id. Property id\r
-     * mapping is done based on the field name or on a @{@link PropertyId}\r
-     * annotation on the field. All non-null fields for which a property id can\r
-     * be determined are bound to the property id.\r
-     * </p>\r
-     * <p>\r
-     * For example:\r
-     * \r
-     * <pre>\r
-     * public class MyForm extends VerticalLayout {\r
-     * private TextField firstName = new TextField("First name");\r
-     * @PropertyId("last")\r
-     * private TextField lastName = new TextField("Last name"); \r
-     * private TextField age = new TextField("Age"); ... }\r
-     * \r
-     * MyForm myForm = new MyForm(); \r
-     * ... \r
-     * fieldGroup.bindMemberFields(myForm);\r
-     * </pre>\r
-     * \r
-     * </p>\r
-     * This binds the firstName TextField to a "firstName" property in the item,\r
-     * lastName TextField to a "last" property and the age TextField to a "age"\r
-     * property.\r
-     * \r
-     * @param objectWithMemberFields\r
-     *            The object that contains (Java) member fields to bind\r
-     * @throws BindException\r
-     *             If there is a problem binding a field\r
-     */\r
-    public void bindMemberFields(Object objectWithMemberFields)\r
-            throws BindException {\r
-        buildAndBindMemberFields(objectWithMemberFields, false);\r
-    }\r
-\r
-    /**\r
-     * Binds member fields found in the given object and builds member fields\r
-     * that have not been initialized.\r
-     * <p>\r
-     * This method processes all (Java) member fields whose type extends\r
-     * {@link Field} and that can be mapped to a property id. Property id\r
-     * mapping is done based on the field name or on a @{@link PropertyId}\r
-     * annotation on the field. Fields that are not initialized (null) are built\r
-     * using the field factory. All non-null fields for which a property id can\r
-     * be determined are bound to the property id.\r
-     * </p>\r
-     * <p>\r
-     * For example:\r
-     * \r
-     * <pre>\r
-     * public class MyForm extends VerticalLayout {\r
-     * private TextField firstName = new TextField("First name");\r
-     * @PropertyId("last")\r
-     * private TextField lastName = new TextField("Last name"); \r
-     * private TextField age;\r
-     * \r
-     * MyForm myForm = new MyForm(); \r
-     * ... \r
-     * fieldGroup.buildAndBindMemberFields(myForm);\r
-     * </pre>\r
-     * \r
-     * </p>\r
-     * <p>\r
-     * This binds the firstName TextField to a "firstName" property in the item,\r
-     * lastName TextField to a "last" property and builds an age TextField using\r
-     * the field factory and then binds it to the "age" property.\r
-     * </p>\r
-     * \r
-     * @param objectWithMemberFields\r
-     *            The object that contains (Java) member fields to build and\r
-     *            bind\r
-     * @throws BindException\r
-     *             If there is a problem binding or building a field\r
-     */\r
-    public void buildAndBindMemberFields(Object objectWithMemberFields)\r
-            throws BindException {\r
-        buildAndBindMemberFields(objectWithMemberFields, true);\r
-    }\r
-\r
-    /**\r
-     * Binds member fields found in the given object and optionally builds\r
-     * member fields that have not been initialized.\r
-     * <p>\r
-     * This method processes all (Java) member fields whose type extends\r
-     * {@link Field} and that can be mapped to a property id. Property id\r
-     * mapping is done based on the field name or on a @{@link PropertyId}\r
-     * annotation on the field. Fields that are not initialized (null) are built\r
-     * using the field factory is buildFields is true. All non-null fields for\r
-     * which a property id can be determined are bound to the property id.\r
-     * </p>\r
-     * \r
-     * @param objectWithMemberFields\r
-     *            The object that contains (Java) member fields to build and\r
-     *            bind\r
-     * @throws BindException\r
-     *             If there is a problem binding or building a field\r
-     */\r
-    protected void buildAndBindMemberFields(Object objectWithMemberFields,\r
-            boolean buildFields) throws BindException {\r
-        Class<?> objectClass = objectWithMemberFields.getClass();\r
-\r
-        for (java.lang.reflect.Field memberField : objectClass\r
-                .getDeclaredFields()) {\r
-\r
-            if (!Field.class.isAssignableFrom(memberField.getType())) {\r
-                // Process next field\r
-                continue;\r
-            }\r
-\r
-            PropertyId propertyIdAnnotation = memberField\r
-                    .getAnnotation(PropertyId.class);\r
-\r
-            Class<? extends Field> fieldType = (Class<? extends Field>) memberField\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 = memberField.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
-            Field<?> field;\r
-            try {\r
-                // Get the field from the object\r
-                field = (Field<?>) ReflectTools.getJavaFieldValue(\r
-                        objectWithMemberFields, memberField);\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
-            if (field == null && buildFields) {\r
-                Caption captionAnnotation = memberField\r
-                        .getAnnotation(Caption.class);\r
-                String caption;\r
-                if (captionAnnotation != null) {\r
-                    caption = captionAnnotation.value();\r
-                } else {\r
-                    caption = DefaultFieldFactory\r
-                            .createCaptionByPropertyId(propertyId);\r
-                }\r
-\r
-                // Create the component (Field)\r
-                field = build(caption, propertyType, fieldType);\r
-\r
-                // Store it in the field\r
-                try {\r
-                    ReflectTools.setJavaFieldValue(objectWithMemberFields,\r
-                            memberField, field);\r
-                } catch (IllegalArgumentException e) {\r
-                    throw new BindException("Could not assign value to field '"\r
-                            + memberField.getName() + "'", e);\r
-                } catch (IllegalAccessException e) {\r
-                    throw new BindException("Could not assign value to field '"\r
-                            + memberField.getName() + "'", e);\r
-                } catch (InvocationTargetException e) {\r
-                    throw new BindException("Could not assign value to field '"\r
-                            + memberField.getName() + "'", e);\r
-                }\r
-            }\r
-\r
-            if (field != null) {\r
-                // Bind it to the property id\r
-                bind(field, propertyId);\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
-\r
-    /**\r
-     * Builds a field and binds it to the given property id using the field\r
-     * binder.\r
-     * \r
-     * @param propertyId\r
-     *            The property id to bind to. Must be present in the field\r
-     *            finder.\r
-     * @throws BindException\r
-     *             If there is a problem while building or binding\r
-     * @return The created and bound field\r
-     */\r
-    public Field<?> buildAndBind(Object propertyId) throws BindException {\r
-        String caption = DefaultFieldFactory\r
-                .createCaptionByPropertyId(propertyId);\r
-        return buildAndBind(caption, propertyId);\r
-    }\r
-\r
-    /**\r
-     * Builds a field using the given caption and binds it to the given property\r
-     * id using the field binder.\r
-     * \r
-     * @param caption\r
-     *            The caption for the field\r
-     * @param propertyId\r
-     *            The property id to bind to. Must be present in the field\r
-     *            finder.\r
-     * @throws BindException\r
-     *             If there is a problem while building or binding\r
-     * @return The created and bound field. Can be any type of {@link Field}.\r
-     */\r
-    public Field<?> buildAndBind(String caption, Object propertyId)\r
-            throws BindException {\r
-        Class<?> type = getPropertyType(propertyId);\r
-        return buildAndBind(caption, propertyId, Field.class);\r
-\r
-    }\r
-\r
-    /**\r
-     * Builds a field using the given caption and binds it to the given property\r
-     * id using the field binder. Ensures the new field is of the given type.\r
-     * \r
-     * @param caption\r
-     *            The caption for the field\r
-     * @param propertyId\r
-     *            The property id to bind to. Must be present in the field\r
-     *            finder.\r
-     * @throws BindException\r
-     *             If the field could not be created\r
-     * @return The created and bound field. Can be any type of {@link Field}.\r
-     */\r
-\r
-    public <T extends Field> T buildAndBind(String caption, Object propertyId,\r
-            Class<T> fieldType) throws BindException {\r
-        Class<?> type = getPropertyType(propertyId);\r
-\r
-        T field = build(caption, type, fieldType);\r
-        bind(field, propertyId);\r
-\r
-        return field;\r
-    }\r
-\r
-    /**\r
-     * Creates a field based on the given data type.\r
-     * <p>\r
-     * The data type is the type that we want to edit using the field. The field\r
-     * type is the type of field we want to create, can be {@link Field} if any\r
-     * Field is good.\r
-     * </p>\r
-     * \r
-     * @param caption\r
-     *            The caption for the new field\r
-     * @param dataType\r
-     *            The data model type that we want to edit using the field\r
-     * @param fieldType\r
-     *            The type of field that we want to create\r
-     * @return A Field capable of editing the given type\r
-     * @throws BindException\r
-     *             If the field could not be created\r
-     */\r
-    protected <T extends Field> T build(String caption, Class<?> dataType,\r
-            Class<T> fieldType) throws BindException {\r
-        T field = getFieldFactory().createField(dataType, fieldType);\r
-        if (field == null) {\r
-            throw new BindException("Unable to build a field of type "\r
-                    + fieldType.getName() + " for editing "\r
-                    + dataType.getName());\r
-        }\r
-\r
-        field.setCaption(caption);\r
-        return field;\r
-    }\r
-}
\ No newline at end of file
diff --git a/src/com/vaadin/data/fieldbinder/FieldGroupFieldFactory.java b/src/com/vaadin/data/fieldbinder/FieldGroupFieldFactory.java
deleted file mode 100644 (file)
index 8a8c2b9..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/* \r
-@VaadinApache2LicenseForJavaFiles@\r
- */\r
-package com.vaadin.data.fieldbinder;\r
-\r
-import java.io.Serializable;\r
-\r
-import com.vaadin.ui.Field;\r
-\r
-/**\r
- * Factory interface for creating new Field-instances based on the data type\r
- * that should be edited.\r
- * \r
- * @author Vaadin Ltd.\r
- * @version @version@\r
- * @since 7.0\r
- */\r
-public interface FieldGroupFieldFactory extends Serializable {\r
-    /**\r
-     * Creates a field based on the data type that we want to edit\r
-     * \r
-     * @param dataType\r
-     *            The type that we want to edit using the field\r
-     * @param fieldType\r
-     *            The type of field we want to create. If set to {@link Field}\r
-     *            then any type of field is accepted\r
-     * @return A field that can be assigned to the given fieldType and that is\r
-     *         capable of editing the given type of data\r
-     */\r
-    <T extends Field> T createField(Class<?> dataType, Class<T> fieldType);\r
-}\r
diff --git a/src/com/vaadin/data/fieldbinder/PropertyId.java b/src/com/vaadin/data/fieldbinder/PropertyId.java
deleted file mode 100644 (file)
index b9d4e80..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/* \r
-@VaadinApache2LicenseForJavaFiles@\r
- */\r
-package com.vaadin.data.fieldbinder;\r
-\r
-import java.lang.annotation.ElementType;\r
-import java.lang.annotation.Retention;\r
-import java.lang.annotation.RetentionPolicy;\r
-import java.lang.annotation.Target;\r
-\r
-@Target({ ElementType.FIELD })\r
-@Retention(RetentionPolicy.RUNTIME)\r
-public @interface PropertyId {\r
-    String value();\r
-}\r
diff --git a/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java b/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java
new file mode 100644 (file)
index 0000000..8ca6c95
--- /dev/null
@@ -0,0 +1,157 @@
+/* \r
+@VaadinApache2LicenseForJavaFiles@\r
+ */\r
+package com.vaadin.data.fieldgroup;\r
+\r
+import java.lang.reflect.Method;\r
+\r
+import com.vaadin.data.Item;\r
+import com.vaadin.data.util.BeanItem;\r
+import com.vaadin.data.validator.BeanValidator;\r
+import com.vaadin.ui.Field;\r
+\r
+public class BeanFieldGroup<T> extends FieldGroup {\r
+\r
+    private Class<T> beanType;\r
+\r
+    private static Boolean beanValidationImplementationAvailable = null;\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 (isBeanValidationImplementationAvailable()) {\r
+            BeanValidator validator = new BeanValidator(\r
+                    beanType, getPropertyId(field).toString());\r
+            field.addValidator(validator);\r
+            if (field.getLocale() != null) {\r
+                validator.setLocale(field.getLocale());\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Checks whether a bean validation implementation (e.g. Hibernate Validator\r
+     * or Apache Bean Validation) is available.\r
+     * \r
+     * TODO move this method to some more generic location\r
+     * \r
+     * @return true if a JSR-303 bean validation implementation is available\r
+     */\r
+    protected static boolean isBeanValidationImplementationAvailable() {\r
+        if (beanValidationImplementationAvailable != null) {\r
+            return beanValidationImplementationAvailable;\r
+        }\r
+        try {\r
+            Class<?> validationClass = Class\r
+                    .forName("javax.validation.Validation");\r
+            Method buildFactoryMethod = validationClass\r
+                    .getMethod("buildDefaultValidatorFactory");\r
+            Object factory = buildFactoryMethod.invoke(null);\r
+            beanValidationImplementationAvailable = (factory != null);\r
+        } catch (Exception e) {\r
+            // no bean validation implementation available\r
+            beanValidationImplementationAvailable = false;\r
+        }\r
+        return beanValidationImplementationAvailable;\r
+    }\r
+}
\ No newline at end of file
diff --git a/src/com/vaadin/data/fieldgroup/Caption.java b/src/com/vaadin/data/fieldgroup/Caption.java
new file mode 100644 (file)
index 0000000..e9ae01a
--- /dev/null
@@ -0,0 +1,15 @@
+/* \r
+@VaadinApache2LicenseForJavaFiles@\r
+ */\r
+package com.vaadin.data.fieldgroup;\r
+\r
+import java.lang.annotation.ElementType;\r
+import java.lang.annotation.Retention;\r
+import java.lang.annotation.RetentionPolicy;\r
+import java.lang.annotation.Target;\r
+\r
+@Target({ ElementType.FIELD })\r
+@Retention(RetentionPolicy.RUNTIME)\r
+public @interface Caption {\r
+    String value();\r
+}\r
diff --git a/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java b/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java
new file mode 100644 (file)
index 0000000..2fc7bc6
--- /dev/null
@@ -0,0 +1,156 @@
+/* \r
+@VaadinApache2LicenseForJavaFiles@\r
+ */\r
+package com.vaadin.data.fieldgroup;\r
+\r
+import java.util.EnumSet;\r
+\r
+import com.vaadin.data.Item;\r
+import com.vaadin.data.fieldgroup.FieldGroup.BindException;\r
+import com.vaadin.ui.AbstractSelect;\r
+import com.vaadin.ui.AbstractTextField;\r
+import com.vaadin.ui.CheckBox;\r
+import com.vaadin.ui.ComboBox;\r
+import com.vaadin.ui.Field;\r
+import com.vaadin.ui.ListSelect;\r
+import com.vaadin.ui.NativeSelect;\r
+import com.vaadin.ui.OptionGroup;\r
+import com.vaadin.ui.RichTextArea;\r
+import com.vaadin.ui.Table;\r
+import com.vaadin.ui.TextField;\r
+\r
+public class DefaultFieldGroupFieldFactory implements FieldGroupFieldFactory {\r
+\r
+    public static final Object CAPTION_PROPERTY_ID = "Caption";\r
+\r
+    public <T extends Field> T createField(Class<?> type, Class<T> fieldType) {\r
+        if (Enum.class.isAssignableFrom(type)) {\r
+            return createEnumField(type, fieldType);\r
+        } else if (Boolean.class.isAssignableFrom(type)\r
+                || boolean.class.isAssignableFrom(type)) {\r
+            return createBooleanField(fieldType);\r
+        }\r
+        if (AbstractTextField.class.isAssignableFrom(fieldType)) {\r
+            return fieldType.cast(createAbstractTextField(fieldType\r
+                    .asSubclass(AbstractTextField.class)));\r
+        } else if (fieldType == RichTextArea.class) {\r
+            return fieldType.cast(createRichTextArea());\r
+        }\r
+        return createDefaultField(type, fieldType);\r
+    }\r
+\r
+    protected RichTextArea createRichTextArea() {\r
+        RichTextArea rta = new RichTextArea();\r
+        rta.setImmediate(true);\r
+\r
+        return rta;\r
+    }\r
+\r
+    private <T extends Field> T createEnumField(Class<?> type,\r
+            Class<T> fieldType) {\r
+        if (AbstractSelect.class.isAssignableFrom(fieldType)) {\r
+            AbstractSelect s = createCompatibleSelect((Class<? extends AbstractSelect>) fieldType);\r
+            populateWithEnumData(s, (Class<? extends Enum>) type);\r
+            return (T) s;\r
+        }\r
+\r
+        return null;\r
+    }\r
+\r
+    protected AbstractSelect createCompatibleSelect(\r
+            Class<? extends AbstractSelect> fieldType) {\r
+        AbstractSelect select;\r
+        if (fieldType.isAssignableFrom(ListSelect.class)) {\r
+            select = new ListSelect();\r
+            select.setMultiSelect(false);\r
+        } else if (fieldType.isAssignableFrom(NativeSelect.class)) {\r
+            select = new NativeSelect();\r
+        } else if (fieldType.isAssignableFrom(OptionGroup.class)) {\r
+            select = new OptionGroup();\r
+            select.setMultiSelect(false);\r
+        } else if (fieldType.isAssignableFrom(Table.class)) {\r
+            Table t = new Table();\r
+            t.setSelectable(true);\r
+            select = t;\r
+        } else {\r
+            select = new ComboBox(null);\r
+        }\r
+        select.setImmediate(true);\r
+        select.setNullSelectionAllowed(false);\r
+\r
+        return select;\r
+    }\r
+\r
+    protected <T extends Field> T createBooleanField(Class<T> fieldType) {\r
+        if (fieldType.isAssignableFrom(CheckBox.class)) {\r
+            CheckBox cb = new CheckBox(null);\r
+            cb.setImmediate(true);\r
+            return (T) cb;\r
+        } else if (AbstractTextField.class.isAssignableFrom(fieldType)) {\r
+            return (T) createAbstractTextField((Class<? extends AbstractTextField>) fieldType);\r
+        }\r
+\r
+        return null;\r
+    }\r
+\r
+    protected <T extends AbstractTextField> T createAbstractTextField(\r
+            Class<T> fieldType) {\r
+        if (fieldType == AbstractTextField.class) {\r
+            fieldType = (Class<T>) TextField.class;\r
+        }\r
+        try {\r
+            T field = fieldType.newInstance();\r
+            field.setImmediate(true);\r
+            return field;\r
+        } catch (Exception e) {\r
+            throw new BindException("Could not create a field of type "\r
+                    + fieldType, e);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Fallback when no specific field has been created. Typically returns a\r
+     * TextField.\r
+     * \r
+     * @param <T>\r
+     *            The type of field to create\r
+     * @param type\r
+     *            The type of data that should be edited\r
+     * @param fieldType\r
+     *            The type of field to create\r
+     * @return A field capable of editing the data or null if no field could be\r
+     *         created\r
+     */\r
+    protected <T extends Field> T createDefaultField(Class<?> type,\r
+            Class<T> fieldType) {\r
+        if (fieldType.isAssignableFrom(TextField.class)) {\r
+            return fieldType.cast(createAbstractTextField(TextField.class));\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Populates the given select with all the enums in the given {@link Enum}\r
+     * class. Uses {@link Enum}.toString() for caption.\r
+     * \r
+     * @param select\r
+     *            The select to populate\r
+     * @param enumClass\r
+     *            The Enum class to use\r
+     */\r
+    protected void populateWithEnumData(AbstractSelect select,\r
+            Class<? extends Enum> enumClass) {\r
+        select.removeAllItems();\r
+        for (Object p : select.getContainerPropertyIds()) {\r
+            select.removeContainerProperty(p);\r
+        }\r
+        select.addContainerProperty(CAPTION_PROPERTY_ID, String.class, "");\r
+        select.setItemCaptionPropertyId(CAPTION_PROPERTY_ID);\r
+        @SuppressWarnings("unchecked")\r
+        EnumSet<?> enumSet = EnumSet.allOf(enumClass);\r
+        for (Object r : enumSet) {\r
+            Item newItem = select.addItem(r);\r
+            newItem.getItemProperty(CAPTION_PROPERTY_ID).setValue(r.toString());\r
+        }\r
+    }\r
+}\r
diff --git a/src/com/vaadin/data/fieldgroup/FieldGroup.java b/src/com/vaadin/data/fieldgroup/FieldGroup.java
new file mode 100644 (file)
index 0000000..a5d676a
--- /dev/null
@@ -0,0 +1,978 @@
+/* \r
+@VaadinApache2LicenseForJavaFiles@\r
+ */\r
+package com.vaadin.data.fieldgroup;\r
+\r
+import java.io.Serializable;\r
+import java.lang.reflect.InvocationTargetException;\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.util.TransactionalPropertyWrapper;\r
+import com.vaadin.tools.ReflectTools;\r
+import com.vaadin.ui.DefaultFieldFactory;\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 buffered = true;\r
+\r
+    private boolean enabled = true;\r
+    private boolean readOnly = 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
+     * The field factory used by builder methods.\r
+     */\r
+    private FieldGroupFieldFactory fieldFactory;\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 #setBuffered(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 #setBuffered(boolean) for more details on buffered mode\r
+     * \r
+     * @see Field#isBuffered()\r
+     * @return true if buffered mode is on, false otherwise\r
+     * \r
+     */\r
+    public boolean isBuffered() {\r
+        return buffered;\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#setBuffered(boolean)\r
+     * @param buffered\r
+     *            true to turn on buffered mode, false otherwise\r
+     */\r
+    public void setBuffered(boolean buffered) {\r
+        if (buffered == this.buffered) {\r
+            return;\r
+        }\r
+\r
+        this.buffered = buffered;\r
+        for (Field<?> field : getFields()) {\r
+            field.setBuffered(buffered);\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 #setEnabled(boolean)}.\r
+     * \r
+     * @return true if the fields are enabled, false otherwise\r
+     */\r
+    public boolean isEnabled() {\r
+        return enabled;\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 setEnabled(boolean fieldsEnabled) {\r
+        enabled = 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 #setReadOnly(boolean)}.\r
+     * \r
+     * @return true if the fields are set to read only, false otherwise\r
+     */\r
+    public boolean isReadOnly() {\r
+        return readOnly;\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 setReadOnly(boolean fieldsReadOnly) {\r
+        readOnly = 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> Property.Transactional<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 unbind(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(isBuffered());\r
+\r
+        field.setEnabled(isEnabled());\r
+        field.setReadOnly(isReadOnly());\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
+    public 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 (!isBuffered()) {\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<?> getField(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 getPropertyId(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
+     * CommitHandlers are used by {@link FieldGroup#commit()} as part of the\r
+     * commit transactions. CommitHandlers can perform custom operations as part\r
+     * of the commit and cause the commit to be aborted by throwing a\r
+     * {@link CommitException}.\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 isValid() {\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 isModified() {\r
+        for (Field<?> field : getFields()) {\r
+            if (field.isModified()) {\r
+                return true;\r
+            }\r
+        }\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * Gets the field factory for the {@link FieldGroup}. The field factory is\r
+     * only used when {@link FieldGroup} creates a new field.\r
+     * \r
+     * @return The field factory in use\r
+     * \r
+     */\r
+    public FieldGroupFieldFactory getFieldFactory() {\r
+        return fieldFactory;\r
+    }\r
+\r
+    /**\r
+     * Sets the field factory for the {@link FieldGroup}. The field factory is\r
+     * only used when {@link FieldGroup} creates a new field.\r
+     * \r
+     * @param fieldFactory\r
+     *            The field factory to use\r
+     */\r
+    public void setFieldFactory(FieldGroupFieldFactory fieldFactory) {\r
+        this.fieldFactory = fieldFactory;\r
+    }\r
+\r
+    /**\r
+     * Binds member fields found in the given object.\r
+     * <p>\r
+     * This method processes all (Java) member fields whose type extends\r
+     * {@link Field} and that can be mapped to a property id. Property id\r
+     * mapping is done based on the field name or on a @{@link PropertyId}\r
+     * annotation on the field. All non-null fields for which a property id can\r
+     * be determined are bound to the property id.\r
+     * </p>\r
+     * <p>\r
+     * For example:\r
+     * \r
+     * <pre>\r
+     * public class MyForm extends VerticalLayout {\r
+     * private TextField firstName = new TextField("First name");\r
+     * @PropertyId("last")\r
+     * private TextField lastName = new TextField("Last name"); \r
+     * private TextField age = new TextField("Age"); ... }\r
+     * \r
+     * MyForm myForm = new MyForm(); \r
+     * ... \r
+     * fieldGroup.bindMemberFields(myForm);\r
+     * </pre>\r
+     * \r
+     * </p>\r
+     * This binds the firstName TextField to a "firstName" property in the item,\r
+     * lastName TextField to a "last" property and the age TextField to a "age"\r
+     * property.\r
+     * \r
+     * @param objectWithMemberFields\r
+     *            The object that contains (Java) member fields to bind\r
+     * @throws BindException\r
+     *             If there is a problem binding a field\r
+     */\r
+    public void bindMemberFields(Object objectWithMemberFields)\r
+            throws BindException {\r
+        buildAndBindMemberFields(objectWithMemberFields, false);\r
+    }\r
+\r
+    /**\r
+     * Binds member fields found in the given object and builds member fields\r
+     * that have not been initialized.\r
+     * <p>\r
+     * This method processes all (Java) member fields whose type extends\r
+     * {@link Field} and that can be mapped to a property id. Property id\r
+     * mapping is done based on the field name or on a @{@link PropertyId}\r
+     * annotation on the field. Fields that are not initialized (null) are built\r
+     * using the field factory. All non-null fields for which a property id can\r
+     * be determined are bound to the property id.\r
+     * </p>\r
+     * <p>\r
+     * For example:\r
+     * \r
+     * <pre>\r
+     * public class MyForm extends VerticalLayout {\r
+     * private TextField firstName = new TextField("First name");\r
+     * @PropertyId("last")\r
+     * private TextField lastName = new TextField("Last name"); \r
+     * private TextField age;\r
+     * \r
+     * MyForm myForm = new MyForm(); \r
+     * ... \r
+     * fieldGroup.buildAndBindMemberFields(myForm);\r
+     * </pre>\r
+     * \r
+     * </p>\r
+     * <p>\r
+     * This binds the firstName TextField to a "firstName" property in the item,\r
+     * lastName TextField to a "last" property and builds an age TextField using\r
+     * the field factory and then binds it to the "age" property.\r
+     * </p>\r
+     * \r
+     * @param objectWithMemberFields\r
+     *            The object that contains (Java) member fields to build and\r
+     *            bind\r
+     * @throws BindException\r
+     *             If there is a problem binding or building a field\r
+     */\r
+    public void buildAndBindMemberFields(Object objectWithMemberFields)\r
+            throws BindException {\r
+        buildAndBindMemberFields(objectWithMemberFields, true);\r
+    }\r
+\r
+    /**\r
+     * Binds member fields found in the given object and optionally builds\r
+     * member fields that have not been initialized.\r
+     * <p>\r
+     * This method processes all (Java) member fields whose type extends\r
+     * {@link Field} and that can be mapped to a property id. Property id\r
+     * mapping is done based on the field name or on a @{@link PropertyId}\r
+     * annotation on the field. Fields that are not initialized (null) are built\r
+     * using the field factory is buildFields is true. All non-null fields for\r
+     * which a property id can be determined are bound to the property id.\r
+     * </p>\r
+     * \r
+     * @param objectWithMemberFields\r
+     *            The object that contains (Java) member fields to build and\r
+     *            bind\r
+     * @throws BindException\r
+     *             If there is a problem binding or building a field\r
+     */\r
+    protected void buildAndBindMemberFields(Object objectWithMemberFields,\r
+            boolean buildFields) throws BindException {\r
+        Class<?> objectClass = objectWithMemberFields.getClass();\r
+\r
+        for (java.lang.reflect.Field memberField : objectClass\r
+                .getDeclaredFields()) {\r
+\r
+            if (!Field.class.isAssignableFrom(memberField.getType())) {\r
+                // Process next field\r
+                continue;\r
+            }\r
+\r
+            PropertyId propertyIdAnnotation = memberField\r
+                    .getAnnotation(PropertyId.class);\r
+\r
+            Class<? extends Field> fieldType = (Class<? extends Field>) memberField\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 = memberField.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
+            Field<?> field;\r
+            try {\r
+                // Get the field from the object\r
+                field = (Field<?>) ReflectTools.getJavaFieldValue(\r
+                        objectWithMemberFields, memberField);\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
+            if (field == null && buildFields) {\r
+                Caption captionAnnotation = memberField\r
+                        .getAnnotation(Caption.class);\r
+                String caption;\r
+                if (captionAnnotation != null) {\r
+                    caption = captionAnnotation.value();\r
+                } else {\r
+                    caption = DefaultFieldFactory\r
+                            .createCaptionByPropertyId(propertyId);\r
+                }\r
+\r
+                // Create the component (Field)\r
+                field = build(caption, propertyType, fieldType);\r
+\r
+                // Store it in the field\r
+                try {\r
+                    ReflectTools.setJavaFieldValue(objectWithMemberFields,\r
+                            memberField, field);\r
+                } catch (IllegalArgumentException e) {\r
+                    throw new BindException("Could not assign value to field '"\r
+                            + memberField.getName() + "'", e);\r
+                } catch (IllegalAccessException e) {\r
+                    throw new BindException("Could not assign value to field '"\r
+                            + memberField.getName() + "'", e);\r
+                } catch (InvocationTargetException e) {\r
+                    throw new BindException("Could not assign value to field '"\r
+                            + memberField.getName() + "'", e);\r
+                }\r
+            }\r
+\r
+            if (field != null) {\r
+                // Bind it to the property id\r
+                bind(field, propertyId);\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
+\r
+    /**\r
+     * Builds a field and binds it to the given property id using the field\r
+     * binder.\r
+     * \r
+     * @param propertyId\r
+     *            The property id to bind to. Must be present in the field\r
+     *            finder.\r
+     * @throws BindException\r
+     *             If there is a problem while building or binding\r
+     * @return The created and bound field\r
+     */\r
+    public Field<?> buildAndBind(Object propertyId) throws BindException {\r
+        String caption = DefaultFieldFactory\r
+                .createCaptionByPropertyId(propertyId);\r
+        return buildAndBind(caption, propertyId);\r
+    }\r
+\r
+    /**\r
+     * Builds a field using the given caption and binds it to the given property\r
+     * id using the field binder.\r
+     * \r
+     * @param caption\r
+     *            The caption for the field\r
+     * @param propertyId\r
+     *            The property id to bind to. Must be present in the field\r
+     *            finder.\r
+     * @throws BindException\r
+     *             If there is a problem while building or binding\r
+     * @return The created and bound field. Can be any type of {@link Field}.\r
+     */\r
+    public Field<?> buildAndBind(String caption, Object propertyId)\r
+            throws BindException {\r
+        Class<?> type = getPropertyType(propertyId);\r
+        return buildAndBind(caption, propertyId, Field.class);\r
+\r
+    }\r
+\r
+    /**\r
+     * Builds a field using the given caption and binds it to the given property\r
+     * id using the field binder. Ensures the new field is of the given type.\r
+     * \r
+     * @param caption\r
+     *            The caption for the field\r
+     * @param propertyId\r
+     *            The property id to bind to. Must be present in the field\r
+     *            finder.\r
+     * @throws BindException\r
+     *             If the field could not be created\r
+     * @return The created and bound field. Can be any type of {@link Field}.\r
+     */\r
+\r
+    public <T extends Field> T buildAndBind(String caption, Object propertyId,\r
+            Class<T> fieldType) throws BindException {\r
+        Class<?> type = getPropertyType(propertyId);\r
+\r
+        T field = build(caption, type, fieldType);\r
+        bind(field, propertyId);\r
+\r
+        return field;\r
+    }\r
+\r
+    /**\r
+     * Creates a field based on the given data type.\r
+     * <p>\r
+     * The data type is the type that we want to edit using the field. The field\r
+     * type is the type of field we want to create, can be {@link Field} if any\r
+     * Field is good.\r
+     * </p>\r
+     * \r
+     * @param caption\r
+     *            The caption for the new field\r
+     * @param dataType\r
+     *            The data model type that we want to edit using the field\r
+     * @param fieldType\r
+     *            The type of field that we want to create\r
+     * @return A Field capable of editing the given type\r
+     * @throws BindException\r
+     *             If the field could not be created\r
+     */\r
+    protected <T extends Field> T build(String caption, Class<?> dataType,\r
+            Class<T> fieldType) throws BindException {\r
+        T field = getFieldFactory().createField(dataType, fieldType);\r
+        if (field == null) {\r
+            throw new BindException("Unable to build a field of type "\r
+                    + fieldType.getName() + " for editing "\r
+                    + dataType.getName());\r
+        }\r
+\r
+        field.setCaption(caption);\r
+        return field;\r
+    }\r
+}
\ No newline at end of file
diff --git a/src/com/vaadin/data/fieldgroup/FieldGroupFieldFactory.java b/src/com/vaadin/data/fieldgroup/FieldGroupFieldFactory.java
new file mode 100644 (file)
index 0000000..6945a49
--- /dev/null
@@ -0,0 +1,31 @@
+/* \r
+@VaadinApache2LicenseForJavaFiles@\r
+ */\r
+package com.vaadin.data.fieldgroup;\r
+\r
+import java.io.Serializable;\r
+\r
+import com.vaadin.ui.Field;\r
+\r
+/**\r
+ * Factory interface for creating new Field-instances based on the data type\r
+ * that should be edited.\r
+ * \r
+ * @author Vaadin Ltd.\r
+ * @version @version@\r
+ * @since 7.0\r
+ */\r
+public interface FieldGroupFieldFactory extends Serializable {\r
+    /**\r
+     * Creates a field based on the data type that we want to edit\r
+     * \r
+     * @param dataType\r
+     *            The type that we want to edit using the field\r
+     * @param fieldType\r
+     *            The type of field we want to create. If set to {@link Field}\r
+     *            then any type of field is accepted\r
+     * @return A field that can be assigned to the given fieldType and that is\r
+     *         capable of editing the given type of data\r
+     */\r
+    <T extends Field> T createField(Class<?> dataType, Class<T> fieldType);\r
+}\r
diff --git a/src/com/vaadin/data/fieldgroup/PropertyId.java b/src/com/vaadin/data/fieldgroup/PropertyId.java
new file mode 100644 (file)
index 0000000..588fdc3
--- /dev/null
@@ -0,0 +1,15 @@
+/* \r
+@VaadinApache2LicenseForJavaFiles@\r
+ */\r
+package com.vaadin.data.fieldgroup;\r
+\r
+import java.lang.annotation.ElementType;\r
+import java.lang.annotation.Retention;\r
+import java.lang.annotation.RetentionPolicy;\r
+import java.lang.annotation.Target;\r
+\r
+@Target({ ElementType.FIELD })\r
+@Retention(RetentionPolicy.RUNTIME)\r
+public @interface PropertyId {\r
+    String value();\r
+}\r
index 8b4dac2d6f6d57f216c82ba8361d23f94403ceba..e2ed2ad696abc8133bb0830fe35303f6f2745f6e 100644 (file)
@@ -1,7 +1,7 @@
 package com.vaadin.tests.fieldbinder;\r
 \r
-import com.vaadin.data.fieldbinder.BeanFieldGroup;\r
-import com.vaadin.data.fieldbinder.FieldGroup.CommitException;\r
+import com.vaadin.data.fieldgroup.BeanFieldGroup;\r
+import com.vaadin.data.fieldgroup.FieldGroup.CommitException;\r
 import com.vaadin.tests.components.TestBase;\r
 import com.vaadin.tests.util.Log;\r
 import com.vaadin.ui.Button;\r
index dd3f201f49a80998da6159fb06c6d3f474d0d58f..1ba9426fced77057fd65b7ea1e75585f841e3cfc 100644 (file)
@@ -1,10 +1,10 @@
 package com.vaadin.tests.fieldbinder;\r
 \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.fieldgroup.BeanFieldGroup;\r
+import com.vaadin.data.fieldgroup.FieldGroup;\r
+import com.vaadin.data.fieldgroup.FieldGroup.CommitEvent;\r
+import com.vaadin.data.fieldgroup.FieldGroup.CommitException;\r
+import com.vaadin.data.fieldgroup.FieldGroup.CommitHandler;\r
 import com.vaadin.data.util.BeanItem;\r
 import com.vaadin.data.util.converter.StringToBooleanConverter;\r
 import com.vaadin.data.validator.EmailValidator;\r
index f6c585d5db6276ccc8f86de107eb28615ced6649..e7b581907e09157260e4c66a2006774ac39e148f 100644 (file)
@@ -1,8 +1,8 @@
 package com.vaadin.tests.fieldbinder;\r
 \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.fieldgroup.BeanFieldGroup;\r
+import com.vaadin.data.fieldgroup.FieldGroup;\r
+import com.vaadin.data.fieldgroup.FieldGroup.CommitException;\r
 import com.vaadin.data.util.BeanItem;\r
 import com.vaadin.tests.components.TestBase;\r
 import com.vaadin.tests.data.bean.Address;\r
index da6a66659036b077ba8dd27d9e85d31d2350ee41..e786dae2791e5f5344061eb454cab024f66b0268 100644 (file)
@@ -1,8 +1,8 @@
 package com.vaadin.tests.fieldbinder;\r
 \r
-import com.vaadin.data.fieldbinder.BeanFieldGroup;\r
-import com.vaadin.data.fieldbinder.FieldGroup;\r
-import com.vaadin.data.fieldbinder.PropertyId;\r
+import com.vaadin.data.fieldgroup.BeanFieldGroup;\r
+import com.vaadin.data.fieldgroup.FieldGroup;\r
+import com.vaadin.data.fieldgroup.PropertyId;\r
 import com.vaadin.data.util.BeanItem;\r
 import com.vaadin.tests.components.TestBase;\r
 import com.vaadin.tests.data.bean.Address;\r
index 9886bc61f514361eb7ce8e4c4a5efcd3389ed6d7..7dbf1e7f13abd6a28f63acaaea17e2176c22cc8b 100644 (file)
@@ -1,7 +1,7 @@
 package com.vaadin.tests.fieldbinder;\r
 \r
-import com.vaadin.data.fieldbinder.BeanFieldGroup;\r
-import com.vaadin.data.fieldbinder.PropertyId;\r
+import com.vaadin.data.fieldgroup.BeanFieldGroup;\r
+import com.vaadin.data.fieldgroup.PropertyId;\r
 import com.vaadin.tests.data.bean.Address;\r
 import com.vaadin.tests.data.bean.Country;\r
 import com.vaadin.tests.data.bean.Person;\r