diff options
Diffstat (limited to 'compatibility-server/src/main/java/com/vaadin/data')
6 files changed, 1914 insertions, 0 deletions
diff --git a/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/BeanFieldGroup.java b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/BeanFieldGroup.java new file mode 100644 index 0000000000..96e4621761 --- /dev/null +++ b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/BeanFieldGroup.java @@ -0,0 +1,272 @@ +/* + * Copyright 2000-2016 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.beans.IntrospectionException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.data.Item; +import com.vaadin.data.util.BeanItem; +import com.vaadin.data.util.BeanUtil; +import com.vaadin.v7.data.validator.LegacyBeanValidator; +import com.vaadin.v7.ui.LegacyField; + +public class BeanFieldGroup<T> extends FieldGroup { + + private final Class<T> beanType; + + private static Boolean beanValidationImplementationAvailable = null; + private final Map<LegacyField<?>, LegacyBeanValidator> defaultValidators; + + public BeanFieldGroup(Class<T> beanType) { + this.beanType = beanType; + this.defaultValidators = new HashMap<LegacyField<?>, LegacyBeanValidator>(); + } + + @Override + protected Class<?> getPropertyType(Object propertyId) { + if (getItemDataSource() != null) { + return super.getPropertyType(propertyId); + } else { + // Data source not set so we need to figure out the type manually + /* + * toString should never really be needed as propertyId should be of + * form "fieldName" or "fieldName.subField[.subField2]" but the + * method declaration comes from parent. + */ + try { + Class<?> type = BeanUtil.getPropertyType(beanType, + propertyId.toString()); + if (type == null) { + throw new BindException( + "Cannot determine type of propertyId '" + propertyId + + "'. The propertyId was not found in " + + beanType.getName()); + } + return type; + } catch (IntrospectionException e) { + throw new BindException("Cannot determine type of propertyId '" + + propertyId + "'. Unable to introspect " + beanType, + e); + } + } + } + + @Override + protected Object findPropertyId(java.lang.reflect.Field memberField) { + String fieldName = memberField.getName(); + Item dataSource = getItemDataSource(); + if (dataSource != null + && dataSource.getItemProperty(fieldName) != null) { + return fieldName; + } else { + String minifiedFieldName = minifyFieldName(fieldName); + try { + return getFieldName(beanType, minifiedFieldName); + } catch (SecurityException e) { + } catch (NoSuchFieldException e) { + } + } + return null; + } + + private static String getFieldName(Class<?> cls, String propertyId) + throws SecurityException, NoSuchFieldException { + for (java.lang.reflect.Field field1 : cls.getDeclaredFields()) { + if (propertyId.equals(minifyFieldName(field1.getName()))) { + return field1.getName(); + } + } + // Try super classes until we reach Object + Class<?> superClass = cls.getSuperclass(); + if (superClass != null && superClass != Object.class) { + return getFieldName(superClass, propertyId); + } else { + throw new NoSuchFieldException(); + } + } + + /** + * Helper method for setting the data source directly using a bean. This + * method wraps the bean in a {@link BeanItem} and calls + * {@link #setItemDataSource(Item)}. + * <p> + * For null values, a null item is passed to + * {@link #setItemDataSource(Item)} to be properly clear fields. + * + * @param bean + * The bean to use as data source. + */ + public void setItemDataSource(T bean) { + if (bean == null) { + setItemDataSource((Item) null); + } else { + setItemDataSource(new BeanItem<T>(bean, beanType)); + } + } + + @Override + public void setItemDataSource(Item item) { + if (item == null || (item instanceof BeanItem)) { + super.setItemDataSource(item); + } else { + throw new RuntimeException(getClass().getSimpleName() + + " only supports BeanItems as item data source"); + } + } + + @Override + public BeanItem<T> getItemDataSource() { + return (BeanItem<T>) super.getItemDataSource(); + } + + private void ensureNestedPropertyAdded(Object propertyId) { + if (getItemDataSource() != null) { + // The data source is set so the property must be found in the item. + // If it is not we try to add it. + try { + getItemProperty(propertyId); + } catch (BindException e) { + // Not found, try to add a nested property; + // BeanItem property ids are always strings so this is safe + getItemDataSource().addNestedProperty((String) propertyId); + } + } + } + + @Override + public void bind(LegacyField field, Object propertyId) { + ensureNestedPropertyAdded(propertyId); + super.bind(field, propertyId); + } + + @Override + public <T extends LegacyField> T buildAndBind(String caption, + Object propertyId, Class<T> fieldType) throws BindException { + ensureNestedPropertyAdded(propertyId); + return super.buildAndBind(caption, propertyId, fieldType); + } + + @Override + public void unbind(LegacyField<?> field) throws BindException { + super.unbind(field); + + LegacyBeanValidator removed = defaultValidators.remove(field); + if (removed != null) { + field.removeValidator(removed); + } + } + + @Override + protected void configureField(LegacyField<?> field) { + super.configureField(field); + // Add Bean validators if there are annotations + if (isBeanValidationImplementationAvailable() + && !defaultValidators.containsKey(field)) { + LegacyBeanValidator validator = new LegacyBeanValidator(beanType, + getPropertyId(field).toString()); + field.addValidator(validator); + if (field.getLocale() != null) { + validator.setLocale(field.getLocale()); + } + defaultValidators.put(field, validator); + } + } + + /** + * Checks whether a bean validation implementation (e.g. Hibernate Validator + * or Apache Bean Validation) is available. + * + * TODO move this method to some more generic location + * + * @return true if a JSR-303 bean validation implementation is available + */ + protected static boolean isBeanValidationImplementationAvailable() { + if (beanValidationImplementationAvailable != null) { + return beanValidationImplementationAvailable; + } + try { + Class<?> validationClass = Class + .forName("javax.validation.Validation"); + Method buildFactoryMethod = validationClass + .getMethod("buildDefaultValidatorFactory"); + Object factory = buildFactoryMethod.invoke(null); + beanValidationImplementationAvailable = (factory != null); + } catch (Exception e) { + // no bean validation implementation available + beanValidationImplementationAvailable = false; + } + return beanValidationImplementationAvailable; + } + + /** + * Convenience method to bind Fields from a given "field container" to a + * given bean with buffering disabled. + * <p> + * The returned {@link BeanFieldGroup} can be used for further + * configuration. + * + * @see #bindFieldsBuffered(Object, Object) + * @see #bindMemberFields(Object) + * @since 7.2 + * @param bean + * the bean to be bound + * @param objectWithMemberFields + * the class that contains {@link LegacyField}s for bean + * properties + * @return the bean field group used to make binding + */ + public static <T> BeanFieldGroup<T> bindFieldsUnbuffered(T bean, + Object objectWithMemberFields) { + return createAndBindFields(bean, objectWithMemberFields, false); + } + + /** + * Convenience method to bind Fields from a given "field container" to a + * given bean with buffering enabled. + * <p> + * The returned {@link BeanFieldGroup} can be used for further + * configuration. + * + * @see #bindFieldsUnbuffered(Object, Object) + * @see #bindMemberFields(Object) + * @since 7.2 + * @param bean + * the bean to be bound + * @param objectWithMemberFields + * the class that contains {@link LegacyField}s for bean + * properties + * @return the bean field group used to make binding + */ + public static <T> BeanFieldGroup<T> bindFieldsBuffered(T bean, + Object objectWithMemberFields) { + return createAndBindFields(bean, objectWithMemberFields, true); + } + + private static <T> BeanFieldGroup<T> createAndBindFields(T bean, + Object objectWithMemberFields, boolean buffered) { + @SuppressWarnings("unchecked") + BeanFieldGroup<T> beanFieldGroup = new BeanFieldGroup<T>( + (Class<T>) bean.getClass()); + beanFieldGroup.setItemDataSource(bean); + beanFieldGroup.setBuffered(buffered); + beanFieldGroup.bindMemberFields(objectWithMemberFields); + return beanFieldGroup; + } + +} diff --git a/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/Caption.java b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/Caption.java new file mode 100644 index 0000000000..d752aa78d2 --- /dev/null +++ b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/Caption.java @@ -0,0 +1,27 @@ +/* + * Copyright 2000-2016 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.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Caption { + String value(); +} diff --git a/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java new file mode 100644 index 0000000000..24c97eedc5 --- /dev/null +++ b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java @@ -0,0 +1,254 @@ +/* + * Copyright 2000-2016 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.util.Date; +import java.util.EnumSet; + +import com.vaadin.data.Item; +import com.vaadin.data.fieldgroup.FieldGroup.BindException; +import com.vaadin.ui.AbstractSelect; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.ListSelect; +import com.vaadin.ui.NativeSelect; +import com.vaadin.ui.OptionGroup; +import com.vaadin.ui.RichTextArea; +import com.vaadin.ui.Table; +import com.vaadin.v7.ui.LegacyAbstractField; +import com.vaadin.v7.ui.LegacyAbstractTextField; +import com.vaadin.v7.ui.LegacyCheckBox; +import com.vaadin.v7.ui.LegacyDateField; +import com.vaadin.v7.ui.LegacyField; +import com.vaadin.v7.ui.LegacyInlineDateField; +import com.vaadin.v7.ui.LegacyPopupDateField; +import com.vaadin.v7.ui.LegacyTextField; + +/** + * This class contains a basic implementation for {@link FieldGroupFieldFactory} + * .The class is singleton, use {@link #get()} method to get reference to the + * instance. + * + * @author Vaadin Ltd + */ +public class DefaultFieldGroupFieldFactory implements FieldGroupFieldFactory { + + private static final DefaultFieldGroupFieldFactory INSTANCE = new DefaultFieldGroupFieldFactory(); + + public static final Object CAPTION_PROPERTY_ID = "Caption"; + + protected DefaultFieldGroupFieldFactory() { + } + + /** + * Gets the singleton instance. + * + * @since 7.4 + * + * @return the singleton instance + */ + public static DefaultFieldGroupFieldFactory get() { + return INSTANCE; + } + + @Override + public <T extends LegacyField> T createField(Class<?> type, + Class<T> fieldType) { + if (Enum.class.isAssignableFrom(type)) { + return createEnumField(type, fieldType); + } else if (Date.class.isAssignableFrom(type)) { + return createDateField(type, fieldType); + } else if (Boolean.class.isAssignableFrom(type) + || boolean.class.isAssignableFrom(type)) { + return createBooleanField(fieldType); + } + if (LegacyAbstractTextField.class.isAssignableFrom(fieldType)) { + return fieldType.cast(createAbstractTextField( + fieldType.asSubclass(LegacyAbstractTextField.class))); + } else if (fieldType == RichTextArea.class) { + return fieldType.cast(createRichTextArea()); + } + return createDefaultField(type, fieldType); + } + + protected RichTextArea createRichTextArea() { + RichTextArea rta = new RichTextArea(); + rta.setImmediate(true); + + return rta; + } + + private <T extends LegacyField> T createEnumField(Class<?> type, + Class<T> fieldType) { + // Determine first if we should (or can) create a select for the enum + Class<AbstractSelect> selectClass = null; + if (AbstractSelect.class.isAssignableFrom(fieldType)) { + selectClass = (Class<AbstractSelect>) fieldType; + } else if (anySelect(fieldType)) { + selectClass = AbstractSelect.class; + } + + if (selectClass != null) { + AbstractSelect s = createCompatibleSelect(selectClass); + populateWithEnumData(s, (Class<? extends Enum>) type); + return (T) s; + } else if (LegacyAbstractTextField.class.isAssignableFrom(fieldType)) { + return (T) createAbstractTextField( + (Class<? extends LegacyAbstractTextField>) fieldType); + } + + return null; + } + + private <T extends LegacyField> T createDateField(Class<?> type, + Class<T> fieldType) { + LegacyAbstractField field; + + if (LegacyInlineDateField.class.isAssignableFrom(fieldType)) { + field = new LegacyInlineDateField(); + } else if (anyField(fieldType) + || LegacyDateField.class.isAssignableFrom(fieldType)) { + field = new LegacyPopupDateField(); + } else if (LegacyAbstractTextField.class.isAssignableFrom(fieldType)) { + field = createAbstractTextField( + (Class<? extends LegacyAbstractTextField>) fieldType); + } else { + return null; + } + + field.setImmediate(true); + return (T) field; + } + + protected AbstractSelect createCompatibleSelect( + Class<? extends AbstractSelect> fieldType) { + AbstractSelect select; + if (fieldType.isAssignableFrom(ListSelect.class)) { + select = new ListSelect(); + select.setMultiSelect(false); + } else if (fieldType.isAssignableFrom(NativeSelect.class)) { + select = new NativeSelect(); + } else if (fieldType.isAssignableFrom(OptionGroup.class)) { + select = new OptionGroup(); + select.setMultiSelect(false); + } else if (fieldType.isAssignableFrom(Table.class)) { + Table t = new Table(); + t.setSelectable(true); + select = t; + } else { + select = new ComboBox(null); + } + select.setImmediate(true); + select.setNullSelectionAllowed(false); + + return select; + } + + /** + * @since 7.4 + * @param fieldType + * the type of the field + * @return true if any LegacyAbstractField can be assigned to the field + */ + protected boolean anyField(Class<?> fieldType) { + return fieldType == LegacyField.class + || fieldType == LegacyAbstractField.class; + } + + /** + * @since 7.4 + * @param fieldType + * the type of the field + * @return true if any AbstractSelect can be assigned to the field + */ + protected boolean anySelect(Class<? extends LegacyField> fieldType) { + return anyField(fieldType) || fieldType == AbstractSelect.class; + } + + protected <T extends LegacyField> T createBooleanField(Class<T> fieldType) { + if (fieldType.isAssignableFrom(LegacyCheckBox.class)) { + LegacyCheckBox cb = new LegacyCheckBox(null); + cb.setImmediate(true); + return (T) cb; + } else if (LegacyAbstractTextField.class.isAssignableFrom(fieldType)) { + return (T) createAbstractTextField( + (Class<? extends LegacyAbstractTextField>) fieldType); + } + + return null; + } + + protected <T extends LegacyAbstractTextField> T createAbstractTextField( + Class<T> fieldType) { + if (fieldType == LegacyAbstractTextField.class) { + fieldType = (Class<T>) LegacyTextField.class; + } + try { + T field = fieldType.newInstance(); + field.setImmediate(true); + return field; + } catch (Exception e) { + throw new BindException( + "Could not create a field of type " + fieldType, e); + } + } + + /** + * Fallback when no specific field has been created. Typically returns a + * TextField. + * + * @param <T> + * The type of field to create + * @param type + * The type of data that should be edited + * @param fieldType + * The type of field to create + * @return A field capable of editing the data or null if no field could be + * created + */ + protected <T extends LegacyField> T createDefaultField(Class<?> type, + Class<T> fieldType) { + if (fieldType.isAssignableFrom(LegacyTextField.class)) { + return fieldType + .cast(createAbstractTextField(LegacyTextField.class)); + } + return null; + } + + /** + * Populates the given select with all the enums in the given {@link Enum} + * class. Uses {@link Enum}.toString() for caption. + * + * @param select + * The select to populate + * @param enumClass + * The Enum class to use + */ + protected void populateWithEnumData(AbstractSelect select, + Class<? extends Enum> enumClass) { + select.removeAllItems(); + for (Object p : select.getContainerPropertyIds()) { + select.removeContainerProperty(p); + } + select.addContainerProperty(CAPTION_PROPERTY_ID, String.class, ""); + select.setItemCaptionPropertyId(CAPTION_PROPERTY_ID); + @SuppressWarnings("unchecked") + EnumSet<?> enumSet = EnumSet.allOf(enumClass); + for (Object r : enumSet) { + Item newItem = select.addItem(r); + newItem.getItemProperty(CAPTION_PROPERTY_ID).setValue(r.toString()); + } + } +} diff --git a/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/FieldGroup.java b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/FieldGroup.java new file mode 100644 index 0000000000..1254009cfc --- /dev/null +++ b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/FieldGroup.java @@ -0,0 +1,1258 @@ +/* + * Copyright 2000-2016 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.Map; + +import com.vaadin.data.Item; +import com.vaadin.data.Property; +import com.vaadin.data.util.TransactionalPropertyWrapper; +import com.vaadin.ui.DefaultFieldFactory; +import com.vaadin.util.ReflectTools; +import com.vaadin.v7.data.Validator.InvalidValueException; +import com.vaadin.v7.ui.LegacyAbstractField; +import com.vaadin.v7.ui.LegacyField; + +/** + * FieldGroup provides an easy way of binding fields to data and handling + * commits of these fields. + * <p> + * 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 Item itemDataSource; + private boolean buffered = true; + + private boolean enabled = true; + private boolean readOnly = false; + + private HashMap<Object, LegacyField<?>> propertyIdToField = new HashMap<Object, LegacyField<?>>(); + private LinkedHashMap<LegacyField<?>, Object> fieldToPropertyId = new LinkedHashMap<LegacyField<?>, Object>(); + private List<CommitHandler> commitHandlers = new ArrayList<CommitHandler>(); + + /** + * The field factory used by builder methods. + */ + private FieldGroupFieldFactory fieldFactory = DefaultFieldGroupFieldFactory + .get(); + + /** + * 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 (LegacyField<?> 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 LegacyField#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 LegacyField#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 (LegacyField<?> 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 (LegacyField<?> field : getFields()) { + field.setEnabled(fieldsEnabled); + } + } + + /** + * Returns the read only status that is used by default with all fields that + * have a writable data source. + * <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; + } + + /** + * Sets the read only state to the given value for all fields with writable + * data source. Fields with read only data source will always be set to read + * only. + * + * @param fieldsReadOnly + * true to set the fields with writable data source to read only, + * false to set them to read write + */ + public void setReadOnly(boolean fieldsReadOnly) { + readOnly = fieldsReadOnly; + for (LegacyField<?> field : getFields()) { + if (field.getPropertyDataSource() == null + || !field.getPropertyDataSource().isReadOnly()) { + field.setReadOnly(fieldsReadOnly); + } else { + field.setReadOnly(true); + } + } + } + + /** + * 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<LegacyField<?>> 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 field is null or the property id is already bound to + * another field by this field binder + */ + public void bind(LegacyField<?> field, Object propertyId) + throws BindException { + throwIfFieldIsNull(field, propertyId); + throwIfPropertyIdAlreadyBound(field, propertyId); + + fieldToPropertyId.put(field, propertyId); + propertyIdToField.put(propertyId, field); + if (itemDataSource == null) { + // Clear any possible existing binding to clear the field + field.setPropertyDataSource(null); + boolean fieldReadOnly = field.isReadOnly(); + if (!fieldReadOnly) { + field.clear(); + } else { + // Temporarily make the field read-write so we can clear the + // value. Needed because setPropertyDataSource(null) does not + // currently clear the field + // (https://dev.vaadin.com/ticket/14733) + field.setReadOnly(false); + field.clear(); + field.setReadOnly(true); + } + + // Will be bound when data source is set + return; + } + + field.setPropertyDataSource( + wrapInTransactionalProperty(getItemProperty(propertyId))); + configureField(field); + } + + /** + * Wrap property to transactional property. + */ + protected <T> Property.Transactional<T> wrapInTransactionalProperty( + Property<T> itemProperty) { + return new TransactionalPropertyWrapper<T>(itemProperty); + } + + private void throwIfFieldIsNull(LegacyField<?> field, Object propertyId) { + if (field == null) { + throw new BindException(String.format( + "Cannot bind property id '%s' to a null field.", + propertyId)); + } + } + + private void throwIfPropertyIdAlreadyBound(LegacyField<?> field, + Object propertyId) { + if (propertyIdToField.containsKey(propertyId) + && propertyIdToField.get(propertyId) != field) { + throw new BindException("Property id " + propertyId + + " is already bound to another field"); + } + } + + /** + * 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(LegacyField<?> field) throws BindException { + Object propertyId = fieldToPropertyId.get(field); + if (propertyId == null) { + throw new BindException( + "The given field is not part of this FieldBinder"); + } + + TransactionalPropertyWrapper<?> wrapper = null; + Property fieldDataSource = field.getPropertyDataSource(); + if (fieldDataSource instanceof TransactionalPropertyWrapper) { + wrapper = (TransactionalPropertyWrapper<?>) fieldDataSource; + fieldDataSource = ((TransactionalPropertyWrapper<?>) fieldDataSource) + .getWrappedProperty(); + + } + if (getItemDataSource() != null + && fieldDataSource == getItemProperty(propertyId)) { + if (null != wrapper) { + wrapper.detachFromProperty(); + } + 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. Fields with read only data + * source are always configured as read only. + * + * @param field + * The field to update + */ + protected void configureField(LegacyField<?> field) { + field.setBuffered(isBuffered()); + + field.setEnabled(isEnabled()); + + if (field.getPropertyDataSource().isReadOnly()) { + field.setReadOnly(true); + } else { + 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; + } + + startTransactions(); + + try { + firePreCommitEvent(); + + Map<LegacyField<?>, InvalidValueException> invalidValueExceptions = commitFields(); + + if (invalidValueExceptions.isEmpty()) { + firePostCommitEvent(); + commitTransactions(); + } else { + throw new FieldGroupInvalidValueException( + invalidValueExceptions); + } + } catch (Exception e) { + rollbackTransactions(); + throw new CommitException("Commit failed", this, e); + } + + } + + /** + * Tries to commit all bound fields one by one and gathers any validation + * exceptions in a map, which is returned to the caller + * + * @return a propertyId to validation exception map which is empty if all + * commits succeeded + */ + private Map<LegacyField<?>, InvalidValueException> commitFields() { + Map<LegacyField<?>, InvalidValueException> invalidValueExceptions = new HashMap<LegacyField<?>, InvalidValueException>(); + + for (LegacyField<?> f : fieldToPropertyId.keySet()) { + try { + f.commit(); + } catch (InvalidValueException e) { + invalidValueExceptions.put(f, e); + } + } + + return invalidValueExceptions; + } + + /** + * Exception which wraps InvalidValueExceptions from all invalid fields in a + * FieldGroup + * + * @since 7.4 + */ + public static class FieldGroupInvalidValueException + extends InvalidValueException { + private Map<LegacyField<?>, InvalidValueException> invalidValueExceptions; + + /** + * Constructs a new exception with the specified validation exceptions. + * + * @param invalidValueExceptions + * a property id to exception map + */ + public FieldGroupInvalidValueException( + Map<LegacyField<?>, InvalidValueException> invalidValueExceptions) { + super(null, invalidValueExceptions.values().toArray( + new InvalidValueException[invalidValueExceptions.size()])); + this.invalidValueExceptions = invalidValueExceptions; + } + + /** + * Returns a map containing fields which failed validation and the + * exceptions the corresponding validators threw. + * + * @return a map with all the invalid value exceptions + */ + public Map<LegacyField<?>, InvalidValueException> getInvalidFields() { + return invalidValueExceptions; + } + } + + private void startTransactions() throws CommitException { + for (LegacyField<?> f : fieldToPropertyId.keySet()) { + Property.Transactional<?> property = (Property.Transactional<?>) f + .getPropertyDataSource(); + if (property == null) { + throw new CommitException( + "Property \"" + fieldToPropertyId.get(f) + + "\" not bound to datasource."); + } + property.startTransaction(); + } + } + + private void commitTransactions() { + for (LegacyField<?> f : fieldToPropertyId.keySet()) { + ((Property.Transactional<?>) f.getPropertyDataSource()).commit(); + } + } + + private void rollbackTransactions() { + for (LegacyField<?> f : fieldToPropertyId.keySet()) { + try { + ((Property.Transactional<?>) f.getPropertyDataSource()) + .rollback(); + } catch (Exception rollbackException) { + // FIXME: What to do ? + } + } + } + + /** + * 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 (LegacyField<?> 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 LegacyField<?> 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(LegacyField<?> 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 LegacyField#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 (LegacyField<?> field : getFields()) { + field.validate(); + } + return true; + } catch (InvalidValueException e) { + return false; + } + } + + /** + * Checks if any bound field has been modified. + * + * @return true if at least one field has been modified, false otherwise + */ + public boolean isModified() { + for (LegacyField<?> 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 LegacyField} 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 LegacyField} and that can be mapped to a property id. Property ids + * are searched in the following order: @{@link PropertyId} annotations, + * exact field name matches and the case-insensitive matching that ignores + * underscores. 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 LegacyField} and that can be mapped to a property id. Property ids + * are searched in the following order: @{@link PropertyId} annotations, + * exact field name matches and the case-insensitive matching that ignores + * underscores. 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 : getFieldsInDeclareOrder( + objectClass)) { + + if (!LegacyField.class.isAssignableFrom(memberField.getType())) { + // Process next field + continue; + } + + PropertyId propertyIdAnnotation = memberField + .getAnnotation(PropertyId.class); + + Class<? extends LegacyField> fieldType = (Class<? extends LegacyField>) memberField + .getType(); + + Object propertyId = null; + if (propertyIdAnnotation != null) { + // @PropertyId(propertyId) always overrides property id + propertyId = propertyIdAnnotation.value(); + } else { + try { + propertyId = findPropertyId(memberField); + } catch (SearchException e) { + // Property id was not found, skip this field + continue; + } + if (propertyId == null) { + // Property id was not found, skip this field + continue; + } + } + + // Ensure that the property id exists + Class<?> propertyType; + + try { + propertyType = getPropertyType(propertyId); + } catch (BindException e) { + // Property id was not found, skip this field + continue; + } + + LegacyField<?> field; + try { + // Get the field from the object + field = (LegacyField<?>) ReflectTools.getJavaFieldValue( + objectWithMemberFields, memberField, LegacyField.class); + } 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 (LegacyField) + 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); + } + } + } + + /** + * Searches for a property id from the current itemDataSource that matches + * the given memberField. + * <p> + * If perfect match is not found, uses a case insensitive search that also + * ignores underscores. Returns null if no match is found. Throws a + * SearchException if no item data source has been set. + * </p> + * <p> + * The propertyId search logic used by + * {@link #buildAndBindMemberFields(Object, boolean) + * buildAndBindMemberFields} can easily be customized by overriding this + * method. No other changes are needed. + * </p> + * + * @param memberField + * The field an object id is searched for + * @return + */ + protected Object findPropertyId(java.lang.reflect.Field memberField) { + String fieldName = memberField.getName(); + if (getItemDataSource() == null) { + throw new SearchException("Property id type for field '" + fieldName + + "' could not be determined. No item data source has been set."); + } + Item dataSource = getItemDataSource(); + if (dataSource.getItemProperty(fieldName) != null) { + return fieldName; + } else { + String minifiedFieldName = minifyFieldName(fieldName); + for (Object itemPropertyId : dataSource.getItemPropertyIds()) { + if (itemPropertyId instanceof String) { + String itemPropertyName = (String) itemPropertyId; + if (minifiedFieldName + .equals(minifyFieldName(itemPropertyName))) { + return itemPropertyName; + } + } + } + } + return null; + } + + protected static String minifyFieldName(String fieldName) { + return fieldName.toLowerCase().replace("_", ""); + } + + /** + * Exception thrown by a FieldGroup when the commit operation fails. + * + * Provides information about validation errors through + * {@link #getInvalidFields()} if the cause of the failure is that all bound + * fields did not pass validation + * + */ + public static class CommitException extends Exception { + + private FieldGroup fieldGroup; + + public CommitException() { + super(); + } + + public CommitException(String message, FieldGroup fieldGroup, + Throwable cause) { + super(message, cause); + this.fieldGroup = fieldGroup; + } + + public CommitException(String message, Throwable cause) { + super(message, cause); + } + + public CommitException(String message) { + super(message); + } + + public CommitException(Throwable cause) { + super(cause); + } + + /** + * Returns a map containing the fields which failed validation and the + * exceptions the corresponding validators threw. + * + * @since 7.4 + * @return a map with all the invalid value exceptions. Can be empty but + * not null + */ + public Map<LegacyField<?>, InvalidValueException> getInvalidFields() { + if (getCause() instanceof FieldGroupInvalidValueException) { + return ((FieldGroupInvalidValueException) getCause()) + .getInvalidFields(); + } + return new HashMap<LegacyField<?>, InvalidValueException>(); + } + + /** + * Returns the field group where the exception occurred + * + * @since 7.4 + * @return the field group + */ + public FieldGroup getFieldGroup() { + return fieldGroup; + } + + } + + public static class BindException extends RuntimeException { + + public BindException(String message) { + super(message); + } + + public BindException(String message, Throwable t) { + super(message, t); + } + + } + + public static class SearchException extends RuntimeException { + + public SearchException(String message) { + super(message); + } + + public SearchException(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 LegacyField<?> 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 LegacyField}. + */ + public LegacyField<?> buildAndBind(String caption, Object propertyId) + throws BindException { + return buildAndBind(caption, propertyId, LegacyField.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 LegacyField}. + */ + + public <T extends LegacyField> 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 LegacyField} + * if any LegacyField 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 LegacyField capable of editing the given type + * @throws BindException + * If the field could not be created + */ + protected <T extends LegacyField> 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; + } + + /** + * Returns an array containing LegacyField objects reflecting all the fields + * of the class or interface represented by this Class object. The elements + * in the array returned are sorted in declare order from sub class to super + * class. + * + * @param searchClass + * @return + */ + protected static List<java.lang.reflect.Field> getFieldsInDeclareOrder( + Class searchClass) { + ArrayList<java.lang.reflect.Field> memberFieldInOrder = new ArrayList<java.lang.reflect.Field>(); + + while (searchClass != null) { + for (java.lang.reflect.Field memberField : searchClass + .getDeclaredFields()) { + memberFieldInOrder.add(memberField); + } + searchClass = searchClass.getSuperclass(); + } + return memberFieldInOrder; + } + + /** + * Clears the value of all fields. + * + * @since 7.4 + */ + public void clear() { + for (LegacyField<?> f : getFields()) { + if (f instanceof LegacyAbstractField) { + ((LegacyAbstractField) f).clear(); + } + } + + } + +}
\ No newline at end of file diff --git a/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/FieldGroupFieldFactory.java b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/FieldGroupFieldFactory.java new file mode 100644 index 0000000000..a80d51c6df --- /dev/null +++ b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/FieldGroupFieldFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2016 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 com.vaadin.v7.ui.LegacyField; + +/** + * Factory interface for creating new LegacyField-instances based on the data + * type that should be edited. + * + * @author Vaadin Ltd. + * @since 7.0 + */ +public interface FieldGroupFieldFactory extends Serializable { + /** + * Creates a field based on the data type that we want to edit + * + * @param dataType + * The type that we want to edit using the field + * @param fieldType + * The type of field we want to create. If set to + * {@link LegacyField} then any type of field is accepted + * @return A field that can be assigned to the given fieldType and that is + * capable of editing the given type of data + */ + <T extends LegacyField> T createField(Class<?> dataType, + Class<T> fieldType); +} diff --git a/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/PropertyId.java b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/PropertyId.java new file mode 100644 index 0000000000..a2a8aa27af --- /dev/null +++ b/compatibility-server/src/main/java/com/vaadin/data/fieldgroup/PropertyId.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2016 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.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines the custom property name to be bound to a {@link LegacyField} using + * {@link FieldGroup} or {@link BeanFieldGroup}. + * <p> + * The automatic data binding in FieldGroup and BeanFieldGroup relies on a + * naming convention by default: properties of an item are bound to similarly + * named field components in given a editor object. If you want to map a + * property with a different name (ID) to a + * {@link com.vaadin.client.ui.LegacyField}, you can use this annotation for the + * member fields, with the name (ID) of the desired property as the parameter. + * <p> + * In following usage example, the text field would be bound to property "foo" + * in the Entity class. <code> + * <pre> + * class Editor extends FormLayout { + @PropertyId("foo") + TextField myField = new TextField(); + } + + class Entity { + String foo; + } + + { + Editor e = new Editor(); + BeanFieldGroup.bindFieldsUnbuffered(new Entity(), e); + } + </pre> + * </code> + * + * @since 7.0 + * @author Vaadin Ltd + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface PropertyId { + String value(); +} |