diff options
Diffstat (limited to 'src/com/vaadin/data/fieldgroup/FieldGroup.java')
-rw-r--r-- | src/com/vaadin/data/fieldgroup/FieldGroup.java | 1954 |
1 files changed, 977 insertions, 977 deletions
diff --git a/src/com/vaadin/data/fieldgroup/FieldGroup.java b/src/com/vaadin/data/fieldgroup/FieldGroup.java index 381f4fef3f..3df19f5bc9 100644 --- a/src/com/vaadin/data/fieldgroup/FieldGroup.java +++ b/src/com/vaadin/data/fieldgroup/FieldGroup.java @@ -1,978 +1,978 @@ -/*
-@VaadinApache2LicenseForJavaFiles@
- */
-package com.vaadin.data.fieldgroup;
-
-import java.io.Serializable;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.logging.Logger;
-
-import com.vaadin.data.Item;
-import com.vaadin.data.Property;
-import com.vaadin.data.Validator.InvalidValueException;
-import com.vaadin.data.util.TransactionalPropertyWrapper;
-import com.vaadin.tools.ReflectTools;
-import com.vaadin.ui.DefaultFieldFactory;
-import com.vaadin.ui.Field;
-import com.vaadin.ui.Form;
-
-/**
- * FieldGroup provides an easy way of binding fields to data and handling
- * commits of these fields.
- * <p>
- * The functionality of FieldGroup is similar to {@link Form} but
- * {@link FieldGroup} does not handle layouts in any way. The typical use case
- * is to create a layout outside the FieldGroup and then use FieldGroup to bind
- * the fields to a data source.
- * </p>
- * <p>
- * {@link FieldGroup} is not a UI component so it cannot be added to a layout.
- * Using the buildAndBind methods {@link FieldGroup} can create fields for you
- * using a FieldGroupFieldFactory but you still have to add them to the correct
- * position in your layout.
- * </p>
- *
- * @author Vaadin Ltd
- * @version @version@
- * @since 7.0
- */
-public class FieldGroup implements Serializable {
-
- private static final Logger logger = Logger.getLogger(FieldGroup.class
- .getName());
-
- private Item itemDataSource;
- private boolean buffered = true;
-
- private boolean enabled = true;
- private boolean readOnly = false;
-
- private HashMap<Object, Field<?>> propertyIdToField = new HashMap<Object, Field<?>>();
- private LinkedHashMap<Field<?>, Object> fieldToPropertyId = new LinkedHashMap<Field<?>, Object>();
- private List<CommitHandler> commitHandlers = new ArrayList<CommitHandler>();
-
- /**
- * The field factory used by builder methods.
- */
- private FieldGroupFieldFactory fieldFactory = new DefaultFieldGroupFieldFactory();
-
- /**
- * Constructs a field binder. Use {@link #setItemDataSource(Item)} to set a
- * data source for the field binder.
- *
- */
- public FieldGroup() {
-
- }
-
- /**
- * Constructs a field binder that uses the given data source.
- *
- * @param itemDataSource
- * The data source to bind the fields to
- */
- public FieldGroup(Item itemDataSource) {
- setItemDataSource(itemDataSource);
- }
-
- /**
- * Updates the item that is used by this FieldBinder. Rebinds all fields to
- * the properties in the new item.
- *
- * @param itemDataSource
- * The new item to use
- */
- public void setItemDataSource(Item itemDataSource) {
- this.itemDataSource = itemDataSource;
-
- for (Field<?> f : fieldToPropertyId.keySet()) {
- bind(f, fieldToPropertyId.get(f));
- }
- }
-
- /**
- * Gets the item used by this FieldBinder. Note that you must call
- * {@link #commit()} for the item to be updated unless buffered mode has
- * been switched off.
- *
- * @see #setBuffered(boolean)
- * @see #commit()
- *
- * @return The item used by this FieldBinder
- */
- public Item getItemDataSource() {
- return itemDataSource;
- }
-
- /**
- * Checks the buffered mode for the bound fields.
- * <p>
- *
- * @see #setBuffered(boolean) for more details on buffered mode
- *
- * @see Field#isBuffered()
- * @return true if buffered mode is on, false otherwise
- *
- */
- public boolean isBuffered() {
- return buffered;
- }
-
- /**
- * Sets the buffered mode for the bound fields.
- * <p>
- * When buffered mode is on the item will not be updated until
- * {@link #commit()} is called. If buffered mode is off the item will be
- * updated once the fields are updated.
- * </p>
- * <p>
- * The default is to use buffered mode.
- * </p>
- *
- * @see Field#setBuffered(boolean)
- * @param buffered
- * true to turn on buffered mode, false otherwise
- */
- public void setBuffered(boolean buffered) {
- if (buffered == this.buffered) {
- return;
- }
-
- this.buffered = buffered;
- for (Field<?> field : getFields()) {
- field.setBuffered(buffered);
- }
- }
-
- /**
- * Returns the enabled status for the fields.
- * <p>
- * Note that this will not accurately represent the enabled status of all
- * fields if you change the enabled status of the fields through some other
- * method than {@link #setEnabled(boolean)}.
- *
- * @return true if the fields are enabled, false otherwise
- */
- public boolean isEnabled() {
- return enabled;
- }
-
- /**
- * Updates the enabled state of all bound fields.
- *
- * @param fieldsEnabled
- * true to enable all bound fields, false to disable them
- */
- public void setEnabled(boolean fieldsEnabled) {
- enabled = fieldsEnabled;
- for (Field<?> field : getFields()) {
- field.setEnabled(fieldsEnabled);
- }
- }
-
- /**
- * Returns the read only status for the fields.
- * <p>
- * Note that this will not accurately represent the read only status of all
- * fields if you change the read only status of the fields through some
- * other method than {@link #setReadOnly(boolean)}.
- *
- * @return true if the fields are set to read only, false otherwise
- */
- public boolean isReadOnly() {
- return readOnly;
- }
-
- /**
- * Updates the read only state of all bound fields.
- *
- * @param fieldsReadOnly
- * true to set all bound fields to read only, false to set them
- * to read write
- */
- public void setReadOnly(boolean fieldsReadOnly) {
- readOnly = fieldsReadOnly;
- }
-
- /**
- * Returns a collection of all fields that have been bound.
- * <p>
- * The fields are not returned in any specific order.
- * </p>
- *
- * @return A collection with all bound Fields
- */
- public Collection<Field<?>> getFields() {
- return fieldToPropertyId.keySet();
- }
-
- /**
- * Binds the field with the given propertyId from the current item. If an
- * item has not been set then the binding is postponed until the item is set
- * using {@link #setItemDataSource(Item)}.
- * <p>
- * This method also adds validators when applicable.
- * </p>
- *
- * @param field
- * The field to bind
- * @param propertyId
- * The propertyId to bind to the field
- * @throws BindException
- * If the property id is already bound to another field by this
- * field binder
- */
- public void bind(Field<?> field, Object propertyId) throws BindException {
- if (propertyIdToField.containsKey(propertyId)
- && propertyIdToField.get(propertyId) != field) {
- throw new BindException("Property id " + propertyId
- + " is already bound to another field");
- }
- fieldToPropertyId.put(field, propertyId);
- propertyIdToField.put(propertyId, field);
- if (itemDataSource == null) {
- // Will be bound when data source is set
- return;
- }
-
- field.setPropertyDataSource(wrapInTransactionalProperty(getItemProperty(propertyId)));
- configureField(field);
- }
-
- private <T> Property.Transactional<T> wrapInTransactionalProperty(
- Property<T> itemProperty) {
- return new TransactionalPropertyWrapper<T>(itemProperty);
- }
-
- /**
- * Gets the property with the given property id from the item.
- *
- * @param propertyId
- * The id if the property to find
- * @return The property with the given id from the item
- * @throws BindException
- * If the property was not found in the item or no item has been
- * set
- */
- protected Property<?> getItemProperty(Object propertyId)
- throws BindException {
- Item item = getItemDataSource();
- if (item == null) {
- throw new BindException("Could not lookup property with id "
- + propertyId + " as no item has been set");
- }
- Property<?> p = item.getItemProperty(propertyId);
- if (p == null) {
- throw new BindException("A property with id " + propertyId
- + " was not found in the item");
- }
- return p;
- }
-
- /**
- * Detaches the field from its property id and removes it from this
- * FieldBinder.
- * <p>
- * Note that the field is not detached from its property data source if it
- * is no longer connected to the same property id it was bound to using this
- * FieldBinder.
- *
- * @param field
- * The field to detach
- * @throws BindException
- * If the field is not bound by this field binder or not bound
- * to the correct property id
- */
- public void unbind(Field<?> field) throws BindException {
- Object propertyId = fieldToPropertyId.get(field);
- if (propertyId == null) {
- throw new BindException(
- "The given field is not part of this FieldBinder");
- }
-
- Property fieldDataSource = field.getPropertyDataSource();
- if (fieldDataSource instanceof TransactionalPropertyWrapper) {
- fieldDataSource = ((TransactionalPropertyWrapper) fieldDataSource)
- .getWrappedProperty();
- }
- if (fieldDataSource == getItemProperty(propertyId)) {
- field.setPropertyDataSource(null);
- }
- fieldToPropertyId.remove(field);
- propertyIdToField.remove(propertyId);
- }
-
- /**
- * Configures a field with the settings set for this FieldBinder.
- * <p>
- * By default this updates the buffered, read only and enabled state of the
- * field. Also adds validators when applicable.
- *
- * @param field
- * The field to update
- */
- protected void configureField(Field<?> field) {
- field.setBuffered(isBuffered());
-
- field.setEnabled(isEnabled());
- field.setReadOnly(isReadOnly());
- }
-
- /**
- * Gets the type of the property with the given property id.
- *
- * @param propertyId
- * The propertyId. Must be find
- * @return The type of the property
- */
- protected Class<?> getPropertyType(Object propertyId) throws BindException {
- if (getItemDataSource() == null) {
- throw new BindException(
- "Property type for '"
- + propertyId
- + "' could not be determined. No item data source has been set.");
- }
- Property<?> p = getItemDataSource().getItemProperty(propertyId);
- if (p == null) {
- throw new BindException(
- "Property type for '"
- + propertyId
- + "' could not be determined. No property with that id was found.");
- }
-
- return p.getType();
- }
-
- /**
- * Returns a collection of all property ids that have been bound to fields.
- * <p>
- * Note that this will return property ids even before the item has been
- * set. In that case it returns the property ids that will be bound once the
- * item is set.
- * </p>
- * <p>
- * No guarantee is given for the order of the property ids
- * </p>
- *
- * @return A collection of bound property ids
- */
- public Collection<Object> getBoundPropertyIds() {
- return Collections.unmodifiableCollection(propertyIdToField.keySet());
- }
-
- /**
- * Returns a collection of all property ids that exist in the item set using
- * {@link #setItemDataSource(Item)} but have not been bound to fields.
- * <p>
- * Will always return an empty collection before an item has been set using
- * {@link #setItemDataSource(Item)}.
- * </p>
- * <p>
- * No guarantee is given for the order of the property ids
- * </p>
- *
- * @return A collection of property ids that have not been bound to fields
- */
- public Collection<Object> getUnboundPropertyIds() {
- if (getItemDataSource() == null) {
- return new ArrayList<Object>();
- }
- List<Object> unboundPropertyIds = new ArrayList<Object>();
- unboundPropertyIds.addAll(getItemDataSource().getItemPropertyIds());
- unboundPropertyIds.removeAll(propertyIdToField.keySet());
- return unboundPropertyIds;
- }
-
- /**
- * Commits all changes done to the bound fields.
- * <p>
- * Calls all {@link CommitHandler}s before and after committing the field
- * changes to the item data source. The whole commit is aborted and state is
- * restored to what it was before commit was called if any
- * {@link CommitHandler} throws a CommitException or there is a problem
- * committing the fields
- *
- * @throws CommitException
- * If the commit was aborted
- */
- public void commit() throws CommitException {
- if (!isBuffered()) {
- // Not using buffered mode, nothing to do
- return;
- }
- for (Field<?> f : fieldToPropertyId.keySet()) {
- ((Property.Transactional<?>) f.getPropertyDataSource())
- .startTransaction();
- }
- try {
- firePreCommitEvent();
- // Commit the field values to the properties
- for (Field<?> f : fieldToPropertyId.keySet()) {
- f.commit();
- }
- firePostCommitEvent();
-
- // Commit the properties
- for (Field<?> f : fieldToPropertyId.keySet()) {
- ((Property.Transactional<?>) f.getPropertyDataSource())
- .commit();
- }
-
- } catch (Exception e) {
- for (Field<?> f : fieldToPropertyId.keySet()) {
- try {
- ((Property.Transactional<?>) f.getPropertyDataSource())
- .rollback();
- } catch (Exception rollbackException) {
- // FIXME: What to do ?
- }
- }
-
- throw new CommitException("Commit failed", e);
- }
-
- }
-
- /**
- * Sends a preCommit event to all registered commit handlers
- *
- * @throws CommitException
- * If the commit should be aborted
- */
- private void firePreCommitEvent() throws CommitException {
- CommitHandler[] handlers = commitHandlers
- .toArray(new CommitHandler[commitHandlers.size()]);
-
- for (CommitHandler handler : handlers) {
- handler.preCommit(new CommitEvent(this));
- }
- }
-
- /**
- * Sends a postCommit event to all registered commit handlers
- *
- * @throws CommitException
- * If the commit should be aborted
- */
- private void firePostCommitEvent() throws CommitException {
- CommitHandler[] handlers = commitHandlers
- .toArray(new CommitHandler[commitHandlers.size()]);
-
- for (CommitHandler handler : handlers) {
- handler.postCommit(new CommitEvent(this));
- }
- }
-
- /**
- * Discards all changes done to the bound fields.
- * <p>
- * Only has effect if buffered mode is used.
- *
- */
- public void discard() {
- for (Field<?> f : fieldToPropertyId.keySet()) {
- try {
- f.discard();
- } catch (Exception e) {
- // TODO: handle exception
- // What can we do if discard fails other than try to discard all
- // other fields?
- }
- }
- }
-
- /**
- * Returns the field that is bound to the given property id
- *
- * @param propertyId
- * The property id to use to lookup the field
- * @return The field that is bound to the property id or null if no field is
- * bound to that property id
- */
- public Field<?> getField(Object propertyId) {
- return propertyIdToField.get(propertyId);
- }
-
- /**
- * Returns the property id that is bound to the given field
- *
- * @param field
- * The field to use to lookup the property id
- * @return The property id that is bound to the field or null if the field
- * is not bound to any property id by this FieldBinder
- */
- public Object getPropertyId(Field<?> field) {
- return fieldToPropertyId.get(field);
- }
-
- /**
- * Adds a commit handler.
- * <p>
- * The commit handler is called before the field values are committed to the
- * item ( {@link CommitHandler#preCommit(CommitEvent)}) and after the item
- * has been updated ({@link CommitHandler#postCommit(CommitEvent)}). If a
- * {@link CommitHandler} throws a CommitException the whole commit is
- * aborted and the fields retain their old values.
- *
- * @param commitHandler
- * The commit handler to add
- */
- public void addCommitHandler(CommitHandler commitHandler) {
- commitHandlers.add(commitHandler);
- }
-
- /**
- * Removes the given commit handler.
- *
- * @see #addCommitHandler(CommitHandler)
- *
- * @param commitHandler
- * The commit handler to remove
- */
- public void removeCommitHandler(CommitHandler commitHandler) {
- commitHandlers.remove(commitHandler);
- }
-
- /**
- * Returns a list of all commit handlers for this {@link FieldGroup}.
- * <p>
- * Use {@link #addCommitHandler(CommitHandler)} and
- * {@link #removeCommitHandler(CommitHandler)} to register or unregister a
- * commit handler.
- *
- * @return A collection of commit handlers
- */
- protected Collection<CommitHandler> getCommitHandlers() {
- return Collections.unmodifiableCollection(commitHandlers);
- }
-
- /**
- * CommitHandlers are used by {@link FieldGroup#commit()} as part of the
- * commit transactions. CommitHandlers can perform custom operations as part
- * of the commit and cause the commit to be aborted by throwing a
- * {@link CommitException}.
- */
- public interface CommitHandler extends Serializable {
- /**
- * Called before changes are committed to the field and the item is
- * updated.
- * <p>
- * Throw a {@link CommitException} to abort the commit.
- *
- * @param commitEvent
- * An event containing information regarding the commit
- * @throws CommitException
- * if the commit should be aborted
- */
- public void preCommit(CommitEvent commitEvent) throws CommitException;
-
- /**
- * Called after changes are committed to the fields and the item is
- * updated..
- * <p>
- * Throw a {@link CommitException} to abort the commit.
- *
- * @param commitEvent
- * An event containing information regarding the commit
- * @throws CommitException
- * if the commit should be aborted
- */
- public void postCommit(CommitEvent commitEvent) throws CommitException;
- }
-
- /**
- * FIXME javadoc
- *
- */
- public static class CommitEvent implements Serializable {
- private FieldGroup fieldBinder;
-
- private CommitEvent(FieldGroup fieldBinder) {
- this.fieldBinder = fieldBinder;
- }
-
- /**
- * Returns the field binder that this commit relates to
- *
- * @return The FieldBinder that is being committed.
- */
- public FieldGroup getFieldBinder() {
- return fieldBinder;
- }
-
- }
-
- /**
- * Checks the validity of the bound fields.
- * <p>
- * Call the {@link Field#validate()} for the fields to get the individual
- * error messages.
- *
- * @return true if all bound fields are valid, false otherwise.
- */
- public boolean isValid() {
- try {
- for (Field<?> field : getFields()) {
- field.validate();
- }
- return true;
- } catch (InvalidValueException e) {
- return false;
- }
- }
-
- /**
- * Checks if any bound field has been modified.
- *
- * @return true if at least on field has been modified, false otherwise
- */
- public boolean isModified() {
- for (Field<?> field : getFields()) {
- if (field.isModified()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Gets the field factory for the {@link FieldGroup}. The field factory is
- * only used when {@link FieldGroup} creates a new field.
- *
- * @return The field factory in use
- *
- */
- public FieldGroupFieldFactory getFieldFactory() {
- return fieldFactory;
- }
-
- /**
- * Sets the field factory for the {@link FieldGroup}. The field factory is
- * only used when {@link FieldGroup} creates a new field.
- *
- * @param fieldFactory
- * The field factory to use
- */
- public void setFieldFactory(FieldGroupFieldFactory fieldFactory) {
- this.fieldFactory = fieldFactory;
- }
-
- /**
- * Binds member fields found in the given object.
- * <p>
- * This method processes all (Java) member fields whose type extends
- * {@link Field} and that can be mapped to a property id. Property id
- * mapping is done based on the field name or on a @{@link PropertyId}
- * annotation on the field. All non-null fields for which a property id can
- * be determined are bound to the property id.
- * </p>
- * <p>
- * For example:
- *
- * <pre>
- * public class MyForm extends VerticalLayout {
- * private TextField firstName = new TextField("First name");
- * @PropertyId("last")
- * private TextField lastName = new TextField("Last name");
- * private TextField age = new TextField("Age"); ... }
- *
- * MyForm myForm = new MyForm();
- * ...
- * fieldGroup.bindMemberFields(myForm);
- * </pre>
- *
- * </p>
- * This binds the firstName TextField to a "firstName" property in the item,
- * lastName TextField to a "last" property and the age TextField to a "age"
- * property.
- *
- * @param objectWithMemberFields
- * The object that contains (Java) member fields to bind
- * @throws BindException
- * If there is a problem binding a field
- */
- public void bindMemberFields(Object objectWithMemberFields)
- throws BindException {
- buildAndBindMemberFields(objectWithMemberFields, false);
- }
-
- /**
- * Binds member fields found in the given object and builds member fields
- * that have not been initialized.
- * <p>
- * This method processes all (Java) member fields whose type extends
- * {@link Field} and that can be mapped to a property id. Property id
- * mapping is done based on the field name or on a @{@link PropertyId}
- * annotation on the field. Fields that are not initialized (null) are built
- * using the field factory. All non-null fields for which a property id can
- * be determined are bound to the property id.
- * </p>
- * <p>
- * For example:
- *
- * <pre>
- * public class MyForm extends VerticalLayout {
- * private TextField firstName = new TextField("First name");
- * @PropertyId("last")
- * private TextField lastName = new TextField("Last name");
- * private TextField age;
- *
- * MyForm myForm = new MyForm();
- * ...
- * fieldGroup.buildAndBindMemberFields(myForm);
- * </pre>
- *
- * </p>
- * <p>
- * This binds the firstName TextField to a "firstName" property in the item,
- * lastName TextField to a "last" property and builds an age TextField using
- * the field factory and then binds it to the "age" property.
- * </p>
- *
- * @param objectWithMemberFields
- * The object that contains (Java) member fields to build and
- * bind
- * @throws BindException
- * If there is a problem binding or building a field
- */
- public void buildAndBindMemberFields(Object objectWithMemberFields)
- throws BindException {
- buildAndBindMemberFields(objectWithMemberFields, true);
- }
-
- /**
- * Binds member fields found in the given object and optionally builds
- * member fields that have not been initialized.
- * <p>
- * This method processes all (Java) member fields whose type extends
- * {@link Field} and that can be mapped to a property id. Property id
- * mapping is done based on the field name or on a @{@link PropertyId}
- * annotation on the field. Fields that are not initialized (null) are built
- * using the field factory is buildFields is true. All non-null fields for
- * which a property id can be determined are bound to the property id.
- * </p>
- *
- * @param objectWithMemberFields
- * The object that contains (Java) member fields to build and
- * bind
- * @throws BindException
- * If there is a problem binding or building a field
- */
- protected void buildAndBindMemberFields(Object objectWithMemberFields,
- boolean buildFields) throws BindException {
- Class<?> objectClass = objectWithMemberFields.getClass();
-
- for (java.lang.reflect.Field memberField : objectClass
- .getDeclaredFields()) {
-
- if (!Field.class.isAssignableFrom(memberField.getType())) {
- // Process next field
- continue;
- }
-
- PropertyId propertyIdAnnotation = memberField
- .getAnnotation(PropertyId.class);
-
- Class<? extends Field> fieldType = (Class<? extends Field>) memberField
- .getType();
-
- Object propertyId = null;
- if (propertyIdAnnotation != null) {
- // @PropertyId(propertyId) always overrides property id
- propertyId = propertyIdAnnotation.value();
- } else {
- propertyId = memberField.getName();
- }
-
- // Ensure that the property id exists
- Class<?> propertyType;
-
- try {
- propertyType = getPropertyType(propertyId);
- } catch (BindException e) {
- // Property id was not found, skip this field
- continue;
- }
-
- Field<?> field;
- try {
- // Get the field from the object
- field = (Field<?>) ReflectTools.getJavaFieldValue(
- objectWithMemberFields, memberField);
- } catch (Exception e) {
- // If we cannot determine the value, just skip the field and try
- // the next one
- continue;
- }
-
- if (field == null && buildFields) {
- Caption captionAnnotation = memberField
- .getAnnotation(Caption.class);
- String caption;
- if (captionAnnotation != null) {
- caption = captionAnnotation.value();
- } else {
- caption = DefaultFieldFactory
- .createCaptionByPropertyId(propertyId);
- }
-
- // Create the component (Field)
- field = build(caption, propertyType, fieldType);
-
- // Store it in the field
- try {
- ReflectTools.setJavaFieldValue(objectWithMemberFields,
- memberField, field);
- } catch (IllegalArgumentException e) {
- throw new BindException("Could not assign value to field '"
- + memberField.getName() + "'", e);
- } catch (IllegalAccessException e) {
- throw new BindException("Could not assign value to field '"
- + memberField.getName() + "'", e);
- } catch (InvocationTargetException e) {
- throw new BindException("Could not assign value to field '"
- + memberField.getName() + "'", e);
- }
- }
-
- if (field != null) {
- // Bind it to the property id
- bind(field, propertyId);
- }
- }
- }
-
- public static class CommitException extends Exception {
-
- public CommitException() {
- super();
- // TODO Auto-generated constructor stub
- }
-
- public CommitException(String message, Throwable cause) {
- super(message, cause);
- // TODO Auto-generated constructor stub
- }
-
- public CommitException(String message) {
- super(message);
- // TODO Auto-generated constructor stub
- }
-
- public CommitException(Throwable cause) {
- super(cause);
- // TODO Auto-generated constructor stub
- }
-
- }
-
- public static class BindException extends RuntimeException {
-
- public BindException(String message) {
- super(message);
- }
-
- public BindException(String message, Throwable t) {
- super(message, t);
- }
-
- }
-
- /**
- * Builds a field and binds it to the given property id using the field
- * binder.
- *
- * @param propertyId
- * The property id to bind to. Must be present in the field
- * finder.
- * @throws BindException
- * If there is a problem while building or binding
- * @return The created and bound field
- */
- public Field<?> buildAndBind(Object propertyId) throws BindException {
- String caption = DefaultFieldFactory
- .createCaptionByPropertyId(propertyId);
- return buildAndBind(caption, propertyId);
- }
-
- /**
- * Builds a field using the given caption and binds it to the given property
- * id using the field binder.
- *
- * @param caption
- * The caption for the field
- * @param propertyId
- * The property id to bind to. Must be present in the field
- * finder.
- * @throws BindException
- * If there is a problem while building or binding
- * @return The created and bound field. Can be any type of {@link Field}.
- */
- public Field<?> buildAndBind(String caption, Object propertyId)
- throws BindException {
- Class<?> type = getPropertyType(propertyId);
- return buildAndBind(caption, propertyId, Field.class);
-
- }
-
- /**
- * Builds a field using the given caption and binds it to the given property
- * id using the field binder. Ensures the new field is of the given type.
- *
- * @param caption
- * The caption for the field
- * @param propertyId
- * The property id to bind to. Must be present in the field
- * finder.
- * @throws BindException
- * If the field could not be created
- * @return The created and bound field. Can be any type of {@link Field}.
- */
-
- public <T extends Field> T buildAndBind(String caption, Object propertyId,
- Class<T> fieldType) throws BindException {
- Class<?> type = getPropertyType(propertyId);
-
- T field = build(caption, type, fieldType);
- bind(field, propertyId);
-
- return field;
- }
-
- /**
- * Creates a field based on the given data type.
- * <p>
- * The data type is the type that we want to edit using the field. The field
- * type is the type of field we want to create, can be {@link Field} if any
- * Field is good.
- * </p>
- *
- * @param caption
- * The caption for the new field
- * @param dataType
- * The data model type that we want to edit using the field
- * @param fieldType
- * The type of field that we want to create
- * @return A Field capable of editing the given type
- * @throws BindException
- * If the field could not be created
- */
- protected <T extends Field> T build(String caption, Class<?> dataType,
- Class<T> fieldType) throws BindException {
- T field = getFieldFactory().createField(dataType, fieldType);
- if (field == null) {
- throw new BindException("Unable to build a field of type "
- + fieldType.getName() + " for editing "
- + dataType.getName());
- }
-
- field.setCaption(caption);
- return field;
- }
+/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.fieldgroup; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.logging.Logger; + +import com.vaadin.data.Item; +import com.vaadin.data.Property; +import com.vaadin.data.Validator.InvalidValueException; +import com.vaadin.data.util.TransactionalPropertyWrapper; +import com.vaadin.tools.ReflectTools; +import com.vaadin.ui.DefaultFieldFactory; +import com.vaadin.ui.Field; +import com.vaadin.ui.Form; + +/** + * FieldGroup provides an easy way of binding fields to data and handling + * commits of these fields. + * <p> + * The functionality of FieldGroup is similar to {@link Form} but + * {@link FieldGroup} does not handle layouts in any way. The typical use case + * is to create a layout outside the FieldGroup and then use FieldGroup to bind + * the fields to a data source. + * </p> + * <p> + * {@link FieldGroup} is not a UI component so it cannot be added to a layout. + * Using the buildAndBind methods {@link FieldGroup} can create fields for you + * using a FieldGroupFieldFactory but you still have to add them to the correct + * position in your layout. + * </p> + * + * @author Vaadin Ltd + * @version @version@ + * @since 7.0 + */ +public class FieldGroup implements Serializable { + + private static final Logger logger = Logger.getLogger(FieldGroup.class + .getName()); + + private Item itemDataSource; + private boolean buffered = true; + + private boolean enabled = true; + private boolean readOnly = false; + + private HashMap<Object, Field<?>> propertyIdToField = new HashMap<Object, Field<?>>(); + private LinkedHashMap<Field<?>, Object> fieldToPropertyId = new LinkedHashMap<Field<?>, Object>(); + private List<CommitHandler> commitHandlers = new ArrayList<CommitHandler>(); + + /** + * The field factory used by builder methods. + */ + private FieldGroupFieldFactory fieldFactory = new DefaultFieldGroupFieldFactory(); + + /** + * Constructs a field binder. Use {@link #setItemDataSource(Item)} to set a + * data source for the field binder. + * + */ + public FieldGroup() { + + } + + /** + * Constructs a field binder that uses the given data source. + * + * @param itemDataSource + * The data source to bind the fields to + */ + public FieldGroup(Item itemDataSource) { + setItemDataSource(itemDataSource); + } + + /** + * Updates the item that is used by this FieldBinder. Rebinds all fields to + * the properties in the new item. + * + * @param itemDataSource + * The new item to use + */ + public void setItemDataSource(Item itemDataSource) { + this.itemDataSource = itemDataSource; + + for (Field<?> f : fieldToPropertyId.keySet()) { + bind(f, fieldToPropertyId.get(f)); + } + } + + /** + * Gets the item used by this FieldBinder. Note that you must call + * {@link #commit()} for the item to be updated unless buffered mode has + * been switched off. + * + * @see #setBuffered(boolean) + * @see #commit() + * + * @return The item used by this FieldBinder + */ + public Item getItemDataSource() { + return itemDataSource; + } + + /** + * Checks the buffered mode for the bound fields. + * <p> + * + * @see #setBuffered(boolean) for more details on buffered mode + * + * @see Field#isBuffered() + * @return true if buffered mode is on, false otherwise + * + */ + public boolean isBuffered() { + return buffered; + } + + /** + * Sets the buffered mode for the bound fields. + * <p> + * When buffered mode is on the item will not be updated until + * {@link #commit()} is called. If buffered mode is off the item will be + * updated once the fields are updated. + * </p> + * <p> + * The default is to use buffered mode. + * </p> + * + * @see Field#setBuffered(boolean) + * @param buffered + * true to turn on buffered mode, false otherwise + */ + public void setBuffered(boolean buffered) { + if (buffered == this.buffered) { + return; + } + + this.buffered = buffered; + for (Field<?> field : getFields()) { + field.setBuffered(buffered); + } + } + + /** + * Returns the enabled status for the fields. + * <p> + * Note that this will not accurately represent the enabled status of all + * fields if you change the enabled status of the fields through some other + * method than {@link #setEnabled(boolean)}. + * + * @return true if the fields are enabled, false otherwise + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Updates the enabled state of all bound fields. + * + * @param fieldsEnabled + * true to enable all bound fields, false to disable them + */ + public void setEnabled(boolean fieldsEnabled) { + enabled = fieldsEnabled; + for (Field<?> field : getFields()) { + field.setEnabled(fieldsEnabled); + } + } + + /** + * Returns the read only status for the fields. + * <p> + * Note that this will not accurately represent the read only status of all + * fields if you change the read only status of the fields through some + * other method than {@link #setReadOnly(boolean)}. + * + * @return true if the fields are set to read only, false otherwise + */ + public boolean isReadOnly() { + return readOnly; + } + + /** + * Updates the read only state of all bound fields. + * + * @param fieldsReadOnly + * true to set all bound fields to read only, false to set them + * to read write + */ + public void setReadOnly(boolean fieldsReadOnly) { + readOnly = fieldsReadOnly; + } + + /** + * Returns a collection of all fields that have been bound. + * <p> + * The fields are not returned in any specific order. + * </p> + * + * @return A collection with all bound Fields + */ + public Collection<Field<?>> getFields() { + return fieldToPropertyId.keySet(); + } + + /** + * Binds the field with the given propertyId from the current item. If an + * item has not been set then the binding is postponed until the item is set + * using {@link #setItemDataSource(Item)}. + * <p> + * This method also adds validators when applicable. + * </p> + * + * @param field + * The field to bind + * @param propertyId + * The propertyId to bind to the field + * @throws BindException + * If the property id is already bound to another field by this + * field binder + */ + public void bind(Field<?> field, Object propertyId) throws BindException { + if (propertyIdToField.containsKey(propertyId) + && propertyIdToField.get(propertyId) != field) { + throw new BindException("Property id " + propertyId + + " is already bound to another field"); + } + fieldToPropertyId.put(field, propertyId); + propertyIdToField.put(propertyId, field); + if (itemDataSource == null) { + // Will be bound when data source is set + return; + } + + field.setPropertyDataSource(wrapInTransactionalProperty(getItemProperty(propertyId))); + configureField(field); + } + + private <T> Property.Transactional<T> wrapInTransactionalProperty( + Property<T> itemProperty) { + return new TransactionalPropertyWrapper<T>(itemProperty); + } + + /** + * Gets the property with the given property id from the item. + * + * @param propertyId + * The id if the property to find + * @return The property with the given id from the item + * @throws BindException + * If the property was not found in the item or no item has been + * set + */ + protected Property<?> getItemProperty(Object propertyId) + throws BindException { + Item item = getItemDataSource(); + if (item == null) { + throw new BindException("Could not lookup property with id " + + propertyId + " as no item has been set"); + } + Property<?> p = item.getItemProperty(propertyId); + if (p == null) { + throw new BindException("A property with id " + propertyId + + " was not found in the item"); + } + return p; + } + + /** + * Detaches the field from its property id and removes it from this + * FieldBinder. + * <p> + * Note that the field is not detached from its property data source if it + * is no longer connected to the same property id it was bound to using this + * FieldBinder. + * + * @param field + * The field to detach + * @throws BindException + * If the field is not bound by this field binder or not bound + * to the correct property id + */ + public void unbind(Field<?> field) throws BindException { + Object propertyId = fieldToPropertyId.get(field); + if (propertyId == null) { + throw new BindException( + "The given field is not part of this FieldBinder"); + } + + Property fieldDataSource = field.getPropertyDataSource(); + if (fieldDataSource instanceof TransactionalPropertyWrapper) { + fieldDataSource = ((TransactionalPropertyWrapper) fieldDataSource) + .getWrappedProperty(); + } + if (fieldDataSource == getItemProperty(propertyId)) { + field.setPropertyDataSource(null); + } + fieldToPropertyId.remove(field); + propertyIdToField.remove(propertyId); + } + + /** + * Configures a field with the settings set for this FieldBinder. + * <p> + * By default this updates the buffered, read only and enabled state of the + * field. Also adds validators when applicable. + * + * @param field + * The field to update + */ + protected void configureField(Field<?> field) { + field.setBuffered(isBuffered()); + + field.setEnabled(isEnabled()); + field.setReadOnly(isReadOnly()); + } + + /** + * Gets the type of the property with the given property id. + * + * @param propertyId + * The propertyId. Must be find + * @return The type of the property + */ + protected Class<?> getPropertyType(Object propertyId) throws BindException { + if (getItemDataSource() == null) { + throw new BindException( + "Property type for '" + + propertyId + + "' could not be determined. No item data source has been set."); + } + Property<?> p = getItemDataSource().getItemProperty(propertyId); + if (p == null) { + throw new BindException( + "Property type for '" + + propertyId + + "' could not be determined. No property with that id was found."); + } + + return p.getType(); + } + + /** + * Returns a collection of all property ids that have been bound to fields. + * <p> + * Note that this will return property ids even before the item has been + * set. In that case it returns the property ids that will be bound once the + * item is set. + * </p> + * <p> + * No guarantee is given for the order of the property ids + * </p> + * + * @return A collection of bound property ids + */ + public Collection<Object> getBoundPropertyIds() { + return Collections.unmodifiableCollection(propertyIdToField.keySet()); + } + + /** + * Returns a collection of all property ids that exist in the item set using + * {@link #setItemDataSource(Item)} but have not been bound to fields. + * <p> + * Will always return an empty collection before an item has been set using + * {@link #setItemDataSource(Item)}. + * </p> + * <p> + * No guarantee is given for the order of the property ids + * </p> + * + * @return A collection of property ids that have not been bound to fields + */ + public Collection<Object> getUnboundPropertyIds() { + if (getItemDataSource() == null) { + return new ArrayList<Object>(); + } + List<Object> unboundPropertyIds = new ArrayList<Object>(); + unboundPropertyIds.addAll(getItemDataSource().getItemPropertyIds()); + unboundPropertyIds.removeAll(propertyIdToField.keySet()); + return unboundPropertyIds; + } + + /** + * Commits all changes done to the bound fields. + * <p> + * Calls all {@link CommitHandler}s before and after committing the field + * changes to the item data source. The whole commit is aborted and state is + * restored to what it was before commit was called if any + * {@link CommitHandler} throws a CommitException or there is a problem + * committing the fields + * + * @throws CommitException + * If the commit was aborted + */ + public void commit() throws CommitException { + if (!isBuffered()) { + // Not using buffered mode, nothing to do + return; + } + for (Field<?> f : fieldToPropertyId.keySet()) { + ((Property.Transactional<?>) f.getPropertyDataSource()) + .startTransaction(); + } + try { + firePreCommitEvent(); + // Commit the field values to the properties + for (Field<?> f : fieldToPropertyId.keySet()) { + f.commit(); + } + firePostCommitEvent(); + + // Commit the properties + for (Field<?> f : fieldToPropertyId.keySet()) { + ((Property.Transactional<?>) f.getPropertyDataSource()) + .commit(); + } + + } catch (Exception e) { + for (Field<?> f : fieldToPropertyId.keySet()) { + try { + ((Property.Transactional<?>) f.getPropertyDataSource()) + .rollback(); + } catch (Exception rollbackException) { + // FIXME: What to do ? + } + } + + throw new CommitException("Commit failed", e); + } + + } + + /** + * Sends a preCommit event to all registered commit handlers + * + * @throws CommitException + * If the commit should be aborted + */ + private void firePreCommitEvent() throws CommitException { + CommitHandler[] handlers = commitHandlers + .toArray(new CommitHandler[commitHandlers.size()]); + + for (CommitHandler handler : handlers) { + handler.preCommit(new CommitEvent(this)); + } + } + + /** + * Sends a postCommit event to all registered commit handlers + * + * @throws CommitException + * If the commit should be aborted + */ + private void firePostCommitEvent() throws CommitException { + CommitHandler[] handlers = commitHandlers + .toArray(new CommitHandler[commitHandlers.size()]); + + for (CommitHandler handler : handlers) { + handler.postCommit(new CommitEvent(this)); + } + } + + /** + * Discards all changes done to the bound fields. + * <p> + * Only has effect if buffered mode is used. + * + */ + public void discard() { + for (Field<?> f : fieldToPropertyId.keySet()) { + try { + f.discard(); + } catch (Exception e) { + // TODO: handle exception + // What can we do if discard fails other than try to discard all + // other fields? + } + } + } + + /** + * Returns the field that is bound to the given property id + * + * @param propertyId + * The property id to use to lookup the field + * @return The field that is bound to the property id or null if no field is + * bound to that property id + */ + public Field<?> getField(Object propertyId) { + return propertyIdToField.get(propertyId); + } + + /** + * Returns the property id that is bound to the given field + * + * @param field + * The field to use to lookup the property id + * @return The property id that is bound to the field or null if the field + * is not bound to any property id by this FieldBinder + */ + public Object getPropertyId(Field<?> field) { + return fieldToPropertyId.get(field); + } + + /** + * Adds a commit handler. + * <p> + * The commit handler is called before the field values are committed to the + * item ( {@link CommitHandler#preCommit(CommitEvent)}) and after the item + * has been updated ({@link CommitHandler#postCommit(CommitEvent)}). If a + * {@link CommitHandler} throws a CommitException the whole commit is + * aborted and the fields retain their old values. + * + * @param commitHandler + * The commit handler to add + */ + public void addCommitHandler(CommitHandler commitHandler) { + commitHandlers.add(commitHandler); + } + + /** + * Removes the given commit handler. + * + * @see #addCommitHandler(CommitHandler) + * + * @param commitHandler + * The commit handler to remove + */ + public void removeCommitHandler(CommitHandler commitHandler) { + commitHandlers.remove(commitHandler); + } + + /** + * Returns a list of all commit handlers for this {@link FieldGroup}. + * <p> + * Use {@link #addCommitHandler(CommitHandler)} and + * {@link #removeCommitHandler(CommitHandler)} to register or unregister a + * commit handler. + * + * @return A collection of commit handlers + */ + protected Collection<CommitHandler> getCommitHandlers() { + return Collections.unmodifiableCollection(commitHandlers); + } + + /** + * CommitHandlers are used by {@link FieldGroup#commit()} as part of the + * commit transactions. CommitHandlers can perform custom operations as part + * of the commit and cause the commit to be aborted by throwing a + * {@link CommitException}. + */ + public interface CommitHandler extends Serializable { + /** + * Called before changes are committed to the field and the item is + * updated. + * <p> + * Throw a {@link CommitException} to abort the commit. + * + * @param commitEvent + * An event containing information regarding the commit + * @throws CommitException + * if the commit should be aborted + */ + public void preCommit(CommitEvent commitEvent) throws CommitException; + + /** + * Called after changes are committed to the fields and the item is + * updated.. + * <p> + * Throw a {@link CommitException} to abort the commit. + * + * @param commitEvent + * An event containing information regarding the commit + * @throws CommitException + * if the commit should be aborted + */ + public void postCommit(CommitEvent commitEvent) throws CommitException; + } + + /** + * FIXME javadoc + * + */ + public static class CommitEvent implements Serializable { + private FieldGroup fieldBinder; + + private CommitEvent(FieldGroup fieldBinder) { + this.fieldBinder = fieldBinder; + } + + /** + * Returns the field binder that this commit relates to + * + * @return The FieldBinder that is being committed. + */ + public FieldGroup getFieldBinder() { + return fieldBinder; + } + + } + + /** + * Checks the validity of the bound fields. + * <p> + * Call the {@link Field#validate()} for the fields to get the individual + * error messages. + * + * @return true if all bound fields are valid, false otherwise. + */ + public boolean isValid() { + try { + for (Field<?> field : getFields()) { + field.validate(); + } + return true; + } catch (InvalidValueException e) { + return false; + } + } + + /** + * Checks if any bound field has been modified. + * + * @return true if at least on field has been modified, false otherwise + */ + public boolean isModified() { + for (Field<?> field : getFields()) { + if (field.isModified()) { + return true; + } + } + return false; + } + + /** + * Gets the field factory for the {@link FieldGroup}. The field factory is + * only used when {@link FieldGroup} creates a new field. + * + * @return The field factory in use + * + */ + public FieldGroupFieldFactory getFieldFactory() { + return fieldFactory; + } + + /** + * Sets the field factory for the {@link FieldGroup}. The field factory is + * only used when {@link FieldGroup} creates a new field. + * + * @param fieldFactory + * The field factory to use + */ + public void setFieldFactory(FieldGroupFieldFactory fieldFactory) { + this.fieldFactory = fieldFactory; + } + + /** + * Binds member fields found in the given object. + * <p> + * This method processes all (Java) member fields whose type extends + * {@link Field} and that can be mapped to a property id. Property id + * mapping is done based on the field name or on a @{@link PropertyId} + * annotation on the field. All non-null fields for which a property id can + * be determined are bound to the property id. + * </p> + * <p> + * For example: + * + * <pre> + * public class MyForm extends VerticalLayout { + * private TextField firstName = new TextField("First name"); + * @PropertyId("last") + * private TextField lastName = new TextField("Last name"); + * private TextField age = new TextField("Age"); ... } + * + * MyForm myForm = new MyForm(); + * ... + * fieldGroup.bindMemberFields(myForm); + * </pre> + * + * </p> + * This binds the firstName TextField to a "firstName" property in the item, + * lastName TextField to a "last" property and the age TextField to a "age" + * property. + * + * @param objectWithMemberFields + * The object that contains (Java) member fields to bind + * @throws BindException + * If there is a problem binding a field + */ + public void bindMemberFields(Object objectWithMemberFields) + throws BindException { + buildAndBindMemberFields(objectWithMemberFields, false); + } + + /** + * Binds member fields found in the given object and builds member fields + * that have not been initialized. + * <p> + * This method processes all (Java) member fields whose type extends + * {@link Field} and that can be mapped to a property id. Property id + * mapping is done based on the field name or on a @{@link PropertyId} + * annotation on the field. Fields that are not initialized (null) are built + * using the field factory. All non-null fields for which a property id can + * be determined are bound to the property id. + * </p> + * <p> + * For example: + * + * <pre> + * public class MyForm extends VerticalLayout { + * private TextField firstName = new TextField("First name"); + * @PropertyId("last") + * private TextField lastName = new TextField("Last name"); + * private TextField age; + * + * MyForm myForm = new MyForm(); + * ... + * fieldGroup.buildAndBindMemberFields(myForm); + * </pre> + * + * </p> + * <p> + * This binds the firstName TextField to a "firstName" property in the item, + * lastName TextField to a "last" property and builds an age TextField using + * the field factory and then binds it to the "age" property. + * </p> + * + * @param objectWithMemberFields + * The object that contains (Java) member fields to build and + * bind + * @throws BindException + * If there is a problem binding or building a field + */ + public void buildAndBindMemberFields(Object objectWithMemberFields) + throws BindException { + buildAndBindMemberFields(objectWithMemberFields, true); + } + + /** + * Binds member fields found in the given object and optionally builds + * member fields that have not been initialized. + * <p> + * This method processes all (Java) member fields whose type extends + * {@link Field} and that can be mapped to a property id. Property id + * mapping is done based on the field name or on a @{@link PropertyId} + * annotation on the field. Fields that are not initialized (null) are built + * using the field factory is buildFields is true. All non-null fields for + * which a property id can be determined are bound to the property id. + * </p> + * + * @param objectWithMemberFields + * The object that contains (Java) member fields to build and + * bind + * @throws BindException + * If there is a problem binding or building a field + */ + protected void buildAndBindMemberFields(Object objectWithMemberFields, + boolean buildFields) throws BindException { + Class<?> objectClass = objectWithMemberFields.getClass(); + + for (java.lang.reflect.Field memberField : objectClass + .getDeclaredFields()) { + + if (!Field.class.isAssignableFrom(memberField.getType())) { + // Process next field + continue; + } + + PropertyId propertyIdAnnotation = memberField + .getAnnotation(PropertyId.class); + + Class<? extends Field> fieldType = (Class<? extends Field>) memberField + .getType(); + + Object propertyId = null; + if (propertyIdAnnotation != null) { + // @PropertyId(propertyId) always overrides property id + propertyId = propertyIdAnnotation.value(); + } else { + propertyId = memberField.getName(); + } + + // Ensure that the property id exists + Class<?> propertyType; + + try { + propertyType = getPropertyType(propertyId); + } catch (BindException e) { + // Property id was not found, skip this field + continue; + } + + Field<?> field; + try { + // Get the field from the object + field = (Field<?>) ReflectTools.getJavaFieldValue( + objectWithMemberFields, memberField); + } catch (Exception e) { + // If we cannot determine the value, just skip the field and try + // the next one + continue; + } + + if (field == null && buildFields) { + Caption captionAnnotation = memberField + .getAnnotation(Caption.class); + String caption; + if (captionAnnotation != null) { + caption = captionAnnotation.value(); + } else { + caption = DefaultFieldFactory + .createCaptionByPropertyId(propertyId); + } + + // Create the component (Field) + field = build(caption, propertyType, fieldType); + + // Store it in the field + try { + ReflectTools.setJavaFieldValue(objectWithMemberFields, + memberField, field); + } catch (IllegalArgumentException e) { + throw new BindException("Could not assign value to field '" + + memberField.getName() + "'", e); + } catch (IllegalAccessException e) { + throw new BindException("Could not assign value to field '" + + memberField.getName() + "'", e); + } catch (InvocationTargetException e) { + throw new BindException("Could not assign value to field '" + + memberField.getName() + "'", e); + } + } + + if (field != null) { + // Bind it to the property id + bind(field, propertyId); + } + } + } + + public static class CommitException extends Exception { + + public CommitException() { + super(); + // TODO Auto-generated constructor stub + } + + public CommitException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public CommitException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public CommitException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + } + + public static class BindException extends RuntimeException { + + public BindException(String message) { + super(message); + } + + public BindException(String message, Throwable t) { + super(message, t); + } + + } + + /** + * Builds a field and binds it to the given property id using the field + * binder. + * + * @param propertyId + * The property id to bind to. Must be present in the field + * finder. + * @throws BindException + * If there is a problem while building or binding + * @return The created and bound field + */ + public Field<?> buildAndBind(Object propertyId) throws BindException { + String caption = DefaultFieldFactory + .createCaptionByPropertyId(propertyId); + return buildAndBind(caption, propertyId); + } + + /** + * Builds a field using the given caption and binds it to the given property + * id using the field binder. + * + * @param caption + * The caption for the field + * @param propertyId + * The property id to bind to. Must be present in the field + * finder. + * @throws BindException + * If there is a problem while building or binding + * @return The created and bound field. Can be any type of {@link Field}. + */ + public Field<?> buildAndBind(String caption, Object propertyId) + throws BindException { + Class<?> type = getPropertyType(propertyId); + return buildAndBind(caption, propertyId, Field.class); + + } + + /** + * Builds a field using the given caption and binds it to the given property + * id using the field binder. Ensures the new field is of the given type. + * + * @param caption + * The caption for the field + * @param propertyId + * The property id to bind to. Must be present in the field + * finder. + * @throws BindException + * If the field could not be created + * @return The created and bound field. Can be any type of {@link Field}. + */ + + public <T extends Field> T buildAndBind(String caption, Object propertyId, + Class<T> fieldType) throws BindException { + Class<?> type = getPropertyType(propertyId); + + T field = build(caption, type, fieldType); + bind(field, propertyId); + + return field; + } + + /** + * Creates a field based on the given data type. + * <p> + * The data type is the type that we want to edit using the field. The field + * type is the type of field we want to create, can be {@link Field} if any + * Field is good. + * </p> + * + * @param caption + * The caption for the new field + * @param dataType + * The data model type that we want to edit using the field + * @param fieldType + * The type of field that we want to create + * @return A Field capable of editing the given type + * @throws BindException + * If the field could not be created + */ + protected <T extends Field> T build(String caption, Class<?> dataType, + Class<T> fieldType) throws BindException { + T field = getFieldFactory().createField(dataType, fieldType); + if (field == null) { + throw new BindException("Unable to build a field of type " + + fieldType.getName() + " for editing " + + dataType.getName()); + } + + field.setCaption(caption); + return field; + } }
\ No newline at end of file |