From c11121e2b7e685d42eb129c8a97e5ed4690af2f2 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Thu, 25 Aug 2016 14:46:30 +0300 Subject: Move old Field and AbstractField to compatibility package Change-Id: Ia9b6f77763abac87ec61d1ee198cb8d41419a934 --- .../main/java/com/vaadin/v7/ui/AbstractField.java | 1810 ++++++++++++++++++++ .../src/main/java/com/vaadin/v7/ui/Field.java | 150 ++ .../java/com/vaadin/v7/tests/VaadinClasses.java | 21 + .../AbstractFieldValueChangeTestBase.java | 130 ++ .../abstractfield/FieldDefaultValuesTest.java | 76 + .../textfield/TextFieldValueChangeTest.java | 2 +- .../server/components/ComboBoxValueChangeTest.java | 2 +- .../main/java/com/vaadin/event/FieldEvents.java | 13 +- .../main/java/com/vaadin/v7/ui/AbstractField.java | 1810 -------------------- server/src/main/java/com/vaadin/v7/ui/Field.java | 150 -- .../test/java/com/vaadin/tests/VaadinClasses.java | 12 +- .../server/component/FieldDefaultValuesTest.java | 77 - .../AbstractFieldValueChangeTestBase.java | 130 -- 13 files changed, 2196 insertions(+), 2187 deletions(-) create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/ui/AbstractField.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/ui/Field.java create mode 100644 compatibility-server/src/test/java/com/vaadin/v7/tests/VaadinClasses.java create mode 100644 compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/abstractfield/AbstractFieldValueChangeTestBase.java create mode 100644 compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/abstractfield/FieldDefaultValuesTest.java delete mode 100644 server/src/main/java/com/vaadin/v7/ui/AbstractField.java delete mode 100644 server/src/main/java/com/vaadin/v7/ui/Field.java delete mode 100644 server/src/test/java/com/vaadin/tests/server/component/FieldDefaultValuesTest.java delete mode 100644 server/src/test/java/com/vaadin/tests/server/components/AbstractFieldValueChangeTestBase.java diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/AbstractField.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/AbstractField.java new file mode 100644 index 0000000000..33f262d581 --- /dev/null +++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/AbstractField.java @@ -0,0 +1,1810 @@ +/* + * 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.v7.ui; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.logging.Logger; + +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.Element; + +import com.vaadin.event.Action; +import com.vaadin.event.ShortcutAction; +import com.vaadin.event.ShortcutListener; +import com.vaadin.server.AbstractErrorMessage; +import com.vaadin.server.CompositeErrorMessage; +import com.vaadin.server.ErrorMessage; +import com.vaadin.shared.AbstractFieldState; +import com.vaadin.shared.util.SharedUtil; +import com.vaadin.ui.AbstractComponent; +import com.vaadin.ui.Component; +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.v7.data.Buffered; +import com.vaadin.v7.data.Property; +import com.vaadin.v7.data.Validatable; +import com.vaadin.v7.data.Validator; +import com.vaadin.v7.data.Validator.InvalidValueException; +import com.vaadin.v7.data.util.converter.Converter; +import com.vaadin.v7.data.util.converter.Converter.ConversionException; +import com.vaadin.v7.data.util.converter.ConverterUtil; + +/** + *

+ * Abstract field component for implementing buffered property editors. The + * field may hold an internal value, or it may be connected to any data source + * that implements the {@link com.vaadin.v7.data.Property}interface. + * LegacyAbstractField implements that interface itself, too, so + * accessing the Property value represented by it is straightforward. + *

+ * + *

+ * LegacyAbstractField also provides the {@link com.vaadin.v7.data.Buffered} + * interface for buffering the data source value. By default the LegacyField is + * in write through-mode and {@link #setWriteThrough(boolean)}should be called + * to enable buffering. + *

+ * + *

+ * The class also supports {@link com.vaadin.v7.data.Validator validators} + * to make sure the value contained in the field is valid. + *

+ * + * @author Vaadin Ltd. + * @since 3.0 + * + * @deprecated This class is, apart from the rename, identical to the Vaadin 7 + * {@code com.vaadin.ui.AbstractField}. It is provided for + * compatibility and migration purposes. As of 8.0, new field + * implementations should extend the new + * {@link com.vaadin.ui.AbstractField} instead. + */ +@SuppressWarnings("serial") +@Deprecated +public abstract class AbstractField extends AbstractComponent + implements Field, Property.ReadOnlyStatusChangeListener, + Property.ReadOnlyStatusChangeNotifier, Action.ShortcutNotifier { + + /* Private members */ + + /** + * Value of the abstract field. + */ + private T value; + + /** + * A converter used to convert from the data model type to the field type + * and vice versa. + */ + private Converter converter = null; + /** + * Connected data-source. + */ + private Property dataSource = null; + + /** + * The list of validators. + */ + private LinkedList validators = null; + + /** + * True if field is in buffered mode, false otherwise + */ + private boolean buffered; + + /** + * Flag to indicate that the field is currently committing its value to the + * datasource. + */ + private boolean committingValueToDataSource = false; + + /** + * Current source exception. + */ + private Buffered.SourceException currentBufferedSourceException = null; + + /** + * Are the invalid values allowed in fields ? + */ + private boolean invalidAllowed = true; + + /** + * Are the invalid values committed ? + */ + private boolean invalidCommitted = false; + + /** + * The error message for the exception that is thrown when the field is + * required but empty. + */ + private String requiredError = ""; + + /** + * The error message that is shown when the field value cannot be converted. + */ + private String conversionError = "Could not convert value to {0}"; + + /** + * Is automatic validation enabled. + */ + private boolean validationVisible = true; + + private boolean valueWasModifiedByDataSourceDuringCommit; + + /** + * Whether this field is currently registered as listening to events from + * its data source. + * + * @see #setPropertyDataSource(Property) + * @see #addPropertyListeners() + * @see #removePropertyListeners() + */ + private boolean isListeningToPropertyEvents = false; + + /** + * The locale used when setting the value. + */ + private Locale valueLocale = null; + + /* Component basics */ + + /* + * Paints the field. Don't add a JavaDoc comment here, we use the default + * documentation from the implemented interface. + */ + + /** + * Returns true if the error indicator be hidden when painting the component + * even when there are errors. + * + * This is a mostly internal method, but can be overridden in subclasses + * e.g. if the error indicator should also be shown for empty fields in some + * cases. + * + * @return true to hide the error indicator, false to use the normal logic + * to show it when there are errors + */ + protected boolean shouldHideErrors() { + // getErrorMessage() can still return something else than null based on + // validation etc. + return isRequired() && isEmpty() && getComponentError() == null; + } + + /** + * Returns the type of the LegacyField. The methods getValue + * and setValue must be compatible with this type: one must be + * able to safely cast the value returned from getValue to the + * given type and pass any variable assignable to this type as an argument + * to setValue. + * + * @return the type of the LegacyField + */ + @Override + public abstract Class getType(); + + /** + * The abstract field is read only also if the data source is in read only + * mode. + */ + @Override + public boolean isReadOnly() { + return super.isReadOnly() + || (dataSource != null && dataSource.isReadOnly()); + } + + /** + * Changes the readonly state and throw read-only status change events. + * + * @see com.vaadin.ui.Component#setReadOnly(boolean) + */ + @Override + public void setReadOnly(boolean readOnly) { + super.setReadOnly(readOnly); + fireReadOnlyStatusChange(); + } + + /** + * Tests if the invalid data is committed to datasource. + * + * @see com.vaadin.v7.data.BufferedValidatable#isInvalidCommitted() + */ + @Override + public boolean isInvalidCommitted() { + return invalidCommitted; + } + + /** + * Sets if the invalid data should be committed to datasource. + * + * @see com.vaadin.v7.data.BufferedValidatable#setInvalidCommitted(boolean) + */ + @Override + public void setInvalidCommitted(boolean isCommitted) { + invalidCommitted = isCommitted; + } + + /* + * Saves the current value to the data source Don't add a JavaDoc comment + * here, we use the default documentation from the implemented interface. + */ + @Override + public void commit() + throws Buffered.SourceException, InvalidValueException { + if (dataSource != null && !dataSource.isReadOnly()) { + if ((isInvalidCommitted() || isValid())) { + try { + + // Commits the value to datasource. + valueWasModifiedByDataSourceDuringCommit = false; + committingValueToDataSource = true; + getPropertyDataSource().setValue(getConvertedValue()); + } catch (final Throwable e) { + + // Sets the buffering state. + SourceException sourceException = new Buffered.SourceException( + this, e); + setCurrentBufferedSourceException(sourceException); + + // Throws the source exception. + throw sourceException; + } finally { + committingValueToDataSource = false; + } + } else { + /* + * An invalid value and we don't allow them, throw the exception + */ + validate(); + } + } + + // The abstract field is not modified anymore + if (isModified()) { + setModified(false); + } + + // If successful, remove set the buffering state to be ok + if (getCurrentBufferedSourceException() != null) { + setCurrentBufferedSourceException(null); + } + + if (valueWasModifiedByDataSourceDuringCommit) { + valueWasModifiedByDataSourceDuringCommit = false; + fireValueChange(false); + } + + } + + /* + * Updates the value from the data source. Don't add a JavaDoc comment here, + * we use the default documentation from the implemented interface. + */ + @Override + public void discard() throws Buffered.SourceException { + updateValueFromDataSource(); + } + + /** + * Gets the value from the data source. This is only here because of clarity + * in the code that handles both the data model value and the field value. + * + * @return The value of the property data source + */ + private Object getDataSourceValue() { + return dataSource.getValue(); + } + + /** + * Returns the field value. This is always identical to {@link #getValue()} + * and only here because of clarity in the code that handles both the data + * model value and the field value. + * + * @return The value of the field + */ + private T getFieldValue() { + // Give the value from abstract buffers if the field if possible + if (dataSource == null || isBuffered() || isModified()) { + return getInternalValue(); + } + + // There is no buffered value so use whatever the data model provides + return convertFromModel(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 + * interface. + */ + @Override + public boolean isModified() { + return getState(false).modified; + } + + private void setModified(boolean modified) { + getState().modified = modified; + } + + /** + * Sets the buffered mode of this LegacyField. + *

+ * When the field is in buffered mode, changes will not be committed to the + * property data source until {@link #commit()} is called. + *

+ *

+ * Setting buffered mode from true to false will commit any pending changes. + *

+ *

+ * + *

+ * + * @since 7.0.0 + * @param buffered + * true if buffered mode should be turned on, false otherwise + */ + @Override + public void setBuffered(boolean buffered) { + if (this.buffered == buffered) { + return; + } + this.buffered = buffered; + if (!buffered) { + commit(); + } + } + + /** + * Checks the buffered mode of this LegacyField. + * + * @return true if buffered mode is on, false otherwise + */ + @Override + public boolean isBuffered() { + return buffered; + } + + /* Property interface implementation */ + + /** + * Gets the current value of the field. + * + *

+ * This is the visible, modified and possible invalid value the user have + * entered to the field. + *

+ * + *

+ * 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 converted value, + * use {@link #getConvertedValue()} and to access the value of the property + * data source, use {@link Property#getValue()} for the property data + * source. + *

+ * + *

+ * Since Vaadin 7.0, no implicit conversions between other data types and + * String are performed, but a converter is used if set. + *

+ * + * @return the current value of the field. + */ + @Override + public T getValue() { + return getFieldValue(); + } + + /** + * Sets the value of the field. + * + * @param newFieldValue + * the New value of the field. + * @throws Property.ReadOnlyException + */ + @Override + public void setValue(T newFieldValue) throws Property.ReadOnlyException, + Converter.ConversionException { + setValue(newFieldValue, false); + } + + /** + * Sets the value of the field. + * + * @param newFieldValue + * the New value of the field. + * @param repaintIsNotNeeded + * True iff caller is sure that repaint is not needed. + * @throws Property.ReadOnlyException + * @throws Converter.ConversionException + * @throws InvalidValueException + */ + protected void setValue(T newFieldValue, boolean repaintIsNotNeeded) { + setValue(newFieldValue, repaintIsNotNeeded, false); + } + + /** + * Sets the value of the field. + * + * @since 7.5.7 + * @param newFieldValue + * the New value of the field. + * @param repaintIsNotNeeded + * True iff caller is sure that repaint is not needed. + * @param ignoreReadOnly + * True iff if the read-only check should be ignored + * @throws Property.ReadOnlyException + * @throws Converter.ConversionException + * @throws InvalidValueException + */ + protected void setValue(T newFieldValue, boolean repaintIsNotNeeded, + boolean ignoreReadOnly) throws Property.ReadOnlyException, + Converter.ConversionException, InvalidValueException { + + if (!SharedUtil.equals(newFieldValue, getInternalValue())) { + + // Read only fields can not be changed + if (!ignoreReadOnly && isReadOnly()) { + throw new Property.ReadOnlyException(); + } + try { + T doubleConvertedFieldValue = convertFromModel( + convertToModel(newFieldValue)); + if (!SharedUtil.equals(newFieldValue, + doubleConvertedFieldValue)) { + newFieldValue = doubleConvertedFieldValue; + repaintIsNotNeeded = false; + } + } catch (Throwable t) { + // Ignore exceptions in the conversion at this stage. Any + // conversion error will be handled later by validate(). + } + + // Repaint is needed even when the client thinks that it knows the + // new state if validity of the component may change + if (repaintIsNotNeeded && (isRequired() || hasValidators() + || getConverter() != null)) { + repaintIsNotNeeded = false; + } + + if (!isInvalidAllowed()) { + /* + * If invalid values are not allowed the value must be validated + * before it is set. If validation fails, the + * InvalidValueException is thrown and the internal value is not + * updated. + */ + validate(newFieldValue); + } + + // Changes the value + setInternalValue(newFieldValue); + setModified(dataSource != null); + + valueWasModifiedByDataSourceDuringCommit = false; + // In not buffering, try to commit + if (!isBuffered() && dataSource != null + && (isInvalidCommitted() || isValid())) { + try { + + // Commits the value to datasource + committingValueToDataSource = true; + getPropertyDataSource() + .setValue(convertToModel(newFieldValue)); + + // The buffer is now unmodified + setModified(false); + + } catch (final Throwable e) { + + // Sets the buffering state + currentBufferedSourceException = new Buffered.SourceException( + this, e); + markAsDirty(); + + // Throws the source exception + throw currentBufferedSourceException; + } finally { + committingValueToDataSource = false; + } + } + + // If successful, remove set the buffering state to be ok + if (getCurrentBufferedSourceException() != null) { + setCurrentBufferedSourceException(null); + } + + if (valueWasModifiedByDataSourceDuringCommit) { + /* + * Value was modified by datasource. Force repaint even if + * repaint was not requested. + */ + valueWasModifiedByDataSourceDuringCommit = repaintIsNotNeeded = false; + } + + // Fires the value change + fireValueChange(repaintIsNotNeeded); + + } + } + + @Deprecated + static boolean equals(Object value1, Object value2) { + return SharedUtil.equals(value1, value2); + } + + /* External data source */ + + /** + * Gets the current data source of the field, if any. + * + * @return the current data source as a Property, or null if + * none defined. + */ + @Override + public Property getPropertyDataSource() { + return dataSource; + } + + /** + *

+ * Sets the specified Property as the data source for the field. All + * uncommitted changes are replaced with a value from the new data source. + *

+ * + *

+ * If the datasource has any validators, the same validators are added to + * the field. Because the default behavior of the field is to allow invalid + * values, but not to allow committing them, this only adds visual error + * messages to fields and do not allow committing them as long as the value + * is invalid. After the value is valid, the error message is not shown and + * the commit can be done normally. + *

+ * + *

+ * If the data source implements + * {@link com.vaadin.v7.data.Property.ValueChangeNotifier} and/or + * {@link com.vaadin.v7.data.Property.ReadOnlyStatusChangeNotifier}, the field + * registers itself as a listener and updates itself according to the events + * it receives. To avoid memory leaks caused by references to a field no + * longer in use, the listener registrations are removed on + * {@link AbstractField#detach() detach} and re-added on + * {@link AbstractField#attach() attach}. + *

+ * + *

+ * Note: before 6.5 we actually called discard() method in the beginning of + * the method. This was removed to simplify implementation, avoid excess + * calls to backing property and to avoid odd value change events that were + * previously fired (developer expects 0-1 value change events if this + * method is called). Some complex field implementations might now need to + * override this method to do housekeeping similar to discard(). + *

+ * + * @param newDataSource + * the new data source Property. + */ + @Override + public void setPropertyDataSource(Property newDataSource) { + + // Saves the old value + final Object oldValue = getInternalValue(); + + // Stop listening to the old data source + removePropertyListeners(); + + // Sets the new data source + dataSource = newDataSource; + getState().propertyReadOnly = dataSource == null ? false + : dataSource.isReadOnly(); + + // Check if the current converter is compatible. + if (newDataSource != null + && !ConverterUtil.canConverterPossiblyHandle( + getConverter(), getType(), newDataSource.getType())) { + // There is no converter set or there is no way the current + // converter can be compatible. + setConverter(newDataSource.getType()); + } + // Gets the value from source. This requires that a valid converter has + // been set. + try { + if (dataSource != null) { + T fieldValue = convertFromModel(getDataSourceValue()); + setInternalValue(fieldValue); + } + setModified(false); + if (getCurrentBufferedSourceException() != null) { + setCurrentBufferedSourceException(null); + } + } catch (final Throwable e) { + setCurrentBufferedSourceException( + new Buffered.SourceException(this, e)); + setModified(true); + throw getCurrentBufferedSourceException(); + } + + // Listen to new data source if possible + addPropertyListeners(); + + // Copy the validators from the data source + if (dataSource instanceof Validatable) { + final Collection validators = ((Validatable) dataSource) + .getValidators(); + if (validators != null) { + for (final Iterator i = validators.iterator(); i + .hasNext();) { + addValidator(i.next()); + } + } + } + + // Fires value change if the value has changed + T value = getInternalValue(); + if ((value != oldValue) && ((value != null && !value.equals(oldValue)) + || value == null)) { + fireValueChange(false); + } + } + + /** + * Retrieves a converter for the field from the converter factory defined + * for the application. Clears the converter if no application reference is + * available or if the factory returns null. + * + * @param datamodelType + * The type of the data model that we want to be able to convert + * from + */ + public void setConverter(Class datamodelType) { + Converter c = (Converter) ConverterUtil + .getConverter(getType(), datamodelType, getSession()); + setConverter(c); + } + + /** + * Convert the given value from the data source type to the UI type. + * + * @param newValue + * The data source value to convert. + * @return The converted value that is compatible with the UI type or the + * original value if its type is compatible and no converter is set. + * @throws Converter.ConversionException + * if there is no converter and the type is not compatible with + * the data source type. + */ + private T convertFromModel(Object newValue) { + return convertFromModel(newValue, getLocale()); + } + + /** + * Convert the given value from the data source type to the UI type. + * + * @param newValue + * The data source value to convert. + * @return The converted value that is compatible with the UI type or the + * original value if its type is compatible and no converter is set. + * @throws Converter.ConversionException + * if there is no converter and the type is not compatible with + * the data source type. + */ + private T convertFromModel(Object newValue, Locale locale) { + return ConverterUtil.convertFromModel(newValue, getType(), + getConverter(), locale); + } + + /** + * Convert the given value from the UI type to the data source type. + * + * @param fieldValue + * The value to convert. Typically returned by + * {@link #getFieldValue()} + * @return The converted value that is compatible with the data source type. + * @throws Converter.ConversionException + * if there is no converter and the type is not compatible with + * the data source type. + */ + private Object convertToModel(T fieldValue) + throws Converter.ConversionException { + return convertToModel(fieldValue, getLocale()); + } + + /** + * Convert the given value from the UI type to the data source type. + * + * @param fieldValue + * The value to convert. Typically returned by + * {@link #getFieldValue()} + * @param locale + * The locale to use for the conversion + * @return The converted value that is compatible with the data source type. + * @throws Converter.ConversionException + * if there is no converter and the type is not compatible with + * the data source type. + */ + private Object convertToModel(T fieldValue, Locale locale) + throws Converter.ConversionException { + Class modelType = getModelType(); + try { + return ConverterUtil.convertToModel(fieldValue, + (Class) modelType, getConverter(), locale); + } catch (ConversionException e) { + throw new ConversionException(getConversionError(modelType, e), e); + } + } + + /** + * Retrieves the type of the currently used data model. If the field has no + * data source then the model type of the converter is used. + * + * @since 7.1 + * @return The type of the currently used data model or null if no data + * source or converter is set. + */ + protected Class getModelType() { + Property pd = getPropertyDataSource(); + if (pd != null) { + return pd.getType(); + } else if (getConverter() != null) { + return getConverter().getModelType(); + } + return null; + } + + /** + * Returns the conversion error with {0} replaced by the data source type + * and {1} replaced by the exception (localized) message. + * + * @since 7.1 + * @param dataSourceType + * the type of the data source + * @param e + * a conversion exception which can provide additional + * information + * @return The value conversion error string with parameters replaced. + */ + protected String getConversionError(Class dataSourceType, + ConversionException e) { + String conversionError = getConversionError(); + + if (conversionError != null) { + if (dataSourceType != null) { + conversionError = conversionError.replace("{0}", + dataSourceType.getSimpleName()); + } + if (e != null) { + conversionError = conversionError.replace("{1}", + e.getLocalizedMessage()); + } + } + return conversionError; + } + + /** + * Returns the current value (as returned by {@link #getValue()}) converted + * to the data source type. + *

+ * This returns the same as {@link AbstractField#getValue()} if no + * converter has been set. The value is not necessarily the same as the data + * source value e.g. if the field is in buffered mode and has been modified. + *

+ * + * @return The converted value that is compatible with the data source type + */ + public Object getConvertedValue() { + return convertToModel(getFieldValue()); + } + + /** + * Sets the value of the field using a value of the data source type. The + * value given is converted to the field type and then assigned to the + * field. This will update the property data source in the same way as when + * {@link #setValue(Object)} is called. + * + * @param value + * The value to set. Must be the same type as the data source. + */ + public void setConvertedValue(Object value) { + setValue(convertFromModel(value)); + } + + /* Validation */ + + /** + * Adds a new validator for the field's value. All validators added to a + * field are checked each time the its value changes. + * + * @param validator + * the new validator to be added. + */ + @Override + public void addValidator(Validator validator) { + if (validators == null) { + validators = new LinkedList<>(); + } + validators.add(validator); + markAsDirty(); + } + + /** + * Gets the validators of the field. + * + * @return An unmodifiable collection that holds all validators for the + * field. + */ + @Override + public Collection getValidators() { + if (validators == null) { + return Collections.emptyList(); + } else { + return Collections.unmodifiableCollection(validators); + } + } + + private boolean hasValidators() { + return validators != null && !validators.isEmpty(); + } + + /** + * Removes the validator from the field. + * + * @param validator + * the validator to remove. + */ + @Override + public void removeValidator(Validator validator) { + if (validators != null) { + validators.remove(validator); + } + markAsDirty(); + } + + /** + * Removes all validators from the field. + */ + @Override + public void removeAllValidators() { + if (validators != null) { + validators.clear(); + } + markAsDirty(); + } + + /** + * Tests the current value against registered validators if the field is not + * empty. If the field is empty it is considered valid if it is not required + * and invalid otherwise. Validators are never checked for empty fields. + * + * In most cases, {@link #validate()} should be used instead of + * {@link #isValid()} to also get the error message. + * + * @return true if all registered validators claim that the + * current value is valid or if the field is empty and not required, + * false otherwise. + */ + @Override + public boolean isValid() { + + try { + validate(); + return true; + } catch (InvalidValueException e) { + return false; + } + } + + /** + * Checks the validity of the LegacyField. + * + * A field is invalid if it is set as required (using + * {@link #setRequired(boolean)} and is empty, if one or several of the + * validators added to the field indicate it is invalid or if the value + * cannot be converted provided a converter has been set. + * + * The "required" validation is a built-in validation feature. If the field + * is required and empty this method throws an EmptyValueException with the + * error message set using {@link #setRequiredError(String)}. + * + * @see com.vaadin.v7.data.Validatable#validate() + */ + @Override + public void validate() throws Validator.InvalidValueException { + + if (isRequired() && isEmpty()) { + throw new Validator.EmptyValueException(requiredError); + } + validate(getFieldValue()); + } + + /** + * Validates that the given value pass the validators for the field. + *

+ * This method does not check the requiredness of the field. + * + * @param fieldValue + * The value to check + * @throws Validator.InvalidValueException + * if one or several validators fail + */ + protected void validate(T fieldValue) + throws Validator.InvalidValueException { + + Object valueToValidate = fieldValue; + + // If there is a converter we start by converting the value as we want + // to validate the converted value + if (getConverter() != null) { + try { + valueToValidate = getConverter().convertToModel(fieldValue, + getModelType(), getLocale()); + } catch (ConversionException e) { + throw new InvalidValueException( + getConversionError(getConverter().getModelType(), e)); + } + } + + List validationExceptions = new ArrayList<>(); + if (validators != null) { + // Gets all the validation errors + for (Validator v : validators) { + try { + v.validate(valueToValidate); + } catch (final Validator.InvalidValueException e) { + validationExceptions.add(e); + } + } + } + + // If there were no errors + if (validationExceptions.isEmpty()) { + return; + } + + // If only one error occurred, throw it forwards + if (validationExceptions.size() == 1) { + throw validationExceptions.get(0); + } + + InvalidValueException[] exceptionArray = validationExceptions.toArray( + new InvalidValueException[validationExceptions.size()]); + + // Create a composite validator and include all exceptions + throw new Validator.InvalidValueException(null, exceptionArray); + } + + /** + * Fields allow invalid values by default. In most cases this is wanted, + * because the field otherwise visually forget the user input immediately. + * + * @return true iff the invalid values are allowed. + * @see com.vaadin.v7.data.Validatable#isInvalidAllowed() + */ + @Override + public boolean isInvalidAllowed() { + return invalidAllowed; + } + + /** + * Fields allow invalid values by default. In most cases this is wanted, + * because the field otherwise visually forget the user input immediately. + *

+ * In common setting where the user wants to assure the correctness of the + * datasource, but allow temporarily invalid contents in the field, the user + * should add the validators to datasource, that should not allow invalid + * values. The validators are automatically copied to the field when the + * datasource is set. + *

+ * + * @see com.vaadin.v7.data.Validatable#setInvalidAllowed(boolean) + */ + @Override + public void setInvalidAllowed(boolean invalidAllowed) + throws UnsupportedOperationException { + this.invalidAllowed = invalidAllowed; + } + + /** + * Error messages shown by the fields are composites of the error message + * thrown by the superclasses (that is the component error message), + * validation errors and buffered source errors. + * + * @see com.vaadin.ui.AbstractComponent#getErrorMessage() + */ + @Override + public ErrorMessage getErrorMessage() { + + /* + * Check validation errors only if automatic validation is enabled. + * Empty, required fields will generate a validation error containing + * the requiredError string. For these fields the exclamation mark will + * be hidden but the error must still be sent to the client. + */ + Validator.InvalidValueException validationError = null; + if (isValidationVisible()) { + try { + validate(); + } catch (Validator.InvalidValueException e) { + if (!e.isInvisible()) { + validationError = e; + } + } + } + + // Check if there are any systems errors + final ErrorMessage superError = super.getErrorMessage(); + + // Return if there are no errors at all + if (superError == null && validationError == null + && getCurrentBufferedSourceException() == null) { + return null; + } + + // Throw combination of the error types + return new CompositeErrorMessage(new ErrorMessage[] { superError, + AbstractErrorMessage + .getErrorMessageForException(validationError), + AbstractErrorMessage.getErrorMessageForException( + getCurrentBufferedSourceException()) }); + + } + + /* Value change events */ + + private static final Method VALUE_CHANGE_METHOD; + + static { + try { + VALUE_CHANGE_METHOD = Property.ValueChangeListener.class + .getDeclaredMethod("valueChange", + new Class[] { Property.ValueChangeEvent.class }); + } catch (final java.lang.NoSuchMethodException e) { + // This should never happen + throw new java.lang.RuntimeException( + "Internal error finding methods in LegacyAbstractField"); + } + } + + /* + * Adds a value change listener for the field. Don't add a JavaDoc comment + * here, we use the default documentation from the implemented interface. + */ + @Override + public void addValueChangeListener(Property.ValueChangeListener listener) { + addListener(AbstractField.ValueChangeEvent.class, listener, + VALUE_CHANGE_METHOD); + // ensure "automatic immediate handling" works + markAsDirty(); + } + + /** + * @deprecated As of 7.0, replaced by + * {@link #addValueChangeListener(com.vaadin.v7.data.Property.ValueChangeListener)} + **/ + @Override + @Deprecated + public void addListener(Property.ValueChangeListener listener) { + addValueChangeListener(listener); + } + + /* + * Removes a value change listener from the field. Don't add a JavaDoc + * comment here, we use the default documentation from the implemented + * interface. + */ + @Override + public void removeValueChangeListener( + Property.ValueChangeListener listener) { + removeListener(AbstractField.ValueChangeEvent.class, listener, + VALUE_CHANGE_METHOD); + // ensure "automatic immediate handling" works + markAsDirty(); + } + + /** + * @deprecated As of 7.0, replaced by + * {@link #removeValueChangeListener(com.vaadin.v7.data.Property.ValueChangeListener)} + **/ + @Override + @Deprecated + public void removeListener(Property.ValueChangeListener listener) { + removeValueChangeListener(listener); + } + + /** + * Emits the value change event. The value contained in the field is + * validated before the event is created. + */ + protected void fireValueChange(boolean repaintIsNotNeeded) { + fireEvent(new AbstractField.ValueChangeEvent(this)); + if (!repaintIsNotNeeded) { + markAsDirty(); + } + } + + /* Read-only status change events */ + + private static final Method READ_ONLY_STATUS_CHANGE_METHOD; + + static { + try { + READ_ONLY_STATUS_CHANGE_METHOD = Property.ReadOnlyStatusChangeListener.class + .getDeclaredMethod("readOnlyStatusChange", new Class[] { + Property.ReadOnlyStatusChangeEvent.class }); + } catch (final java.lang.NoSuchMethodException e) { + // This should never happen + throw new java.lang.RuntimeException( + "Internal error finding methods in LegacyAbstractField"); + } + } + + /** + * React to read only status changes of the property by requesting a + * repaint. + * + * @see Property.ReadOnlyStatusChangeListener + */ + @Override + public void readOnlyStatusChange(Property.ReadOnlyStatusChangeEvent event) { + boolean readOnly = event.getProperty().isReadOnly(); + + boolean shouldFireChange = isReadOnly() != readOnly + || getState().propertyReadOnly != readOnly; + + getState().propertyReadOnly = readOnly; + + if (shouldFireChange) { + fireReadOnlyStatusChange(); + } + } + + /** + * An Event object specifying the Property whose read-only + * status has changed. + * + * @author Vaadin Ltd. + * @since 3.0 + */ + public static class ReadOnlyStatusChangeEvent extends Component.Event + implements Property.ReadOnlyStatusChangeEvent, Serializable { + + /** + * New instance of text change event. + * + * @param source + * the Source of the event. + */ + public ReadOnlyStatusChangeEvent(AbstractField source) { + super(source); + } + + /** + * Property where the event occurred. + * + * @return the Source of the event. + */ + @Override + public Property getProperty() { + return (Property) getSource(); + } + } + + /* + * Adds a read-only status change listener for the field. Don't add a + * JavaDoc comment here, we use the default documentation from the + * implemented interface. + */ + @Override + public void addReadOnlyStatusChangeListener( + Property.ReadOnlyStatusChangeListener listener) { + addListener(Property.ReadOnlyStatusChangeEvent.class, listener, + READ_ONLY_STATUS_CHANGE_METHOD); + } + + /** + * @deprecated As of 7.0, replaced by + * {@link #addReadOnlyStatusChangeListener(com.vaadin.v7.data.Property.ReadOnlyStatusChangeListener)} + **/ + @Override + @Deprecated + public void addListener(Property.ReadOnlyStatusChangeListener listener) { + addReadOnlyStatusChangeListener(listener); + } + + /* + * Removes a read-only status change listener from the field. Don't add a + * JavaDoc comment here, we use the default documentation from the + * implemented interface. + */ + @Override + public void removeReadOnlyStatusChangeListener( + Property.ReadOnlyStatusChangeListener listener) { + removeListener(Property.ReadOnlyStatusChangeEvent.class, listener, + READ_ONLY_STATUS_CHANGE_METHOD); + } + + /** + * @deprecated As of 7.0, replaced by + * {@link #removeReadOnlyStatusChangeListener(com.vaadin.v7.data.Property.ReadOnlyStatusChangeListener)} + **/ + @Override + @Deprecated + public void removeListener(Property.ReadOnlyStatusChangeListener listener) { + removeReadOnlyStatusChangeListener(listener); + } + + /** + * Emits the read-only status change event. The value contained in the field + * is validated before the event is created. + */ + protected void fireReadOnlyStatusChange() { + fireEvent(new AbstractField.ReadOnlyStatusChangeEvent(this)); + } + + /** + * This method listens to data source value changes and passes the changes + * forwards. + * + * Changes are not forwarded to the listeners of the field during internal + * operations of the field to avoid duplicate notifications. + * + * @param event + * the value change event telling the data source contents have + * changed. + */ + @Override + public void valueChange(Property.ValueChangeEvent event) { + if (!isBuffered()) { + if (committingValueToDataSource) { + boolean propertyNotifiesOfTheBufferedValue = SharedUtil.equals( + event.getProperty().getValue(), getInternalValue()); + if (!propertyNotifiesOfTheBufferedValue) { + /* + * Property (or chained property like PropertyFormatter) now + * reports different value than the one the field has just + * committed to it. In this case we respect the property + * value. + * + * Still, we don't fire value change yet, but instead + * postpone it until "commit" is done. See setValue(Object, + * boolean) and commit(). + */ + readValueFromProperty(event); + valueWasModifiedByDataSourceDuringCommit = true; + } + } else if (!isModified()) { + readValueFromProperty(event); + fireValueChange(false); + } + } + } + + private void readValueFromProperty(Property.ValueChangeEvent event) { + setInternalValue(convertFromModel(event.getProperty().getValue())); + } + + /** + * {@inheritDoc} + */ + @Override + public void focus() { + super.focus(); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.Component.Focusable#getTabIndex() + */ + @Override + public int getTabIndex() { + return getState(false).tabIndex; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.Component.Focusable#setTabIndex(int) + */ + @Override + public void setTabIndex(int tabIndex) { + getState().tabIndex = tabIndex; + } + + /** + * Returns the internal field value, which might not match the data source + * value e.g. if the field has been modified and is not in write-through + * mode. + * + * This method can be overridden by subclasses together with + * {@link #setInternalValue(Object)} to compute internal field value at + * runtime. When doing so, typically also {@link #isModified()} needs to be + * overridden and care should be taken in the management of the empty state + * and buffering support. + * + * @return internal field value + */ + protected T getInternalValue() { + return value; + } + + /** + * Sets the internal field value. This is purely used by LegacyAbstractField + * to change the internal LegacyField value. It does not trigger valuechange + * events. It can be overridden by the inheriting classes to update all + * dependent variables. + * + * Subclasses can also override {@link #getInternalValue()} if necessary. + * + * @param newValue + * the new value to be set. + */ + protected void setInternalValue(T newValue) { + value = newValue; + valueLocale = getLocale(); + if (validators != null && !validators.isEmpty()) { + markAsDirty(); + } + } + + /** + * Notifies the component that it is connected to an application. + * + * @see com.vaadin.ui.Component#attach() + */ + @Override + public void attach() { + super.attach(); + + localeMightHaveChanged(); + if (!isListeningToPropertyEvents) { + addPropertyListeners(); + if (!isModified() && !isBuffered()) { + // Update value from data source + updateValueFromDataSource(); + } + } + } + + @Override + public void setLocale(Locale locale) { + super.setLocale(locale); + localeMightHaveChanged(); + } + + private void localeMightHaveChanged() { + if (!SharedUtil.equals(valueLocale, getLocale())) { + // The locale HAS actually changed + + if (dataSource != null && !isModified()) { + // When we have a data source and the internal value is directly + // read from that we want to update the value + T newInternalValue = convertFromModel( + getPropertyDataSource().getValue()); + if (!SharedUtil.equals(newInternalValue, getInternalValue())) { + setInternalValue(newInternalValue); + fireValueChange(false); + } + } else if (dataSource == null && converter != null) { + /* + * No data source but a converter has been set. The same issues + * as above but we cannot use propertyDataSource. Convert the + * current value back to a model value using the old locale and + * then convert back using the new locale. If this does not + * match the field value we need to set the converted value + * again. + */ + Object convertedValue = convertToModel(getInternalValue(), + valueLocale); + T newinternalValue = convertFromModel(convertedValue); + if (!SharedUtil.equals(getInternalValue(), newinternalValue)) { + setInternalValue(newinternalValue); + fireValueChange(false); + } + } + } + } + + @Override + public void detach() { + super.detach(); + // Stop listening to data source events on detach to avoid a potential + // memory leak. See #6155. + removePropertyListeners(); + } + + /** + * Is this field required. Required fields must filled by the user. + * + * If the field is required, it is visually indicated in the user interface. + * Furthermore, setting field to be required implicitly adds "non-empty" + * validator and thus isValid() == false or any isEmpty() fields. In those + * cases validation errors are not painted as it is obvious that the user + * must fill in the required fields. + * + * On the other hand, for the non-required fields isValid() == true if the + * field isEmpty() regardless of any attached validators. + * + * + * @return true if the field is required, otherwise + * false. + */ + @Override + public boolean isRequired() { + return getState(false).required; + } + + /** + * Sets the field required. Required fields must filled by the user. + * + * If the field is required, it is visually indicated in the user interface. + * Furthermore, setting field to be required implicitly adds "non-empty" + * validator and thus isValid() == false or any isEmpty() fields. In those + * cases validation errors are not painted as it is obvious that the user + * must fill in the required fields. + * + * On the other hand, for the non-required fields isValid() == true if the + * field isEmpty() regardless of any attached validators. + * + * @param required + * Is the field required. + */ + @Override + public void setRequired(boolean required) { + getState().required = required; + } + + /** + * Set the error that is show if this field is required, but empty. When + * setting requiredMessage to be "" or null, no error pop-up or exclamation + * mark is shown for a empty required field. This faults to "". Even in + * those cases isValid() returns false for empty required fields. + * + * @param requiredMessage + * Message to be shown when this field is required, but empty. + */ + @Override + public void setRequiredError(String requiredMessage) { + requiredError = requiredMessage; + markAsDirty(); + } + + @Override + public String getRequiredError() { + return requiredError; + } + + /** + * Gets the error that is shown if the field value cannot be converted to + * the data source type. + * + * @return The error that is shown if conversion of the field value fails + */ + public String getConversionError() { + return conversionError; + } + + /** + * Sets the error that is shown if the field value cannot be converted to + * the data source type. If {0} is present in the message, it will be + * replaced by the simple name of the data source type. If {1} is present in + * the message, it will be replaced by the ConversionException message. + * + * @param valueConversionError + * Message to be shown when conversion of the value fails + */ + public void setConversionError(String valueConversionError) { + this.conversionError = valueConversionError; + markAsDirty(); + } + + @Override + public boolean isEmpty() { + return (getFieldValue() == null); + } + + @Override + public void clear() { + setValue(null); + } + + /** + * Is automatic, visible validation enabled? + * + * If automatic validation is enabled, any validators connected to this + * component are evaluated while painting the component and potential error + * messages are sent to client. If the automatic validation is turned off, + * isValid() and validate() methods still work, but one must show the + * validation in their own code. + * + * @return True, if automatic validation is enabled. + */ + public boolean isValidationVisible() { + return validationVisible; + } + + /** + * Enable or disable automatic, visible validation. + * + * If automatic validation is enabled, any validators connected to this + * component are evaluated while painting the component and potential error + * messages are sent to client. If the automatic validation is turned off, + * isValid() and validate() methods still work, but one must show the + * validation in their own code. + * + * @param validateAutomatically + * True, if automatic validation is enabled. + */ + public void setValidationVisible(boolean validateAutomatically) { + if (validationVisible != validateAutomatically) { + markAsDirty(); + validationVisible = validateAutomatically; + } + } + + /** + * Sets the current buffered source exception. + * + * @param currentBufferedSourceException + */ + public void setCurrentBufferedSourceException( + Buffered.SourceException currentBufferedSourceException) { + this.currentBufferedSourceException = currentBufferedSourceException; + markAsDirty(); + } + + /** + * Gets the current buffered source exception. + * + * @return The current source exception + */ + protected Buffered.SourceException getCurrentBufferedSourceException() { + return currentBufferedSourceException; + } + + /** + * A ready-made {@link ShortcutListener} that focuses the given + * {@link Focusable} (usually a {@link Field}) when the keyboard + * shortcut is invoked. + * + */ + public static class FocusShortcut extends ShortcutListener { + protected Focusable focusable; + + /** + * Creates a keyboard shortcut for focusing the given {@link Focusable} + * using the shorthand notation defined in {@link ShortcutAction}. + * + * @param focusable + * to focused when the shortcut is invoked + * @param shorthandCaption + * caption with keycode and modifiers indicated + */ + public FocusShortcut(Focusable focusable, String shorthandCaption) { + super(shorthandCaption); + this.focusable = focusable; + } + + /** + * Creates a keyboard shortcut for focusing the given {@link Focusable}. + * + * @param focusable + * to focused when the shortcut is invoked + * @param keyCode + * keycode that invokes the shortcut + * @param modifiers + * modifiers required to invoke the shortcut + */ + public FocusShortcut(Focusable focusable, int keyCode, + int... modifiers) { + super(null, keyCode, modifiers); + this.focusable = focusable; + } + + /** + * Creates a keyboard shortcut for focusing the given {@link Focusable}. + * + * @param focusable + * to focused when the shortcut is invoked + * @param keyCode + * keycode that invokes the shortcut + */ + public FocusShortcut(Focusable focusable, int keyCode) { + this(focusable, keyCode, null); + } + + @Override + public void handleAction(Object sender, Object target) { + focusable.focus(); + } + } + + private void updateValueFromDataSource() { + if (dataSource != null) { + + // Gets the correct value from datasource + T newFieldValue; + try { + + // Discards buffer by overwriting from datasource + newFieldValue = convertFromModel(getDataSourceValue()); + + // If successful, remove set the buffering state to be ok + if (getCurrentBufferedSourceException() != null) { + setCurrentBufferedSourceException(null); + } + } catch (final Throwable e) { + // FIXME: What should really be done here if conversion fails? + + // Sets the buffering state + currentBufferedSourceException = new Buffered.SourceException( + this, e); + markAsDirty(); + + // Throws the source exception + throw currentBufferedSourceException; + } + + final boolean wasModified = isModified(); + setModified(false); + + // If the new value differs from the previous one + if (!SharedUtil.equals(newFieldValue, getInternalValue())) { + setInternalValue(newFieldValue); + fireValueChange(false); + } else if (wasModified) { + // If the value did not change, but the modification status did + markAsDirty(); + } + } + } + + /** + * 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 getConverter() { + return converter; + } + + /** + * Sets the converter used to convert the field value to property data + * source type. The converter must have a presentation type that matches the + * field type. + * + * @param converter + * The new converter to use. + */ + public void setConverter(Converter converter) { + this.converter = (Converter) converter; + markAsDirty(); + } + + @Override + protected AbstractFieldState getState() { + return (AbstractFieldState) super.getState(); + } + + @Override + protected AbstractFieldState getState(boolean markAsDirty) { + return (AbstractFieldState) super.getState(markAsDirty); + } + + @Override + public void beforeClientResponse(boolean initial) { + super.beforeClientResponse(initial); + + // Hide the error indicator if needed + getState().hideErrors = shouldHideErrors(); + } + + /** + * Registers this as an event listener for events sent by the data source + * (if any). Does nothing if + * isListeningToPropertyEvents == true. + */ + private void addPropertyListeners() { + if (!isListeningToPropertyEvents) { + if (dataSource instanceof Property.ValueChangeNotifier) { + ((Property.ValueChangeNotifier) dataSource).addListener(this); + } + if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { + ((Property.ReadOnlyStatusChangeNotifier) dataSource) + .addListener(this); + } + isListeningToPropertyEvents = true; + } + } + + /** + * Stops listening to events sent by the data source (if any). Does nothing + * if isListeningToPropertyEvents == false. + */ + private void removePropertyListeners() { + if (isListeningToPropertyEvents) { + if (dataSource instanceof Property.ValueChangeNotifier) { + ((Property.ValueChangeNotifier) dataSource) + .removeListener(this); + } + if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { + ((Property.ReadOnlyStatusChangeNotifier) dataSource) + .removeListener(this); + } + isListeningToPropertyEvents = false; + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.AbstractComponent#readDesign(org.jsoup.nodes .Element, + * com.vaadin.ui.declarative.DesignContext) + */ + @Override + public void readDesign(Element design, DesignContext designContext) { + super.readDesign(design, designContext); + Attributes attr = design.attributes(); + if (design.hasAttr("readonly")) { + setReadOnly(DesignAttributeHandler.readAttribute("readonly", attr, + Boolean.class)); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.AbstractComponent#getCustomAttributes() + */ + @Override + protected Collection getCustomAttributes() { + Collection attributes = super.getCustomAttributes(); + attributes.add("readonly"); + // must be handled by subclasses + attributes.add("value"); + attributes.add("converted-value"); + return attributes; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.AbstractComponent#writeDesign(org.jsoup.nodes.Element + * , com.vaadin.ui.declarative.DesignContext) + */ + @Override + public void writeDesign(Element design, DesignContext designContext) { + super.writeDesign(design, designContext); + AbstractField def = (AbstractField) designContext + .getDefaultInstance(this); + Attributes attr = design.attributes(); + // handle readonly + DesignAttributeHandler.writeAttribute("readonly", attr, + super.isReadOnly(), def.isReadOnly(), Boolean.class); + } + + private static final Logger getLogger() { + return Logger.getLogger(AbstractField.class.getName()); + } +} diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/Field.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/Field.java new file mode 100644 index 0000000000..0756dfaf90 --- /dev/null +++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/Field.java @@ -0,0 +1,150 @@ +/* + * 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.v7.ui; + +import com.vaadin.data.HasRequired; +import com.vaadin.data.HasValue.ValueChange; +import com.vaadin.ui.Component; +import com.vaadin.ui.Component.Focusable; +import com.vaadin.v7.data.BufferedValidatable; +import com.vaadin.v7.data.Property; + +/** + * LegacyField interface is implemented by all legacy field components that have + * a value that the user can change through the user interface. + * + * LegacyField components are built upon the framework defined in the + * LegacyField interface and the {@link com.vaadin.AbstractField} base + * class. + * + * The LegacyField interface inherits the {@link com.vaadin.ui.Component} + * superinterface and also the {@link com.vaadin.ui.Property} interface to have + * a value for the field. + * + * @author Vaadin Ltd. + * + * @param + * the type of values in the field, which might not be the same type + * as that of the data source if converters are used + * + * @deprecated This interface is, apart from the rename, identical to the Vaadin + * 7 {@code com.vaadin.ui.Field}. It is provided for compatibility + * and migration purposes. As of 8.0, new field components should + * extend {@link com.vaadin.ui.AbstractField} instead. + */ +@Deprecated +public interface Field extends Component, BufferedValidatable, + Property, Property.ValueChangeNotifier, Property.ValueChangeListener, + Property.Editor, Focusable, HasRequired { + /** + * Is this field required. + * + * Required fields must filled by the user. + * + * @return true if the field is required,otherwise + * false. + * @since 3.1 + */ + @Override + public boolean isRequired(); + + /** + * Sets the field required. Required fields must filled by the user. + * + * @param required + * Is the field required. + * @since 3.1 + */ + @Override + public void setRequired(boolean required); + + /** + * Sets the error message to be displayed if a required field is empty. + * + * @param requiredMessage + * Error message. + * @since 5.2.6 + */ + public void setRequiredError(String requiredMessage); + + /** + * Gets the error message that is to be displayed if a required field is + * empty. + * + * @return Error message. + * @since 5.2.6 + */ + public String getRequiredError(); + + /** + * An Event object specifying the LegacyField whose value has + * been changed. + * + * @author Vaadin Ltd. + * @since 3.0 + * + * @deprecated As of 8.0, replaced by {@link ValueChange}. + */ + @Deprecated + @SuppressWarnings("serial") + public static class ValueChangeEvent extends Component.Event + implements Property.ValueChangeEvent { + + /** + * Constructs a new event object with the specified source field object. + * + * @param source + * the field that caused the event. + */ + public ValueChangeEvent(Field source) { + super(source); + } + + /** + * Gets the Property which triggered the event. + * + * @return the Source Property of the event. + */ + @Override + public Property getProperty() { + return (Property) getSource(); + } + + } + + /** + * Is the field empty? + * + * In general, "empty" state is same as null. As an exception, TextField + * also treats empty string as "empty". + * + * @since 7.4 + * @return true if the field is empty, false otherwise + */ + public boolean isEmpty(); + + /** + * Clears the value of the field. + *

+ * The field value is typically reset to the initial value of the field. + * Calling {@link #isEmpty()} on a cleared field must always returns true. + * + * @since 7.4 + */ + public void clear(); + +} diff --git a/compatibility-server/src/test/java/com/vaadin/v7/tests/VaadinClasses.java b/compatibility-server/src/test/java/com/vaadin/v7/tests/VaadinClasses.java new file mode 100644 index 0000000000..b37f1bbe86 --- /dev/null +++ b/compatibility-server/src/test/java/com/vaadin/v7/tests/VaadinClasses.java @@ -0,0 +1,21 @@ +package com.vaadin.v7.tests; + +import java.io.IOException; +import java.util.List; + +import com.vaadin.v7.ui.Field; + +@SuppressWarnings("deprecation") +public class VaadinClasses { + + public static List> getFields() { + try { + return com.vaadin.tests.VaadinClasses.findClasses(Field.class, + "com.vaadin.ui"); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/abstractfield/AbstractFieldValueChangeTestBase.java b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/abstractfield/AbstractFieldValueChangeTestBase.java new file mode 100644 index 0000000000..e37e19aecb --- /dev/null +++ b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/abstractfield/AbstractFieldValueChangeTestBase.java @@ -0,0 +1,130 @@ +package com.vaadin.v7.tests.server.component.abstractfield; + +import org.easymock.EasyMock; +import org.junit.Test; + +import com.vaadin.v7.data.Property.ValueChangeEvent; +import com.vaadin.v7.data.Property.ValueChangeListener; +import com.vaadin.v7.data.Property.ValueChangeNotifier; +import com.vaadin.v7.data.util.ObjectProperty; +import com.vaadin.v7.ui.AbstractField; + +/** + * Base class for tests for checking that value change listeners for fields are + * not called exactly once when they should be, and not at other times. + * + * Does not check all cases (e.g. properties that do not implement + * {@link ValueChangeNotifier}). + * + * Subclasses should implement {@link #setValue()} and call + * super.setValue(LegacyAbstractField). Also, subclasses should + * typically override {@link #setValue(AbstractField)} to set the field + * value via changeVariables(). + */ +public abstract class AbstractFieldValueChangeTestBase { + + private AbstractField field; + private ValueChangeListener listener; + + protected void setUp(AbstractField field) { + this.field = field; + listener = EasyMock.createStrictMock(ValueChangeListener.class); + + } + + protected ValueChangeListener getListener() { + return listener; + } + + /** + * Test that listeners are not called when they have been unregistered. + */ + @Test + public void testRemoveListener() { + getField().setPropertyDataSource(new ObjectProperty("")); + getField().setBuffered(false); + + // Expectations and start test + listener.valueChange(EasyMock.isA(ValueChangeEvent.class)); + EasyMock.replay(listener); + + // Add listener and set the value -> should end up in listener once + getField().addListener(listener); + setValue(getField()); + + // Ensure listener was called once + EasyMock.verify(listener); + + // Remove the listener and set the value -> should not end up in + // listener + getField().removeListener(listener); + setValue(getField()); + + // Ensure listener still has been called only once + EasyMock.verify(listener); + } + + /** + * Common unbuffered case: both writeThrough (auto-commit) and readThrough + * are on. Calling commit() should not cause notifications. + * + * Using the readThrough mode allows changes made to the property value to + * be seen in some cases also when there is no notification of value change + * from the property. + * + * LegacyField value change notifications closely mirror value changes of + * the data source behind the field. + */ + @Test + public void testNonBuffered() { + getField().setPropertyDataSource(new ObjectProperty("")); + getField().setBuffered(false); + + expectValueChangeFromSetValueNotCommit(); + } + + /** + * Fully buffered use where the data source is neither read nor modified + * during editing, and is updated at commit(). + * + * LegacyField value change notifications reflect the buffered value in the + * field, not the original data source value changes. + */ + public void testBuffered() { + getField().setPropertyDataSource(new ObjectProperty("")); + getField().setBuffered(true); + + expectValueChangeFromSetValueNotCommit(); + } + + protected void expectValueChangeFromSetValueNotCommit() { + // Expectations and start test + listener.valueChange(EasyMock.isA(ValueChangeEvent.class)); + EasyMock.replay(listener); + + // Add listener and set the value -> should end up in listener once + getField().addListener(listener); + setValue(getField()); + + // Ensure listener was called once + EasyMock.verify(listener); + + // commit + getField().commit(); + + // Ensure listener was not called again + EasyMock.verify(listener); + } + + protected AbstractField getField() { + return field; + } + + /** + * Override in subclasses to set value with changeVariables(). + */ + protected void setValue(AbstractField field) { + field.setValue((T) "newValue"); + } + +} diff --git a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/abstractfield/FieldDefaultValuesTest.java b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/abstractfield/FieldDefaultValuesTest.java new file mode 100644 index 0000000000..a53e3bbbd1 --- /dev/null +++ b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/abstractfield/FieldDefaultValuesTest.java @@ -0,0 +1,76 @@ +/* + * 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.v7.tests.server.component.abstractfield; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.ui.Slider; +import com.vaadin.v7.tests.VaadinClasses; +import com.vaadin.v7.ui.Field; + +public class FieldDefaultValuesTest { + + @Test + public void testFieldsHaveDefaultValueAfterClear() throws Exception { + for (Field field : createFields()) { + Object originalValue = field.getValue(); + + field.clear(); + + Object clearedValue = field.getValue(); + + Assert.assertEquals( + "Expected to get default value after clearing " + + field.getClass().getName(), + originalValue, clearedValue); + } + } + + @Test + public void testFieldsAreEmptyAfterClear() throws Exception { + for (Field field : createFields()) { + field.clear(); + + if (field instanceof Slider) { + Assert.assertFalse( + "Slider should not be empty even after being cleared", + field.isEmpty()); + + } else { + Assert.assertTrue( + field.getClass().getName() + + " should be empty after being cleared", + field.isEmpty()); + } + } + } + + @SuppressWarnings("rawtypes") + private static List> createFields() + throws InstantiationException, IllegalAccessException { + List> fieldInstances = new ArrayList>(); + + for (Class fieldType : VaadinClasses.getFields()) { + fieldInstances.add(fieldType.newInstance()); + } + return fieldInstances; + } + +} diff --git a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/textfield/TextFieldValueChangeTest.java b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/textfield/TextFieldValueChangeTest.java index bb286296b4..9a35dbeacd 100644 --- a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/textfield/TextFieldValueChangeTest.java +++ b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/component/textfield/TextFieldValueChangeTest.java @@ -8,9 +8,9 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.vaadin.tests.server.components.AbstractFieldValueChangeTestBase; import com.vaadin.v7.data.Property.ValueChangeEvent; import com.vaadin.v7.data.util.ObjectProperty; +import com.vaadin.v7.tests.server.component.abstractfield.AbstractFieldValueChangeTestBase; import com.vaadin.v7.ui.AbstractField; import com.vaadin.v7.ui.TextField; diff --git a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/components/ComboBoxValueChangeTest.java b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/components/ComboBoxValueChangeTest.java index dbba1a0e9f..ffb46c519a 100644 --- a/compatibility-server/src/test/java/com/vaadin/v7/tests/server/components/ComboBoxValueChangeTest.java +++ b/compatibility-server/src/test/java/com/vaadin/v7/tests/server/components/ComboBoxValueChangeTest.java @@ -4,8 +4,8 @@ import org.junit.Before; import com.vaadin.server.ServerRpcManager; import com.vaadin.server.ServerRpcMethodInvocation; -import com.vaadin.tests.server.components.AbstractFieldValueChangeTestBase; import com.vaadin.v7.shared.ui.combobox.ComboBoxServerRpc; +import com.vaadin.v7.tests.server.component.abstractfield.AbstractFieldValueChangeTestBase; import com.vaadin.v7.ui.AbstractField; import com.vaadin.v7.ui.ComboBox; diff --git a/server/src/main/java/com/vaadin/event/FieldEvents.java b/server/src/main/java/com/vaadin/event/FieldEvents.java index a8e5ca8191..ba84e96b3f 100644 --- a/server/src/main/java/com/vaadin/event/FieldEvents.java +++ b/server/src/main/java/com/vaadin/event/FieldEvents.java @@ -24,7 +24,6 @@ import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc; import com.vaadin.ui.Component; import com.vaadin.ui.Component.Event; import com.vaadin.util.ReflectTools; -import com.vaadin.v7.ui.Field; /** * Interface that serves as a wrapper for {@link Field} related events. @@ -43,7 +42,7 @@ public interface FieldEvents { public interface FocusNotifier extends Serializable { /** * Adds a FocusListener to the Component which gets fired - * when a LegacyField receives keyboard focus. + * when a Field receives keyboard focus. * * @param listener * @see FocusListener @@ -74,7 +73,7 @@ public interface FieldEvents { public interface BlurNotifier extends Serializable { /** * Adds a BlurListener to the Component which gets fired - * when a LegacyField loses keyboard focus. + * when a Field loses keyboard focus. * * @param listener * @see BlurListener @@ -95,7 +94,7 @@ public interface FieldEvents { /** * FocusEvent class for holding additional event information. - * Fired when a LegacyField receives keyboard focus. + * Fired when a Field receives keyboard focus. * * @since 6.2 */ @@ -114,7 +113,7 @@ public interface FieldEvents { /** * FocusListener interface for listening for - * FocusEvent fired by a LegacyField. + * FocusEvent fired by a Field. * * @see FocusEvent * @since 6.2 @@ -135,7 +134,7 @@ public interface FieldEvents { /** * BlurEvent class for holding additional event information. - * Fired when a LegacyField loses keyboard focus. + * Fired when a Field loses keyboard focus. * * @since 6.2 */ @@ -154,7 +153,7 @@ public interface FieldEvents { /** * BlurListener interface for listening for - * BlurEvent fired by a LegacyField. + * BlurEvent fired by a Field. * * @see BlurEvent * @since 6.2 diff --git a/server/src/main/java/com/vaadin/v7/ui/AbstractField.java b/server/src/main/java/com/vaadin/v7/ui/AbstractField.java deleted file mode 100644 index 33f262d581..0000000000 --- a/server/src/main/java/com/vaadin/v7/ui/AbstractField.java +++ /dev/null @@ -1,1810 +0,0 @@ -/* - * 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.v7.ui; - -import java.io.Serializable; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.logging.Logger; - -import org.jsoup.nodes.Attributes; -import org.jsoup.nodes.Element; - -import com.vaadin.event.Action; -import com.vaadin.event.ShortcutAction; -import com.vaadin.event.ShortcutListener; -import com.vaadin.server.AbstractErrorMessage; -import com.vaadin.server.CompositeErrorMessage; -import com.vaadin.server.ErrorMessage; -import com.vaadin.shared.AbstractFieldState; -import com.vaadin.shared.util.SharedUtil; -import com.vaadin.ui.AbstractComponent; -import com.vaadin.ui.Component; -import com.vaadin.ui.declarative.DesignAttributeHandler; -import com.vaadin.ui.declarative.DesignContext; -import com.vaadin.v7.data.Buffered; -import com.vaadin.v7.data.Property; -import com.vaadin.v7.data.Validatable; -import com.vaadin.v7.data.Validator; -import com.vaadin.v7.data.Validator.InvalidValueException; -import com.vaadin.v7.data.util.converter.Converter; -import com.vaadin.v7.data.util.converter.Converter.ConversionException; -import com.vaadin.v7.data.util.converter.ConverterUtil; - -/** - *

- * Abstract field component for implementing buffered property editors. The - * field may hold an internal value, or it may be connected to any data source - * that implements the {@link com.vaadin.v7.data.Property}interface. - * LegacyAbstractField implements that interface itself, too, so - * accessing the Property value represented by it is straightforward. - *

- * - *

- * LegacyAbstractField also provides the {@link com.vaadin.v7.data.Buffered} - * interface for buffering the data source value. By default the LegacyField is - * in write through-mode and {@link #setWriteThrough(boolean)}should be called - * to enable buffering. - *

- * - *

- * The class also supports {@link com.vaadin.v7.data.Validator validators} - * to make sure the value contained in the field is valid. - *

- * - * @author Vaadin Ltd. - * @since 3.0 - * - * @deprecated This class is, apart from the rename, identical to the Vaadin 7 - * {@code com.vaadin.ui.AbstractField}. It is provided for - * compatibility and migration purposes. As of 8.0, new field - * implementations should extend the new - * {@link com.vaadin.ui.AbstractField} instead. - */ -@SuppressWarnings("serial") -@Deprecated -public abstract class AbstractField extends AbstractComponent - implements Field, Property.ReadOnlyStatusChangeListener, - Property.ReadOnlyStatusChangeNotifier, Action.ShortcutNotifier { - - /* Private members */ - - /** - * Value of the abstract field. - */ - private T value; - - /** - * A converter used to convert from the data model type to the field type - * and vice versa. - */ - private Converter converter = null; - /** - * Connected data-source. - */ - private Property dataSource = null; - - /** - * The list of validators. - */ - private LinkedList validators = null; - - /** - * True if field is in buffered mode, false otherwise - */ - private boolean buffered; - - /** - * Flag to indicate that the field is currently committing its value to the - * datasource. - */ - private boolean committingValueToDataSource = false; - - /** - * Current source exception. - */ - private Buffered.SourceException currentBufferedSourceException = null; - - /** - * Are the invalid values allowed in fields ? - */ - private boolean invalidAllowed = true; - - /** - * Are the invalid values committed ? - */ - private boolean invalidCommitted = false; - - /** - * The error message for the exception that is thrown when the field is - * required but empty. - */ - private String requiredError = ""; - - /** - * The error message that is shown when the field value cannot be converted. - */ - private String conversionError = "Could not convert value to {0}"; - - /** - * Is automatic validation enabled. - */ - private boolean validationVisible = true; - - private boolean valueWasModifiedByDataSourceDuringCommit; - - /** - * Whether this field is currently registered as listening to events from - * its data source. - * - * @see #setPropertyDataSource(Property) - * @see #addPropertyListeners() - * @see #removePropertyListeners() - */ - private boolean isListeningToPropertyEvents = false; - - /** - * The locale used when setting the value. - */ - private Locale valueLocale = null; - - /* Component basics */ - - /* - * Paints the field. Don't add a JavaDoc comment here, we use the default - * documentation from the implemented interface. - */ - - /** - * Returns true if the error indicator be hidden when painting the component - * even when there are errors. - * - * This is a mostly internal method, but can be overridden in subclasses - * e.g. if the error indicator should also be shown for empty fields in some - * cases. - * - * @return true to hide the error indicator, false to use the normal logic - * to show it when there are errors - */ - protected boolean shouldHideErrors() { - // getErrorMessage() can still return something else than null based on - // validation etc. - return isRequired() && isEmpty() && getComponentError() == null; - } - - /** - * Returns the type of the LegacyField. The methods getValue - * and setValue must be compatible with this type: one must be - * able to safely cast the value returned from getValue to the - * given type and pass any variable assignable to this type as an argument - * to setValue. - * - * @return the type of the LegacyField - */ - @Override - public abstract Class getType(); - - /** - * The abstract field is read only also if the data source is in read only - * mode. - */ - @Override - public boolean isReadOnly() { - return super.isReadOnly() - || (dataSource != null && dataSource.isReadOnly()); - } - - /** - * Changes the readonly state and throw read-only status change events. - * - * @see com.vaadin.ui.Component#setReadOnly(boolean) - */ - @Override - public void setReadOnly(boolean readOnly) { - super.setReadOnly(readOnly); - fireReadOnlyStatusChange(); - } - - /** - * Tests if the invalid data is committed to datasource. - * - * @see com.vaadin.v7.data.BufferedValidatable#isInvalidCommitted() - */ - @Override - public boolean isInvalidCommitted() { - return invalidCommitted; - } - - /** - * Sets if the invalid data should be committed to datasource. - * - * @see com.vaadin.v7.data.BufferedValidatable#setInvalidCommitted(boolean) - */ - @Override - public void setInvalidCommitted(boolean isCommitted) { - invalidCommitted = isCommitted; - } - - /* - * Saves the current value to the data source Don't add a JavaDoc comment - * here, we use the default documentation from the implemented interface. - */ - @Override - public void commit() - throws Buffered.SourceException, InvalidValueException { - if (dataSource != null && !dataSource.isReadOnly()) { - if ((isInvalidCommitted() || isValid())) { - try { - - // Commits the value to datasource. - valueWasModifiedByDataSourceDuringCommit = false; - committingValueToDataSource = true; - getPropertyDataSource().setValue(getConvertedValue()); - } catch (final Throwable e) { - - // Sets the buffering state. - SourceException sourceException = new Buffered.SourceException( - this, e); - setCurrentBufferedSourceException(sourceException); - - // Throws the source exception. - throw sourceException; - } finally { - committingValueToDataSource = false; - } - } else { - /* - * An invalid value and we don't allow them, throw the exception - */ - validate(); - } - } - - // The abstract field is not modified anymore - if (isModified()) { - setModified(false); - } - - // If successful, remove set the buffering state to be ok - if (getCurrentBufferedSourceException() != null) { - setCurrentBufferedSourceException(null); - } - - if (valueWasModifiedByDataSourceDuringCommit) { - valueWasModifiedByDataSourceDuringCommit = false; - fireValueChange(false); - } - - } - - /* - * Updates the value from the data source. Don't add a JavaDoc comment here, - * we use the default documentation from the implemented interface. - */ - @Override - public void discard() throws Buffered.SourceException { - updateValueFromDataSource(); - } - - /** - * Gets the value from the data source. This is only here because of clarity - * in the code that handles both the data model value and the field value. - * - * @return The value of the property data source - */ - private Object getDataSourceValue() { - return dataSource.getValue(); - } - - /** - * Returns the field value. This is always identical to {@link #getValue()} - * and only here because of clarity in the code that handles both the data - * model value and the field value. - * - * @return The value of the field - */ - private T getFieldValue() { - // Give the value from abstract buffers if the field if possible - if (dataSource == null || isBuffered() || isModified()) { - return getInternalValue(); - } - - // There is no buffered value so use whatever the data model provides - return convertFromModel(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 - * interface. - */ - @Override - public boolean isModified() { - return getState(false).modified; - } - - private void setModified(boolean modified) { - getState().modified = modified; - } - - /** - * Sets the buffered mode of this LegacyField. - *

- * When the field is in buffered mode, changes will not be committed to the - * property data source until {@link #commit()} is called. - *

- *

- * Setting buffered mode from true to false will commit any pending changes. - *

- *

- * - *

- * - * @since 7.0.0 - * @param buffered - * true if buffered mode should be turned on, false otherwise - */ - @Override - public void setBuffered(boolean buffered) { - if (this.buffered == buffered) { - return; - } - this.buffered = buffered; - if (!buffered) { - commit(); - } - } - - /** - * Checks the buffered mode of this LegacyField. - * - * @return true if buffered mode is on, false otherwise - */ - @Override - public boolean isBuffered() { - return buffered; - } - - /* Property interface implementation */ - - /** - * Gets the current value of the field. - * - *

- * This is the visible, modified and possible invalid value the user have - * entered to the field. - *

- * - *

- * 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 converted value, - * use {@link #getConvertedValue()} and to access the value of the property - * data source, use {@link Property#getValue()} for the property data - * source. - *

- * - *

- * Since Vaadin 7.0, no implicit conversions between other data types and - * String are performed, but a converter is used if set. - *

- * - * @return the current value of the field. - */ - @Override - public T getValue() { - return getFieldValue(); - } - - /** - * Sets the value of the field. - * - * @param newFieldValue - * the New value of the field. - * @throws Property.ReadOnlyException - */ - @Override - public void setValue(T newFieldValue) throws Property.ReadOnlyException, - Converter.ConversionException { - setValue(newFieldValue, false); - } - - /** - * Sets the value of the field. - * - * @param newFieldValue - * the New value of the field. - * @param repaintIsNotNeeded - * True iff caller is sure that repaint is not needed. - * @throws Property.ReadOnlyException - * @throws Converter.ConversionException - * @throws InvalidValueException - */ - protected void setValue(T newFieldValue, boolean repaintIsNotNeeded) { - setValue(newFieldValue, repaintIsNotNeeded, false); - } - - /** - * Sets the value of the field. - * - * @since 7.5.7 - * @param newFieldValue - * the New value of the field. - * @param repaintIsNotNeeded - * True iff caller is sure that repaint is not needed. - * @param ignoreReadOnly - * True iff if the read-only check should be ignored - * @throws Property.ReadOnlyException - * @throws Converter.ConversionException - * @throws InvalidValueException - */ - protected void setValue(T newFieldValue, boolean repaintIsNotNeeded, - boolean ignoreReadOnly) throws Property.ReadOnlyException, - Converter.ConversionException, InvalidValueException { - - if (!SharedUtil.equals(newFieldValue, getInternalValue())) { - - // Read only fields can not be changed - if (!ignoreReadOnly && isReadOnly()) { - throw new Property.ReadOnlyException(); - } - try { - T doubleConvertedFieldValue = convertFromModel( - convertToModel(newFieldValue)); - if (!SharedUtil.equals(newFieldValue, - doubleConvertedFieldValue)) { - newFieldValue = doubleConvertedFieldValue; - repaintIsNotNeeded = false; - } - } catch (Throwable t) { - // Ignore exceptions in the conversion at this stage. Any - // conversion error will be handled later by validate(). - } - - // Repaint is needed even when the client thinks that it knows the - // new state if validity of the component may change - if (repaintIsNotNeeded && (isRequired() || hasValidators() - || getConverter() != null)) { - repaintIsNotNeeded = false; - } - - if (!isInvalidAllowed()) { - /* - * If invalid values are not allowed the value must be validated - * before it is set. If validation fails, the - * InvalidValueException is thrown and the internal value is not - * updated. - */ - validate(newFieldValue); - } - - // Changes the value - setInternalValue(newFieldValue); - setModified(dataSource != null); - - valueWasModifiedByDataSourceDuringCommit = false; - // In not buffering, try to commit - if (!isBuffered() && dataSource != null - && (isInvalidCommitted() || isValid())) { - try { - - // Commits the value to datasource - committingValueToDataSource = true; - getPropertyDataSource() - .setValue(convertToModel(newFieldValue)); - - // The buffer is now unmodified - setModified(false); - - } catch (final Throwable e) { - - // Sets the buffering state - currentBufferedSourceException = new Buffered.SourceException( - this, e); - markAsDirty(); - - // Throws the source exception - throw currentBufferedSourceException; - } finally { - committingValueToDataSource = false; - } - } - - // If successful, remove set the buffering state to be ok - if (getCurrentBufferedSourceException() != null) { - setCurrentBufferedSourceException(null); - } - - if (valueWasModifiedByDataSourceDuringCommit) { - /* - * Value was modified by datasource. Force repaint even if - * repaint was not requested. - */ - valueWasModifiedByDataSourceDuringCommit = repaintIsNotNeeded = false; - } - - // Fires the value change - fireValueChange(repaintIsNotNeeded); - - } - } - - @Deprecated - static boolean equals(Object value1, Object value2) { - return SharedUtil.equals(value1, value2); - } - - /* External data source */ - - /** - * Gets the current data source of the field, if any. - * - * @return the current data source as a Property, or null if - * none defined. - */ - @Override - public Property getPropertyDataSource() { - return dataSource; - } - - /** - *

- * Sets the specified Property as the data source for the field. All - * uncommitted changes are replaced with a value from the new data source. - *

- * - *

- * If the datasource has any validators, the same validators are added to - * the field. Because the default behavior of the field is to allow invalid - * values, but not to allow committing them, this only adds visual error - * messages to fields and do not allow committing them as long as the value - * is invalid. After the value is valid, the error message is not shown and - * the commit can be done normally. - *

- * - *

- * If the data source implements - * {@link com.vaadin.v7.data.Property.ValueChangeNotifier} and/or - * {@link com.vaadin.v7.data.Property.ReadOnlyStatusChangeNotifier}, the field - * registers itself as a listener and updates itself according to the events - * it receives. To avoid memory leaks caused by references to a field no - * longer in use, the listener registrations are removed on - * {@link AbstractField#detach() detach} and re-added on - * {@link AbstractField#attach() attach}. - *

- * - *

- * Note: before 6.5 we actually called discard() method in the beginning of - * the method. This was removed to simplify implementation, avoid excess - * calls to backing property and to avoid odd value change events that were - * previously fired (developer expects 0-1 value change events if this - * method is called). Some complex field implementations might now need to - * override this method to do housekeeping similar to discard(). - *

- * - * @param newDataSource - * the new data source Property. - */ - @Override - public void setPropertyDataSource(Property newDataSource) { - - // Saves the old value - final Object oldValue = getInternalValue(); - - // Stop listening to the old data source - removePropertyListeners(); - - // Sets the new data source - dataSource = newDataSource; - getState().propertyReadOnly = dataSource == null ? false - : dataSource.isReadOnly(); - - // Check if the current converter is compatible. - if (newDataSource != null - && !ConverterUtil.canConverterPossiblyHandle( - getConverter(), getType(), newDataSource.getType())) { - // There is no converter set or there is no way the current - // converter can be compatible. - setConverter(newDataSource.getType()); - } - // Gets the value from source. This requires that a valid converter has - // been set. - try { - if (dataSource != null) { - T fieldValue = convertFromModel(getDataSourceValue()); - setInternalValue(fieldValue); - } - setModified(false); - if (getCurrentBufferedSourceException() != null) { - setCurrentBufferedSourceException(null); - } - } catch (final Throwable e) { - setCurrentBufferedSourceException( - new Buffered.SourceException(this, e)); - setModified(true); - throw getCurrentBufferedSourceException(); - } - - // Listen to new data source if possible - addPropertyListeners(); - - // Copy the validators from the data source - if (dataSource instanceof Validatable) { - final Collection validators = ((Validatable) dataSource) - .getValidators(); - if (validators != null) { - for (final Iterator i = validators.iterator(); i - .hasNext();) { - addValidator(i.next()); - } - } - } - - // Fires value change if the value has changed - T value = getInternalValue(); - if ((value != oldValue) && ((value != null && !value.equals(oldValue)) - || value == null)) { - fireValueChange(false); - } - } - - /** - * Retrieves a converter for the field from the converter factory defined - * for the application. Clears the converter if no application reference is - * available or if the factory returns null. - * - * @param datamodelType - * The type of the data model that we want to be able to convert - * from - */ - public void setConverter(Class datamodelType) { - Converter c = (Converter) ConverterUtil - .getConverter(getType(), datamodelType, getSession()); - setConverter(c); - } - - /** - * Convert the given value from the data source type to the UI type. - * - * @param newValue - * The data source value to convert. - * @return The converted value that is compatible with the UI type or the - * original value if its type is compatible and no converter is set. - * @throws Converter.ConversionException - * if there is no converter and the type is not compatible with - * the data source type. - */ - private T convertFromModel(Object newValue) { - return convertFromModel(newValue, getLocale()); - } - - /** - * Convert the given value from the data source type to the UI type. - * - * @param newValue - * The data source value to convert. - * @return The converted value that is compatible with the UI type or the - * original value if its type is compatible and no converter is set. - * @throws Converter.ConversionException - * if there is no converter and the type is not compatible with - * the data source type. - */ - private T convertFromModel(Object newValue, Locale locale) { - return ConverterUtil.convertFromModel(newValue, getType(), - getConverter(), locale); - } - - /** - * Convert the given value from the UI type to the data source type. - * - * @param fieldValue - * The value to convert. Typically returned by - * {@link #getFieldValue()} - * @return The converted value that is compatible with the data source type. - * @throws Converter.ConversionException - * if there is no converter and the type is not compatible with - * the data source type. - */ - private Object convertToModel(T fieldValue) - throws Converter.ConversionException { - return convertToModel(fieldValue, getLocale()); - } - - /** - * Convert the given value from the UI type to the data source type. - * - * @param fieldValue - * The value to convert. Typically returned by - * {@link #getFieldValue()} - * @param locale - * The locale to use for the conversion - * @return The converted value that is compatible with the data source type. - * @throws Converter.ConversionException - * if there is no converter and the type is not compatible with - * the data source type. - */ - private Object convertToModel(T fieldValue, Locale locale) - throws Converter.ConversionException { - Class modelType = getModelType(); - try { - return ConverterUtil.convertToModel(fieldValue, - (Class) modelType, getConverter(), locale); - } catch (ConversionException e) { - throw new ConversionException(getConversionError(modelType, e), e); - } - } - - /** - * Retrieves the type of the currently used data model. If the field has no - * data source then the model type of the converter is used. - * - * @since 7.1 - * @return The type of the currently used data model or null if no data - * source or converter is set. - */ - protected Class getModelType() { - Property pd = getPropertyDataSource(); - if (pd != null) { - return pd.getType(); - } else if (getConverter() != null) { - return getConverter().getModelType(); - } - return null; - } - - /** - * Returns the conversion error with {0} replaced by the data source type - * and {1} replaced by the exception (localized) message. - * - * @since 7.1 - * @param dataSourceType - * the type of the data source - * @param e - * a conversion exception which can provide additional - * information - * @return The value conversion error string with parameters replaced. - */ - protected String getConversionError(Class dataSourceType, - ConversionException e) { - String conversionError = getConversionError(); - - if (conversionError != null) { - if (dataSourceType != null) { - conversionError = conversionError.replace("{0}", - dataSourceType.getSimpleName()); - } - if (e != null) { - conversionError = conversionError.replace("{1}", - e.getLocalizedMessage()); - } - } - return conversionError; - } - - /** - * Returns the current value (as returned by {@link #getValue()}) converted - * to the data source type. - *

- * This returns the same as {@link AbstractField#getValue()} if no - * converter has been set. The value is not necessarily the same as the data - * source value e.g. if the field is in buffered mode and has been modified. - *

- * - * @return The converted value that is compatible with the data source type - */ - public Object getConvertedValue() { - return convertToModel(getFieldValue()); - } - - /** - * Sets the value of the field using a value of the data source type. The - * value given is converted to the field type and then assigned to the - * field. This will update the property data source in the same way as when - * {@link #setValue(Object)} is called. - * - * @param value - * The value to set. Must be the same type as the data source. - */ - public void setConvertedValue(Object value) { - setValue(convertFromModel(value)); - } - - /* Validation */ - - /** - * Adds a new validator for the field's value. All validators added to a - * field are checked each time the its value changes. - * - * @param validator - * the new validator to be added. - */ - @Override - public void addValidator(Validator validator) { - if (validators == null) { - validators = new LinkedList<>(); - } - validators.add(validator); - markAsDirty(); - } - - /** - * Gets the validators of the field. - * - * @return An unmodifiable collection that holds all validators for the - * field. - */ - @Override - public Collection getValidators() { - if (validators == null) { - return Collections.emptyList(); - } else { - return Collections.unmodifiableCollection(validators); - } - } - - private boolean hasValidators() { - return validators != null && !validators.isEmpty(); - } - - /** - * Removes the validator from the field. - * - * @param validator - * the validator to remove. - */ - @Override - public void removeValidator(Validator validator) { - if (validators != null) { - validators.remove(validator); - } - markAsDirty(); - } - - /** - * Removes all validators from the field. - */ - @Override - public void removeAllValidators() { - if (validators != null) { - validators.clear(); - } - markAsDirty(); - } - - /** - * Tests the current value against registered validators if the field is not - * empty. If the field is empty it is considered valid if it is not required - * and invalid otherwise. Validators are never checked for empty fields. - * - * In most cases, {@link #validate()} should be used instead of - * {@link #isValid()} to also get the error message. - * - * @return true if all registered validators claim that the - * current value is valid or if the field is empty and not required, - * false otherwise. - */ - @Override - public boolean isValid() { - - try { - validate(); - return true; - } catch (InvalidValueException e) { - return false; - } - } - - /** - * Checks the validity of the LegacyField. - * - * A field is invalid if it is set as required (using - * {@link #setRequired(boolean)} and is empty, if one or several of the - * validators added to the field indicate it is invalid or if the value - * cannot be converted provided a converter has been set. - * - * The "required" validation is a built-in validation feature. If the field - * is required and empty this method throws an EmptyValueException with the - * error message set using {@link #setRequiredError(String)}. - * - * @see com.vaadin.v7.data.Validatable#validate() - */ - @Override - public void validate() throws Validator.InvalidValueException { - - if (isRequired() && isEmpty()) { - throw new Validator.EmptyValueException(requiredError); - } - validate(getFieldValue()); - } - - /** - * Validates that the given value pass the validators for the field. - *

- * This method does not check the requiredness of the field. - * - * @param fieldValue - * The value to check - * @throws Validator.InvalidValueException - * if one or several validators fail - */ - protected void validate(T fieldValue) - throws Validator.InvalidValueException { - - Object valueToValidate = fieldValue; - - // If there is a converter we start by converting the value as we want - // to validate the converted value - if (getConverter() != null) { - try { - valueToValidate = getConverter().convertToModel(fieldValue, - getModelType(), getLocale()); - } catch (ConversionException e) { - throw new InvalidValueException( - getConversionError(getConverter().getModelType(), e)); - } - } - - List validationExceptions = new ArrayList<>(); - if (validators != null) { - // Gets all the validation errors - for (Validator v : validators) { - try { - v.validate(valueToValidate); - } catch (final Validator.InvalidValueException e) { - validationExceptions.add(e); - } - } - } - - // If there were no errors - if (validationExceptions.isEmpty()) { - return; - } - - // If only one error occurred, throw it forwards - if (validationExceptions.size() == 1) { - throw validationExceptions.get(0); - } - - InvalidValueException[] exceptionArray = validationExceptions.toArray( - new InvalidValueException[validationExceptions.size()]); - - // Create a composite validator and include all exceptions - throw new Validator.InvalidValueException(null, exceptionArray); - } - - /** - * Fields allow invalid values by default. In most cases this is wanted, - * because the field otherwise visually forget the user input immediately. - * - * @return true iff the invalid values are allowed. - * @see com.vaadin.v7.data.Validatable#isInvalidAllowed() - */ - @Override - public boolean isInvalidAllowed() { - return invalidAllowed; - } - - /** - * Fields allow invalid values by default. In most cases this is wanted, - * because the field otherwise visually forget the user input immediately. - *

- * In common setting where the user wants to assure the correctness of the - * datasource, but allow temporarily invalid contents in the field, the user - * should add the validators to datasource, that should not allow invalid - * values. The validators are automatically copied to the field when the - * datasource is set. - *

- * - * @see com.vaadin.v7.data.Validatable#setInvalidAllowed(boolean) - */ - @Override - public void setInvalidAllowed(boolean invalidAllowed) - throws UnsupportedOperationException { - this.invalidAllowed = invalidAllowed; - } - - /** - * Error messages shown by the fields are composites of the error message - * thrown by the superclasses (that is the component error message), - * validation errors and buffered source errors. - * - * @see com.vaadin.ui.AbstractComponent#getErrorMessage() - */ - @Override - public ErrorMessage getErrorMessage() { - - /* - * Check validation errors only if automatic validation is enabled. - * Empty, required fields will generate a validation error containing - * the requiredError string. For these fields the exclamation mark will - * be hidden but the error must still be sent to the client. - */ - Validator.InvalidValueException validationError = null; - if (isValidationVisible()) { - try { - validate(); - } catch (Validator.InvalidValueException e) { - if (!e.isInvisible()) { - validationError = e; - } - } - } - - // Check if there are any systems errors - final ErrorMessage superError = super.getErrorMessage(); - - // Return if there are no errors at all - if (superError == null && validationError == null - && getCurrentBufferedSourceException() == null) { - return null; - } - - // Throw combination of the error types - return new CompositeErrorMessage(new ErrorMessage[] { superError, - AbstractErrorMessage - .getErrorMessageForException(validationError), - AbstractErrorMessage.getErrorMessageForException( - getCurrentBufferedSourceException()) }); - - } - - /* Value change events */ - - private static final Method VALUE_CHANGE_METHOD; - - static { - try { - VALUE_CHANGE_METHOD = Property.ValueChangeListener.class - .getDeclaredMethod("valueChange", - new Class[] { Property.ValueChangeEvent.class }); - } catch (final java.lang.NoSuchMethodException e) { - // This should never happen - throw new java.lang.RuntimeException( - "Internal error finding methods in LegacyAbstractField"); - } - } - - /* - * Adds a value change listener for the field. Don't add a JavaDoc comment - * here, we use the default documentation from the implemented interface. - */ - @Override - public void addValueChangeListener(Property.ValueChangeListener listener) { - addListener(AbstractField.ValueChangeEvent.class, listener, - VALUE_CHANGE_METHOD); - // ensure "automatic immediate handling" works - markAsDirty(); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #addValueChangeListener(com.vaadin.v7.data.Property.ValueChangeListener)} - **/ - @Override - @Deprecated - public void addListener(Property.ValueChangeListener listener) { - addValueChangeListener(listener); - } - - /* - * Removes a value change listener from the field. Don't add a JavaDoc - * comment here, we use the default documentation from the implemented - * interface. - */ - @Override - public void removeValueChangeListener( - Property.ValueChangeListener listener) { - removeListener(AbstractField.ValueChangeEvent.class, listener, - VALUE_CHANGE_METHOD); - // ensure "automatic immediate handling" works - markAsDirty(); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #removeValueChangeListener(com.vaadin.v7.data.Property.ValueChangeListener)} - **/ - @Override - @Deprecated - public void removeListener(Property.ValueChangeListener listener) { - removeValueChangeListener(listener); - } - - /** - * Emits the value change event. The value contained in the field is - * validated before the event is created. - */ - protected void fireValueChange(boolean repaintIsNotNeeded) { - fireEvent(new AbstractField.ValueChangeEvent(this)); - if (!repaintIsNotNeeded) { - markAsDirty(); - } - } - - /* Read-only status change events */ - - private static final Method READ_ONLY_STATUS_CHANGE_METHOD; - - static { - try { - READ_ONLY_STATUS_CHANGE_METHOD = Property.ReadOnlyStatusChangeListener.class - .getDeclaredMethod("readOnlyStatusChange", new Class[] { - Property.ReadOnlyStatusChangeEvent.class }); - } catch (final java.lang.NoSuchMethodException e) { - // This should never happen - throw new java.lang.RuntimeException( - "Internal error finding methods in LegacyAbstractField"); - } - } - - /** - * React to read only status changes of the property by requesting a - * repaint. - * - * @see Property.ReadOnlyStatusChangeListener - */ - @Override - public void readOnlyStatusChange(Property.ReadOnlyStatusChangeEvent event) { - boolean readOnly = event.getProperty().isReadOnly(); - - boolean shouldFireChange = isReadOnly() != readOnly - || getState().propertyReadOnly != readOnly; - - getState().propertyReadOnly = readOnly; - - if (shouldFireChange) { - fireReadOnlyStatusChange(); - } - } - - /** - * An Event object specifying the Property whose read-only - * status has changed. - * - * @author Vaadin Ltd. - * @since 3.0 - */ - public static class ReadOnlyStatusChangeEvent extends Component.Event - implements Property.ReadOnlyStatusChangeEvent, Serializable { - - /** - * New instance of text change event. - * - * @param source - * the Source of the event. - */ - public ReadOnlyStatusChangeEvent(AbstractField source) { - super(source); - } - - /** - * Property where the event occurred. - * - * @return the Source of the event. - */ - @Override - public Property getProperty() { - return (Property) getSource(); - } - } - - /* - * Adds a read-only status change listener for the field. Don't add a - * JavaDoc comment here, we use the default documentation from the - * implemented interface. - */ - @Override - public void addReadOnlyStatusChangeListener( - Property.ReadOnlyStatusChangeListener listener) { - addListener(Property.ReadOnlyStatusChangeEvent.class, listener, - READ_ONLY_STATUS_CHANGE_METHOD); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #addReadOnlyStatusChangeListener(com.vaadin.v7.data.Property.ReadOnlyStatusChangeListener)} - **/ - @Override - @Deprecated - public void addListener(Property.ReadOnlyStatusChangeListener listener) { - addReadOnlyStatusChangeListener(listener); - } - - /* - * Removes a read-only status change listener from the field. Don't add a - * JavaDoc comment here, we use the default documentation from the - * implemented interface. - */ - @Override - public void removeReadOnlyStatusChangeListener( - Property.ReadOnlyStatusChangeListener listener) { - removeListener(Property.ReadOnlyStatusChangeEvent.class, listener, - READ_ONLY_STATUS_CHANGE_METHOD); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #removeReadOnlyStatusChangeListener(com.vaadin.v7.data.Property.ReadOnlyStatusChangeListener)} - **/ - @Override - @Deprecated - public void removeListener(Property.ReadOnlyStatusChangeListener listener) { - removeReadOnlyStatusChangeListener(listener); - } - - /** - * Emits the read-only status change event. The value contained in the field - * is validated before the event is created. - */ - protected void fireReadOnlyStatusChange() { - fireEvent(new AbstractField.ReadOnlyStatusChangeEvent(this)); - } - - /** - * This method listens to data source value changes and passes the changes - * forwards. - * - * Changes are not forwarded to the listeners of the field during internal - * operations of the field to avoid duplicate notifications. - * - * @param event - * the value change event telling the data source contents have - * changed. - */ - @Override - public void valueChange(Property.ValueChangeEvent event) { - if (!isBuffered()) { - if (committingValueToDataSource) { - boolean propertyNotifiesOfTheBufferedValue = SharedUtil.equals( - event.getProperty().getValue(), getInternalValue()); - if (!propertyNotifiesOfTheBufferedValue) { - /* - * Property (or chained property like PropertyFormatter) now - * reports different value than the one the field has just - * committed to it. In this case we respect the property - * value. - * - * Still, we don't fire value change yet, but instead - * postpone it until "commit" is done. See setValue(Object, - * boolean) and commit(). - */ - readValueFromProperty(event); - valueWasModifiedByDataSourceDuringCommit = true; - } - } else if (!isModified()) { - readValueFromProperty(event); - fireValueChange(false); - } - } - } - - private void readValueFromProperty(Property.ValueChangeEvent event) { - setInternalValue(convertFromModel(event.getProperty().getValue())); - } - - /** - * {@inheritDoc} - */ - @Override - public void focus() { - super.focus(); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.Component.Focusable#getTabIndex() - */ - @Override - public int getTabIndex() { - return getState(false).tabIndex; - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.Component.Focusable#setTabIndex(int) - */ - @Override - public void setTabIndex(int tabIndex) { - getState().tabIndex = tabIndex; - } - - /** - * Returns the internal field value, which might not match the data source - * value e.g. if the field has been modified and is not in write-through - * mode. - * - * This method can be overridden by subclasses together with - * {@link #setInternalValue(Object)} to compute internal field value at - * runtime. When doing so, typically also {@link #isModified()} needs to be - * overridden and care should be taken in the management of the empty state - * and buffering support. - * - * @return internal field value - */ - protected T getInternalValue() { - return value; - } - - /** - * Sets the internal field value. This is purely used by LegacyAbstractField - * to change the internal LegacyField value. It does not trigger valuechange - * events. It can be overridden by the inheriting classes to update all - * dependent variables. - * - * Subclasses can also override {@link #getInternalValue()} if necessary. - * - * @param newValue - * the new value to be set. - */ - protected void setInternalValue(T newValue) { - value = newValue; - valueLocale = getLocale(); - if (validators != null && !validators.isEmpty()) { - markAsDirty(); - } - } - - /** - * Notifies the component that it is connected to an application. - * - * @see com.vaadin.ui.Component#attach() - */ - @Override - public void attach() { - super.attach(); - - localeMightHaveChanged(); - if (!isListeningToPropertyEvents) { - addPropertyListeners(); - if (!isModified() && !isBuffered()) { - // Update value from data source - updateValueFromDataSource(); - } - } - } - - @Override - public void setLocale(Locale locale) { - super.setLocale(locale); - localeMightHaveChanged(); - } - - private void localeMightHaveChanged() { - if (!SharedUtil.equals(valueLocale, getLocale())) { - // The locale HAS actually changed - - if (dataSource != null && !isModified()) { - // When we have a data source and the internal value is directly - // read from that we want to update the value - T newInternalValue = convertFromModel( - getPropertyDataSource().getValue()); - if (!SharedUtil.equals(newInternalValue, getInternalValue())) { - setInternalValue(newInternalValue); - fireValueChange(false); - } - } else if (dataSource == null && converter != null) { - /* - * No data source but a converter has been set. The same issues - * as above but we cannot use propertyDataSource. Convert the - * current value back to a model value using the old locale and - * then convert back using the new locale. If this does not - * match the field value we need to set the converted value - * again. - */ - Object convertedValue = convertToModel(getInternalValue(), - valueLocale); - T newinternalValue = convertFromModel(convertedValue); - if (!SharedUtil.equals(getInternalValue(), newinternalValue)) { - setInternalValue(newinternalValue); - fireValueChange(false); - } - } - } - } - - @Override - public void detach() { - super.detach(); - // Stop listening to data source events on detach to avoid a potential - // memory leak. See #6155. - removePropertyListeners(); - } - - /** - * Is this field required. Required fields must filled by the user. - * - * If the field is required, it is visually indicated in the user interface. - * Furthermore, setting field to be required implicitly adds "non-empty" - * validator and thus isValid() == false or any isEmpty() fields. In those - * cases validation errors are not painted as it is obvious that the user - * must fill in the required fields. - * - * On the other hand, for the non-required fields isValid() == true if the - * field isEmpty() regardless of any attached validators. - * - * - * @return true if the field is required, otherwise - * false. - */ - @Override - public boolean isRequired() { - return getState(false).required; - } - - /** - * Sets the field required. Required fields must filled by the user. - * - * If the field is required, it is visually indicated in the user interface. - * Furthermore, setting field to be required implicitly adds "non-empty" - * validator and thus isValid() == false or any isEmpty() fields. In those - * cases validation errors are not painted as it is obvious that the user - * must fill in the required fields. - * - * On the other hand, for the non-required fields isValid() == true if the - * field isEmpty() regardless of any attached validators. - * - * @param required - * Is the field required. - */ - @Override - public void setRequired(boolean required) { - getState().required = required; - } - - /** - * Set the error that is show if this field is required, but empty. When - * setting requiredMessage to be "" or null, no error pop-up or exclamation - * mark is shown for a empty required field. This faults to "". Even in - * those cases isValid() returns false for empty required fields. - * - * @param requiredMessage - * Message to be shown when this field is required, but empty. - */ - @Override - public void setRequiredError(String requiredMessage) { - requiredError = requiredMessage; - markAsDirty(); - } - - @Override - public String getRequiredError() { - return requiredError; - } - - /** - * Gets the error that is shown if the field value cannot be converted to - * the data source type. - * - * @return The error that is shown if conversion of the field value fails - */ - public String getConversionError() { - return conversionError; - } - - /** - * Sets the error that is shown if the field value cannot be converted to - * the data source type. If {0} is present in the message, it will be - * replaced by the simple name of the data source type. If {1} is present in - * the message, it will be replaced by the ConversionException message. - * - * @param valueConversionError - * Message to be shown when conversion of the value fails - */ - public void setConversionError(String valueConversionError) { - this.conversionError = valueConversionError; - markAsDirty(); - } - - @Override - public boolean isEmpty() { - return (getFieldValue() == null); - } - - @Override - public void clear() { - setValue(null); - } - - /** - * Is automatic, visible validation enabled? - * - * If automatic validation is enabled, any validators connected to this - * component are evaluated while painting the component and potential error - * messages are sent to client. If the automatic validation is turned off, - * isValid() and validate() methods still work, but one must show the - * validation in their own code. - * - * @return True, if automatic validation is enabled. - */ - public boolean isValidationVisible() { - return validationVisible; - } - - /** - * Enable or disable automatic, visible validation. - * - * If automatic validation is enabled, any validators connected to this - * component are evaluated while painting the component and potential error - * messages are sent to client. If the automatic validation is turned off, - * isValid() and validate() methods still work, but one must show the - * validation in their own code. - * - * @param validateAutomatically - * True, if automatic validation is enabled. - */ - public void setValidationVisible(boolean validateAutomatically) { - if (validationVisible != validateAutomatically) { - markAsDirty(); - validationVisible = validateAutomatically; - } - } - - /** - * Sets the current buffered source exception. - * - * @param currentBufferedSourceException - */ - public void setCurrentBufferedSourceException( - Buffered.SourceException currentBufferedSourceException) { - this.currentBufferedSourceException = currentBufferedSourceException; - markAsDirty(); - } - - /** - * Gets the current buffered source exception. - * - * @return The current source exception - */ - protected Buffered.SourceException getCurrentBufferedSourceException() { - return currentBufferedSourceException; - } - - /** - * A ready-made {@link ShortcutListener} that focuses the given - * {@link Focusable} (usually a {@link Field}) when the keyboard - * shortcut is invoked. - * - */ - public static class FocusShortcut extends ShortcutListener { - protected Focusable focusable; - - /** - * Creates a keyboard shortcut for focusing the given {@link Focusable} - * using the shorthand notation defined in {@link ShortcutAction}. - * - * @param focusable - * to focused when the shortcut is invoked - * @param shorthandCaption - * caption with keycode and modifiers indicated - */ - public FocusShortcut(Focusable focusable, String shorthandCaption) { - super(shorthandCaption); - this.focusable = focusable; - } - - /** - * Creates a keyboard shortcut for focusing the given {@link Focusable}. - * - * @param focusable - * to focused when the shortcut is invoked - * @param keyCode - * keycode that invokes the shortcut - * @param modifiers - * modifiers required to invoke the shortcut - */ - public FocusShortcut(Focusable focusable, int keyCode, - int... modifiers) { - super(null, keyCode, modifiers); - this.focusable = focusable; - } - - /** - * Creates a keyboard shortcut for focusing the given {@link Focusable}. - * - * @param focusable - * to focused when the shortcut is invoked - * @param keyCode - * keycode that invokes the shortcut - */ - public FocusShortcut(Focusable focusable, int keyCode) { - this(focusable, keyCode, null); - } - - @Override - public void handleAction(Object sender, Object target) { - focusable.focus(); - } - } - - private void updateValueFromDataSource() { - if (dataSource != null) { - - // Gets the correct value from datasource - T newFieldValue; - try { - - // Discards buffer by overwriting from datasource - newFieldValue = convertFromModel(getDataSourceValue()); - - // If successful, remove set the buffering state to be ok - if (getCurrentBufferedSourceException() != null) { - setCurrentBufferedSourceException(null); - } - } catch (final Throwable e) { - // FIXME: What should really be done here if conversion fails? - - // Sets the buffering state - currentBufferedSourceException = new Buffered.SourceException( - this, e); - markAsDirty(); - - // Throws the source exception - throw currentBufferedSourceException; - } - - final boolean wasModified = isModified(); - setModified(false); - - // If the new value differs from the previous one - if (!SharedUtil.equals(newFieldValue, getInternalValue())) { - setInternalValue(newFieldValue); - fireValueChange(false); - } else if (wasModified) { - // If the value did not change, but the modification status did - markAsDirty(); - } - } - } - - /** - * 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 getConverter() { - return converter; - } - - /** - * Sets the converter used to convert the field value to property data - * source type. The converter must have a presentation type that matches the - * field type. - * - * @param converter - * The new converter to use. - */ - public void setConverter(Converter converter) { - this.converter = (Converter) converter; - markAsDirty(); - } - - @Override - protected AbstractFieldState getState() { - return (AbstractFieldState) super.getState(); - } - - @Override - protected AbstractFieldState getState(boolean markAsDirty) { - return (AbstractFieldState) super.getState(markAsDirty); - } - - @Override - public void beforeClientResponse(boolean initial) { - super.beforeClientResponse(initial); - - // Hide the error indicator if needed - getState().hideErrors = shouldHideErrors(); - } - - /** - * Registers this as an event listener for events sent by the data source - * (if any). Does nothing if - * isListeningToPropertyEvents == true. - */ - private void addPropertyListeners() { - if (!isListeningToPropertyEvents) { - if (dataSource instanceof Property.ValueChangeNotifier) { - ((Property.ValueChangeNotifier) dataSource).addListener(this); - } - if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { - ((Property.ReadOnlyStatusChangeNotifier) dataSource) - .addListener(this); - } - isListeningToPropertyEvents = true; - } - } - - /** - * Stops listening to events sent by the data source (if any). Does nothing - * if isListeningToPropertyEvents == false. - */ - private void removePropertyListeners() { - if (isListeningToPropertyEvents) { - if (dataSource instanceof Property.ValueChangeNotifier) { - ((Property.ValueChangeNotifier) dataSource) - .removeListener(this); - } - if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { - ((Property.ReadOnlyStatusChangeNotifier) dataSource) - .removeListener(this); - } - isListeningToPropertyEvents = false; - } - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.AbstractComponent#readDesign(org.jsoup.nodes .Element, - * com.vaadin.ui.declarative.DesignContext) - */ - @Override - public void readDesign(Element design, DesignContext designContext) { - super.readDesign(design, designContext); - Attributes attr = design.attributes(); - if (design.hasAttr("readonly")) { - setReadOnly(DesignAttributeHandler.readAttribute("readonly", attr, - Boolean.class)); - } - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.AbstractComponent#getCustomAttributes() - */ - @Override - protected Collection getCustomAttributes() { - Collection attributes = super.getCustomAttributes(); - attributes.add("readonly"); - // must be handled by subclasses - attributes.add("value"); - attributes.add("converted-value"); - return attributes; - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.AbstractComponent#writeDesign(org.jsoup.nodes.Element - * , com.vaadin.ui.declarative.DesignContext) - */ - @Override - public void writeDesign(Element design, DesignContext designContext) { - super.writeDesign(design, designContext); - AbstractField def = (AbstractField) designContext - .getDefaultInstance(this); - Attributes attr = design.attributes(); - // handle readonly - DesignAttributeHandler.writeAttribute("readonly", attr, - super.isReadOnly(), def.isReadOnly(), Boolean.class); - } - - private static final Logger getLogger() { - return Logger.getLogger(AbstractField.class.getName()); - } -} diff --git a/server/src/main/java/com/vaadin/v7/ui/Field.java b/server/src/main/java/com/vaadin/v7/ui/Field.java deleted file mode 100644 index 0756dfaf90..0000000000 --- a/server/src/main/java/com/vaadin/v7/ui/Field.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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.v7.ui; - -import com.vaadin.data.HasRequired; -import com.vaadin.data.HasValue.ValueChange; -import com.vaadin.ui.Component; -import com.vaadin.ui.Component.Focusable; -import com.vaadin.v7.data.BufferedValidatable; -import com.vaadin.v7.data.Property; - -/** - * LegacyField interface is implemented by all legacy field components that have - * a value that the user can change through the user interface. - * - * LegacyField components are built upon the framework defined in the - * LegacyField interface and the {@link com.vaadin.AbstractField} base - * class. - * - * The LegacyField interface inherits the {@link com.vaadin.ui.Component} - * superinterface and also the {@link com.vaadin.ui.Property} interface to have - * a value for the field. - * - * @author Vaadin Ltd. - * - * @param - * the type of values in the field, which might not be the same type - * as that of the data source if converters are used - * - * @deprecated This interface is, apart from the rename, identical to the Vaadin - * 7 {@code com.vaadin.ui.Field}. It is provided for compatibility - * and migration purposes. As of 8.0, new field components should - * extend {@link com.vaadin.ui.AbstractField} instead. - */ -@Deprecated -public interface Field extends Component, BufferedValidatable, - Property, Property.ValueChangeNotifier, Property.ValueChangeListener, - Property.Editor, Focusable, HasRequired { - /** - * Is this field required. - * - * Required fields must filled by the user. - * - * @return true if the field is required,otherwise - * false. - * @since 3.1 - */ - @Override - public boolean isRequired(); - - /** - * Sets the field required. Required fields must filled by the user. - * - * @param required - * Is the field required. - * @since 3.1 - */ - @Override - public void setRequired(boolean required); - - /** - * Sets the error message to be displayed if a required field is empty. - * - * @param requiredMessage - * Error message. - * @since 5.2.6 - */ - public void setRequiredError(String requiredMessage); - - /** - * Gets the error message that is to be displayed if a required field is - * empty. - * - * @return Error message. - * @since 5.2.6 - */ - public String getRequiredError(); - - /** - * An Event object specifying the LegacyField whose value has - * been changed. - * - * @author Vaadin Ltd. - * @since 3.0 - * - * @deprecated As of 8.0, replaced by {@link ValueChange}. - */ - @Deprecated - @SuppressWarnings("serial") - public static class ValueChangeEvent extends Component.Event - implements Property.ValueChangeEvent { - - /** - * Constructs a new event object with the specified source field object. - * - * @param source - * the field that caused the event. - */ - public ValueChangeEvent(Field source) { - super(source); - } - - /** - * Gets the Property which triggered the event. - * - * @return the Source Property of the event. - */ - @Override - public Property getProperty() { - return (Property) getSource(); - } - - } - - /** - * Is the field empty? - * - * In general, "empty" state is same as null. As an exception, TextField - * also treats empty string as "empty". - * - * @since 7.4 - * @return true if the field is empty, false otherwise - */ - public boolean isEmpty(); - - /** - * Clears the value of the field. - *

- * The field value is typically reset to the initial value of the field. - * Calling {@link #isEmpty()} on a cleared field must always returns true. - * - * @since 7.4 - */ - public void clear(); - -} diff --git a/server/src/test/java/com/vaadin/tests/VaadinClasses.java b/server/src/test/java/com/vaadin/tests/VaadinClasses.java index ad1119a8ad..ba56fcee35 100644 --- a/server/src/test/java/com/vaadin/tests/VaadinClasses.java +++ b/server/src/test/java/com/vaadin/tests/VaadinClasses.java @@ -29,7 +29,6 @@ import com.vaadin.ui.UI; import com.vaadin.ui.VerticalSplitPanel; import com.vaadin.ui.Window; import com.vaadin.ui.themes.BaseTheme; -import com.vaadin.v7.ui.Field; @SuppressWarnings("deprecation") public class VaadinClasses { @@ -63,15 +62,6 @@ public class VaadinClasses { } } - public static List> getFields() { - try { - return findClasses(Field.class, "com.vaadin.ui"); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - public static List> getThemeClasses() { try { return findClasses(BaseTheme.class, "com.vaadin.ui.themes"); @@ -136,7 +126,7 @@ public class VaadinClasses { } - private static List> findClasses(Class baseClass, + public static List> findClasses(Class baseClass, String basePackage) throws IOException { return findClasses(baseClass, basePackage, new String[] {}); } diff --git a/server/src/test/java/com/vaadin/tests/server/component/FieldDefaultValuesTest.java b/server/src/test/java/com/vaadin/tests/server/component/FieldDefaultValuesTest.java deleted file mode 100644 index ba2bccfb78..0000000000 --- a/server/src/test/java/com/vaadin/tests/server/component/FieldDefaultValuesTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.tests.server.component; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.Assert; -import org.junit.Test; - -import com.vaadin.tests.VaadinClasses; -import com.vaadin.ui.Slider; -import com.vaadin.v7.ui.Field; - -public class FieldDefaultValuesTest { - - @Test - public void testFieldsHaveDefaultValueAfterClear() throws Exception { - for (Field field : createFields()) { - Object originalValue = field.getValue(); - - field.clear(); - - Object clearedValue = field.getValue(); - - Assert.assertEquals( - "Expected to get default value after clearing " - + field.getClass().getName(), - originalValue, clearedValue); - } - } - - @Test - public void testFieldsAreEmptyAfterClear() throws Exception { - for (Field field : createFields()) { - field.clear(); - - if (field instanceof Slider) { - Assert.assertFalse( - "Slider should not be empty even after being cleared", - field.isEmpty()); - - } else { - Assert.assertTrue( - field.getClass().getName() - + " should be empty after being cleared", - field.isEmpty()); - } - } - } - - @SuppressWarnings("rawtypes") - private static List> createFields() - throws InstantiationException, IllegalAccessException { - List> fieldInstances = new ArrayList>(); - - for (Class fieldType : VaadinClasses - .getFields()) { - fieldInstances.add(fieldType.newInstance()); - } - return fieldInstances; - } - -} diff --git a/server/src/test/java/com/vaadin/tests/server/components/AbstractFieldValueChangeTestBase.java b/server/src/test/java/com/vaadin/tests/server/components/AbstractFieldValueChangeTestBase.java deleted file mode 100644 index 36057e935f..0000000000 --- a/server/src/test/java/com/vaadin/tests/server/components/AbstractFieldValueChangeTestBase.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.vaadin.tests.server.components; - -import org.easymock.EasyMock; -import org.junit.Test; - -import com.vaadin.v7.data.Property.ValueChangeEvent; -import com.vaadin.v7.data.Property.ValueChangeListener; -import com.vaadin.v7.data.Property.ValueChangeNotifier; -import com.vaadin.v7.data.util.ObjectProperty; -import com.vaadin.v7.ui.AbstractField; - -/** - * Base class for tests for checking that value change listeners for fields are - * not called exactly once when they should be, and not at other times. - * - * Does not check all cases (e.g. properties that do not implement - * {@link ValueChangeNotifier}). - * - * Subclasses should implement {@link #setValue()} and call - * super.setValue(LegacyAbstractField). Also, subclasses should - * typically override {@link #setValue(AbstractField)} to set the field - * value via changeVariables(). - */ -public abstract class AbstractFieldValueChangeTestBase { - - private AbstractField field; - private ValueChangeListener listener; - - protected void setUp(AbstractField field) { - this.field = field; - listener = EasyMock.createStrictMock(ValueChangeListener.class); - - } - - protected ValueChangeListener getListener() { - return listener; - } - - /** - * Test that listeners are not called when they have been unregistered. - */ - @Test - public void testRemoveListener() { - getField().setPropertyDataSource(new ObjectProperty("")); - getField().setBuffered(false); - - // Expectations and start test - listener.valueChange(EasyMock.isA(ValueChangeEvent.class)); - EasyMock.replay(listener); - - // Add listener and set the value -> should end up in listener once - getField().addListener(listener); - setValue(getField()); - - // Ensure listener was called once - EasyMock.verify(listener); - - // Remove the listener and set the value -> should not end up in - // listener - getField().removeListener(listener); - setValue(getField()); - - // Ensure listener still has been called only once - EasyMock.verify(listener); - } - - /** - * Common unbuffered case: both writeThrough (auto-commit) and readThrough - * are on. Calling commit() should not cause notifications. - * - * Using the readThrough mode allows changes made to the property value to - * be seen in some cases also when there is no notification of value change - * from the property. - * - * LegacyField value change notifications closely mirror value changes of - * the data source behind the field. - */ - @Test - public void testNonBuffered() { - getField().setPropertyDataSource(new ObjectProperty("")); - getField().setBuffered(false); - - expectValueChangeFromSetValueNotCommit(); - } - - /** - * Fully buffered use where the data source is neither read nor modified - * during editing, and is updated at commit(). - * - * LegacyField value change notifications reflect the buffered value in the - * field, not the original data source value changes. - */ - public void testBuffered() { - getField().setPropertyDataSource(new ObjectProperty("")); - getField().setBuffered(true); - - expectValueChangeFromSetValueNotCommit(); - } - - protected void expectValueChangeFromSetValueNotCommit() { - // Expectations and start test - listener.valueChange(EasyMock.isA(ValueChangeEvent.class)); - EasyMock.replay(listener); - - // Add listener and set the value -> should end up in listener once - getField().addListener(listener); - setValue(getField()); - - // Ensure listener was called once - EasyMock.verify(listener); - - // commit - getField().commit(); - - // Ensure listener was not called again - EasyMock.verify(listener); - } - - protected AbstractField getField() { - return field; - } - - /** - * Override in subclasses to set value with changeVariables(). - */ - protected void setValue(AbstractField field) { - field.setValue((T) "newValue"); - } - -} -- cgit v1.2.3