/* 
 * Copyright 2011 Vaadin Ltd.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.vaadin.data.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.ui.DefaultFieldFactory;
import com.vaadin.ui.Field;
import com.vaadin.ui.Form;
import com.vaadin.util.ReflectTools;

/**
 * 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
 * @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;
        for (Field<?> field : getFields()) {
            field.setReadOnly(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;
    }
}