+++ /dev/null
-/* \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
+++ /dev/null
-/* \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
+++ /dev/null
-/* \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
+++ /dev/null
-/* \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
+++ /dev/null
-/* \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
+++ /dev/null
-/* \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
--- /dev/null
+/* \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
--- /dev/null
+/* \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
--- /dev/null
+/* \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
--- /dev/null
+/* \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
--- /dev/null
+/* \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
--- /dev/null
+/* \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
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
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
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
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
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