diff options
-rw-r--r-- | src/com/vaadin/Application.java | 47 | ||||
-rw-r--r-- | src/com/vaadin/ui/AbstractComponent.java | 2 | ||||
-rw-r--r-- | src/com/vaadin/ui/AbstractField.java | 315 | ||||
-rw-r--r-- | src/com/vaadin/ui/AbstractTextField.java | 2 | ||||
-rw-r--r-- | src/com/vaadin/ui/Button.java | 2 | ||||
-rw-r--r-- | src/com/vaadin/ui/DateField.java | 20 |
6 files changed, 271 insertions, 117 deletions
diff --git a/src/com/vaadin/Application.java b/src/com/vaadin/Application.java index f751431e63..c96a0adc6a 100644 --- a/src/com/vaadin/Application.java +++ b/src/com/vaadin/Application.java @@ -20,6 +20,9 @@ import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; +import com.vaadin.data.util.converter.Converter; +import com.vaadin.data.util.converter.ConverterFactory; +import com.vaadin.data.util.converter.DefaultConverterFactory; import com.vaadin.service.ApplicationContext; import com.vaadin.terminal.ApplicationResource; import com.vaadin.terminal.DownloadStream; @@ -33,6 +36,7 @@ import com.vaadin.terminal.gwt.server.ChangeVariablesErrorEvent; import com.vaadin.terminal.gwt.server.PortletApplicationContext; import com.vaadin.terminal.gwt.server.WebApplicationContext; import com.vaadin.ui.AbstractComponent; +import com.vaadin.ui.AbstractField; import com.vaadin.ui.Window; /** @@ -189,6 +193,13 @@ public abstract class Application implements URIHandler, private Terminal.ErrorListener errorHandler = this; /** + * The converter factory that is used for all fields in the application. + */ + // private ConverterFactory converterFactory = new + // DefaultConverterFactory(); + private static ConverterFactory converterFactory = new DefaultConverterFactory(); + + /** * <p> * Gets a window by name. Returns <code>null</code> if the application is * not running or it does not contain a window corresponding to the name. @@ -1280,6 +1291,42 @@ public abstract class Application implements URIHandler, } /** + * Gets the {@link ConverterFactory} used to locate a suitable + * {@link Converter} for fields in the application. + * + * See {@link #setConverterFactory(ConverterFactory)} for more details + * + * @return The converter factory used in the application + */ + // public ConverterFactory getConverterFactory() { + // return converterFactory; + // } + // FIXME: Should not be static + public static ConverterFactory getConverterFactory() { + return converterFactory; + } + + /** + * Sets the {@link ConverterFactory} used to locate a suitable + * {@link Converter} for fields in the application. + * + * The {@link ConverterFactory} is used to find a suitable converter when + * binding data to a UI component and the data type does not match the UI + * component type, e.g. binding a Double to a TextField (which is based on a + * String). + * + * The {@link Converter} for an individual field can be overridden using + * {@link AbstractField#setValueConverter(Converter)}. + * + * @param converterFactory + * The converter factory used in the application + */ + // FIXME: Should not be static + public static void setConverterFactory(ConverterFactory converterFactory) { + Application.converterFactory = converterFactory; + } + + /** * Contains the system messages used to notify the user about various * critical situations that can occur. * <p> diff --git a/src/com/vaadin/ui/AbstractComponent.java b/src/com/vaadin/ui/AbstractComponent.java index b9fffac35f..79300cbc9c 100644 --- a/src/com/vaadin/ui/AbstractComponent.java +++ b/src/com/vaadin/ui/AbstractComponent.java @@ -327,6 +327,8 @@ public abstract class AbstractComponent implements Component, MethodEventSource */ public void setLocale(Locale locale) { this.locale = locale; + + // FIXME: Reload value if there is a converter requestRepaint(); } diff --git a/src/com/vaadin/ui/AbstractField.java b/src/com/vaadin/ui/AbstractField.java index 71fa32f245..dbd6094300 100644 --- a/src/com/vaadin/ui/AbstractField.java +++ b/src/com/vaadin/ui/AbstractField.java @@ -12,11 +12,14 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.Map; +import com.vaadin.Application; import com.vaadin.data.Buffered; import com.vaadin.data.Property; import com.vaadin.data.Validatable; import com.vaadin.data.Validator; import com.vaadin.data.Validator.InvalidValueException; +import com.vaadin.data.util.converter.Converter; +import com.vaadin.data.util.converter.ConverterFactory; import com.vaadin.event.Action; import com.vaadin.event.ShortcutAction; import com.vaadin.event.ShortcutListener; @@ -64,6 +67,11 @@ public abstract class AbstractField<T> extends AbstractComponent implements private T value; /** + * A converter used to convert from the data model type to the field type + * and vice versa. + */ + private Converter<Object, T> valueConverter = null; + /** * Connected data-source. */ private Property<?> dataSource = null; @@ -178,9 +186,14 @@ public abstract class AbstractField<T> extends AbstractComponent implements && getErrorMessage() != null; } - /* - * Gets the field type Don't add a JavaDoc comment here, we use the default - * documentation from the implemented interface. + /** + * Returns the type of the Field. The methods <code>getValue</code> and + * <code>setValue</code> must be compatible with this type: one must be able + * to safely cast the value returned from <code>getValue</code> to the given + * type and pass any variable assignable to this type as an argument to + * <code>setValue</code>. + * + * @return the type of the Field */ public abstract Class<? extends T> getType(); @@ -230,14 +243,14 @@ public abstract class AbstractField<T> extends AbstractComponent implements public void commit() throws Buffered.SourceException, InvalidValueException { if (dataSource != null && !dataSource.isReadOnly()) { if ((isInvalidCommitted() || isValid())) { - final T newValue = getValue(); + final T fieldValue = getFieldValue(); try { // Commits the value to datasource. valueWasModifiedByDataSourceDuringCommit = false; committingValueToDataSource = true; - // TODO cast required until conversions applied - ((Property) dataSource).setValue(newValue); + getPropertyDataSource().setValue( + convertToDataSource(fieldValue)); } catch (final Throwable e) { @@ -260,8 +273,8 @@ public abstract class AbstractField<T> extends AbstractComponent implements boolean repaintNeeded = false; // The abstract field is not modified anymore - if (modified) { - modified = false; + if (isModified()) { + setModified(false); repaintNeeded = true; } @@ -288,14 +301,11 @@ public abstract class AbstractField<T> extends AbstractComponent implements if (dataSource != null) { // Gets the correct value from datasource - Object newValue; + T newFieldValue; try { // Discards buffer by overwriting from datasource - newValue = dataSource.getValue(); - if (String.class == getType() && newValue != null) { - newValue = newValue.toString(); - } + newFieldValue = convertFromDataSource(getDataSourceValue()); // If successful, remove set the buffering state to be ok if (currentBufferedSourceException != null) { @@ -303,6 +313,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements requestRepaint(); } } catch (final Throwable e) { + // FIXME: What should really be done here if conversion fails? // Sets the buffering state currentBufferedSourceException = new Buffered.SourceException( @@ -314,23 +325,43 @@ public abstract class AbstractField<T> extends AbstractComponent implements } final boolean wasModified = isModified(); - modified = false; + setModified(false); // If the new value differs from the previous one - if ((newValue == null && value != null) - || (newValue != null && !newValue.equals(value))) { - // TODO use converter - setInternalValue(newValue); + if (!equals(newFieldValue, value)) { + setInternalValue(newFieldValue); fireValueChange(false); - } - - // If the value did not change, but the modification status did - else if (wasModified) { + } else if (wasModified) { + // If the value did not change, but the modification status did requestRepaint(); } } } + private Object getDataSourceValue() { + return dataSource.getValue(); + } + + /** + * Returns the value that is or should be displayed in the field. This is + * always of type T. + * + * This method should return the same as + * convertFromDataSource(getDataSourceValue()) if there are no buffered + * changes in the field. + * + * @return The value of the field + */ + private T getFieldValue() { + // Give the value from abstract buffers if the field if possible + if (dataSource == null || !isReadThrough() || isModified()) { + return value; + } + + // There is no buffered value so use whatever the data model provides + return convertFromDataSource(getDataSourceValue()); + } + /* * Has the field been modified since the last commit()? Don't add a JavaDoc * comment here, we use the default documentation from the implemented @@ -340,6 +371,10 @@ public abstract class AbstractField<T> extends AbstractComponent implements return modified; } + private void setModified(boolean modified) { + this.modified = modified; + } + /* * Tests if the field is in write-through mode. Don't add a JavaDoc comment * here, we use the default documentation from the implemented interface. @@ -383,13 +418,8 @@ public abstract class AbstractField<T> extends AbstractComponent implements return; } readThroughMode = readThrough; - if (!isModified() && readThroughMode && dataSource != null) { - Object newValue = dataSource.getValue(); - if (String.class == getType() && newValue != null) { - newValue = newValue.toString(); - } - // TODO use converter - setInternalValue(newValue); + if (!isModified() && readThroughMode && getPropertyDataSource() != null) { + setInternalValue(convertFromDataSource(getDataSourceValue())); fireValueChange(false); } } @@ -423,7 +453,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements * @since 7.0 */ public String getStringValue() { - final Object value = getValue(); + final Object value = getFieldValue(); if (value == null) { return null; } @@ -441,61 +471,57 @@ public abstract class AbstractField<T> extends AbstractComponent implements * <p> * Note that the object returned is compatible with getType(). For example, * if the type is String, this returns Strings even when the underlying - * datasource is of some other type. In order to access the native type of - * the datasource, use getPropertyDatasource().getValue() instead. + * datasource is of some other type. In order to access the native value of + * the datasource, use getDataSourceValue() instead. * </p> * * <p> * Since Vaadin 7.0, no implicit conversions between other data types and - * String are performed, but the converter is used if set. + * String are performed, but a value converter is used if set. * </p> * * @return the current value of the field. * @throws Property.ConversionException */ public T getValue() { - - // Give the value from abstract buffers if the field if possible - if (dataSource == null || !isReadThrough() || isModified()) { - return value; - } - - Object result = dataSource.getValue(); - // TODO perform correct conversion, no cast or toString() - if (String.class == getType() && result != null) { - result = result.toString(); - } - return (T) result; + return getFieldValue(); } /** * Sets the value of the field. * - * @param newValue + * @param newFieldValue * the New value of the field. * @throws Property.ReadOnlyException * @throws Property.ConversionException */ - public void setValue(Object newValue) throws Property.ReadOnlyException, - Property.ConversionException { - setValue(newValue, false); + public void setValue(Object newFieldValue) + throws Property.ReadOnlyException, Property.ConversionException { + // This check is needed as long as setValue accepts Object instead of T + if (newFieldValue != null) { + if (!getType().isAssignableFrom(newFieldValue.getClass())) { + throw new ConversionException("Value of type " + + newFieldValue.getClass() + " cannot be assigned to " + + getClass().getName()); + } + } + setValue((T) newFieldValue, false); } /** * Sets the value of the field. * - * @param newValue + * @param newFieldValue * the New value of the field. * @param repaintIsNotNeeded * True iff caller is sure that repaint is not needed. * @throws Property.ReadOnlyException * @throws Property.ConversionException */ - protected void setValue(Object newValue, boolean repaintIsNotNeeded) + protected void setValue(T newFieldValue, boolean repaintIsNotNeeded) throws Property.ReadOnlyException, Property.ConversionException { - if ((newValue == null && value != null) - || (newValue != null && !newValue.equals(value))) { + if (!equals(newFieldValue, value)) { // Read only fields can not be changed if (isReadOnly()) { @@ -514,14 +540,14 @@ public abstract class AbstractField<T> extends AbstractComponent implements if (v != null) { for (final Iterator<Validator> i = v.iterator(); i .hasNext();) { - (i.next()).validate(newValue); + (i.next()).validate(newFieldValue); } } } // Changes the value - setInternalValue(newValue); - modified = dataSource != null; + setInternalValue(newFieldValue); + setModified(dataSource != null); valueWasModifiedByDataSourceDuringCommit = false; // In write through mode , try to commit @@ -531,10 +557,11 @@ public abstract class AbstractField<T> extends AbstractComponent implements // Commits the value to datasource committingValueToDataSource = true; - ((Property) dataSource).setValue(newValue); + getPropertyDataSource().setValue( + convertToDataSource(newFieldValue)); // The buffer is now unmodified - modified = false; + setModified(false); } catch (final Throwable e) { @@ -570,6 +597,13 @@ public abstract class AbstractField<T> extends AbstractComponent implements } } + private static boolean equals(Object value1, Object value2) { + if (value1 == null) { + return value2 == null; + } + return value1.equals(value2); + } + /* External data source */ /** @@ -630,21 +664,23 @@ public abstract class AbstractField<T> extends AbstractComponent implements // Sets the new data source dataSource = newDataSource; + // Check if the current converter is compatible. If not, get a new one + if (newDataSource == null) { + setValueConverter(null); + } else if (!isValueConverterType(newDataSource.getType())) { + setValueConverterFromFactory(newDataSource.getType()); + } // Gets the value from source try { if (dataSource != null) { - Object newValue = dataSource.getValue(); - if (String.class == getType() && newValue != null) { - newValue = newValue.toString(); - } - // TODO use converter - setInternalValue(newValue); + T fieldValue = convertFromDataSource(getDataSourceValue()); + setInternalValue(fieldValue); } - modified = false; + setModified(false); } catch (final Throwable e) { currentBufferedSourceException = new Buffered.SourceException(this, e); - modified = true; + setModified(true); } // Listens the new data source if possible @@ -675,6 +711,87 @@ public abstract class AbstractField<T> extends AbstractComponent implements } } + private void setValueConverterFromFactory(Class<?> datamodelType) { + // FIXME Use thread local to get application + ConverterFactory factory = Application.getConverterFactory(); + + Converter<?, T> converter = (Converter<?, T>) factory.createConverter( + datamodelType, getType()); + + setValueConverter(converter); + } + + private boolean isValueConverterType(Class<?> type) { + if (getValueConverter() == null) { + return false; + } + return getValueConverter().getSourceType().isAssignableFrom(type); + } + + @SuppressWarnings("unchecked") + private T convertFromDataSource(Object newValue) { + if (valueConverter != null) { + return valueConverter.convertFromSourceToTarget(newValue, + getLocale()); + } + if (newValue == null) { + return null; + } + + if (getType().isAssignableFrom(newValue.getClass())) { + return (T) newValue; + } else { + throw new ConversionException( + "Unable to convert value of type " + + newValue.getClass().getName() + + " to " + + getType() + + ". No value converter is set and the types are not compatible."); + } + } + + private Object convertToDataSource(T fieldValue) + throws Converter.ConversionException { + if (valueConverter != null) { + /* + * If there is a value converter, always use it. It must convert or + * throw an exception. + */ + return valueConverter.convertFromTargetToSource(fieldValue, + getLocale()); + } + + if (fieldValue == null) { + // Null should always be passed through the converter but if there + // is no converter we can safely return null + return null; + } + if (getPropertyDataSource() != null) { + /* + * Data source is set + */ + if (getPropertyDataSource().getType().isAssignableFrom( + fieldValue.getClass())) { + return fieldValue; + } else { + throw new Converter.ConversionException( + "Unable to convert value of type " + + fieldValue.getClass().getName() + + " to " + + getPropertyDataSource().getType() + + ". No value converter is set and the types are not compatible."); + + } + } + + throw new Converter.ConversionException( + "Unable to convert value of type " + + fieldValue.getClass().getName() + + " to " + + getType() + + ". No value converter is set and the types are not compatible."); + } + /* Validation */ /** @@ -769,12 +886,12 @@ public abstract class AbstractField<T> extends AbstractComponent implements // Initialize temps Validator.InvalidValueException firstError = null; LinkedList<InvalidValueException> errors = null; - final Object value = getValue(); + final Object fieldValue = getFieldValue(); // Gets all the validation errors for (final Iterator<Validator> i = validators.iterator(); i.hasNext();) { try { - (i.next()).validate(value); + (i.next()).validate(fieldValue); } catch (final Validator.InvalidValueException e) { if (firstError == null) { firstError = e; @@ -1028,10 +1145,8 @@ public abstract class AbstractField<T> extends AbstractComponent implements public void valueChange(Property.ValueChangeEvent event) { if (isReadThrough()) { if (committingValueToDataSource) { - boolean propertyNotifiesOfTheBufferedValue = event - .getProperty().getValue() == value - || (value != null && value.equals(event.getProperty() - .getValue())); + boolean propertyNotifiesOfTheBufferedValue = equals(event + .getProperty().getValue(), value); if (!propertyNotifiesOfTheBufferedValue) { /* * Property (or chained property like PropertyFormatter) now @@ -1054,8 +1169,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements } private void readValueFromProperty(Property.ValueChangeEvent event) { - // TODO use converter or check type otherwise - setInternalValue(event.getProperty().getValue()); + setInternalValue(convertFromDataSource(event.getProperty().getValue())); } @Override @@ -1071,25 +1185,6 @@ public abstract class AbstractField<T> extends AbstractComponent implements super.focus(); } - /** - * Creates abstract field by the type of the property. - * - * <p> - * This returns most suitable field type for editing property of given type. - * </p> - * - * @param propertyType - * the Type of the property, that needs to be edited. - * @deprecated use e.g. - * {@link DefaultFieldFactory#createFieldByPropertyType(Class)} - * instead - */ - @Deprecated - public static AbstractField constructField(Class<?> propertyType) { - return (AbstractField) DefaultFieldFactory - .createFieldByPropertyType(propertyType); - } - /* * (non-Javadoc) * @@ -1118,8 +1213,8 @@ public abstract class AbstractField<T> extends AbstractComponent implements * @param newValue * the new value to be set. */ - protected void setInternalValue(Object newValue) { - value = (T) newValue; + protected void setInternalValue(T newValue) { + value = newValue; if (validators != null && !validators.isEmpty()) { requestRepaint(); } @@ -1190,7 +1285,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements * also treats empty string as "empty". */ protected boolean isEmpty() { - return (getValue() == null); + return (getFieldValue() == null); } /** @@ -1293,4 +1388,32 @@ public abstract class AbstractField<T> extends AbstractComponent implements focusable.focus(); } } + + /** + * Gets the converter used to convert the property data source value to the + * field value. + * + * @return The converter or null if none is set. + */ + public Converter<Object, T> getValueConverter() { + return valueConverter; + } + + /** + * Sets the converter used to convert the property data source value to the + * field value. The converter must have a target type that matches the field + * type. + * + * The source for the converter is the data model and the target is the + * field. + * + * @param valueConverter + * The new value converter to use. + */ + public void setValueConverter(Converter<?, T> valueConverter) { + // + this.valueConverter = (Converter<Object, T>) valueConverter; + requestRepaint(); + } + } diff --git a/src/com/vaadin/ui/AbstractTextField.java b/src/com/vaadin/ui/AbstractTextField.java index 1bcb794eba..9eb2ff076d 100644 --- a/src/com/vaadin/ui/AbstractTextField.java +++ b/src/com/vaadin/ui/AbstractTextField.java @@ -456,7 +456,7 @@ public abstract class AbstractTextField extends AbstractField<String> implements } @Override - protected void setInternalValue(Object newValue) { + protected void setInternalValue(String newValue) { if (changingVariables && !textChangeEventPending) { /* diff --git a/src/com/vaadin/ui/Button.java b/src/com/vaadin/ui/Button.java index 93bd6d8963..eb580efe31 100644 --- a/src/com/vaadin/ui/Button.java +++ b/src/com/vaadin/ui/Button.java @@ -525,12 +525,10 @@ public class Button extends AbstractComponent implements requestRepaint(); } - @Override public int getTabIndex() { return tabIndex; } - @Override public void setTabIndex(int tabIndex) { this.tabIndex = tabIndex; diff --git a/src/com/vaadin/ui/DateField.java b/src/com/vaadin/ui/DateField.java index 4d83502f65..2daac4a613 100644 --- a/src/com/vaadin/ui/DateField.java +++ b/src/com/vaadin/ui/DateField.java @@ -479,7 +479,7 @@ public class DateField extends AbstractField<Date> implements * @see com.vaadin.ui.AbstractField#setValue(java.lang.Object, boolean) */ @Override - protected void setValue(Object newValue, boolean repaintIsNotNeeded) + protected void setValue(Date newValue, boolean repaintIsNotNeeded) throws Property.ReadOnlyException, Property.ConversionException { /* @@ -564,24 +564,8 @@ public class DateField extends AbstractField<Date> implements } } - /** - * Sets the DateField datasource. Datasource type must assignable to Date. - * - * @see com.vaadin.data.Property.Viewer#setPropertyDataSource(Property) - */ - @Override - public void setPropertyDataSource(Property newDataSource) { - if (newDataSource == null - || Date.class.isAssignableFrom(newDataSource.getType())) { - super.setPropertyDataSource(newDataSource); - } else { - throw new IllegalArgumentException( - "DateField only supports Date properties"); - } - } - @Override - protected void setInternalValue(Object newValue) { + protected void setInternalValue(Date newValue) { // Also set the internal dateString if (newValue != null) { dateString = newValue.toString(); |