diff options
author | Artur Signell <artur@vaadin.com> | 2016-08-25 14:46:30 +0300 |
---|---|---|
committer | Artur Signell <artur@vaadin.com> | 2016-08-26 11:40:13 +0300 |
commit | c11121e2b7e685d42eb129c8a97e5ed4690af2f2 (patch) | |
tree | e1ed542be05eda64d12d18f8310db9900f835cb5 /compatibility-server/src | |
parent | 58853fe47bb01a0c0c2b5c380056d22ccccd6f08 (diff) | |
download | vaadin-framework-c11121e2b7e685d42eb129c8a97e5ed4690af2f2.tar.gz vaadin-framework-c11121e2b7e685d42eb129c8a97e5ed4690af2f2.zip |
Move old Field and AbstractField to compatibility package
Change-Id: Ia9b6f77763abac87ec61d1ee198cb8d41419a934
Diffstat (limited to 'compatibility-server/src')
7 files changed, 2189 insertions, 2 deletions
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; + +/** + * <p> + * 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. + * <code>LegacyAbstractField</code> implements that interface itself, too, so + * accessing the Property value represented by it is straightforward. + * </p> + * + * <p> + * 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. + * </p> + * + * <p> + * The class also supports {@link com.vaadin.v7.data.Validator validators} + * to make sure the value contained in the field is valid. + * </p> + * + * @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<T> extends AbstractComponent + implements Field<T>, 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<T, Object> converter = null; + /** + * Connected data-source. + */ + private Property<?> dataSource = null; + + /** + * The list of validators. + */ + private LinkedList<Validator> 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 <code>getValue</code> + * and <code>setValue</code> must be compatible with this type: one must be + * able to safely cast the value returned from <code>getValue</code> to the + * given type and pass any variable assignable to this type as an argument + * to <code>setValue</code>. + * + * @return the type of the LegacyField + */ + @Override + public abstract Class<? extends T> 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. + * <p> + * When the field is in buffered mode, changes will not be committed to the + * property data source until {@link #commit()} is called. + * </p> + * <p> + * Setting buffered mode from true to false will commit any pending changes. + * </p> + * <p> + * + * </p> + * + * @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. + * + * <p> + * This is the visible, modified and possible invalid value the user have + * entered to the field. + * </p> + * + * <p> + * Note that the object returned is compatible with getType(). For example, + * if the type is String, this returns Strings even when the underlying + * datasource is of some other type. In order to access the converted value, + * use {@link #getConvertedValue()} and to access the value of the property + * data source, use {@link Property#getValue()} for the property data + * source. + * </p> + * + * <p> + * Since Vaadin 7.0, no implicit conversions between other data types and + * String are performed, but a converter is used if set. + * </p> + * + * @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 <code>null</code> if + * none defined. + */ + @Override + public Property getPropertyDataSource() { + return dataSource; + } + + /** + * <p> + * Sets the specified Property as the data source for the field. All + * uncommitted changes are replaced with a value from the new data source. + * </p> + * + * <p> + * 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. + * </p> + * + * <p> + * 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}. + * </p> + * + * <p> + * 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(). + * </p> + * + * @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<Validator> validators = ((Validatable) dataSource) + .getValidators(); + if (validators != null) { + for (final Iterator<Validator> 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<T, ?> c = (Converter<T, ?>) 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<Object>) 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. + * <p> + * 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. + * </p> + * + * @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<Validator> 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 <code>true</code> if all registered validators claim that the + * current value is valid or if the field is empty and not required, + * <code>false</code> 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. + * <p> + * 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<InvalidValueException> 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. + * <p> + * 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. + * </p> + * + * @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 <code>Event</code> 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 <code>true</code> if the field is required, otherwise + * <code>false</code>. + */ + @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<T, Object> 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<T, ?> converter) { + this.converter = (Converter<T, Object>) 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 + * <code>isListeningToPropertyEvents == true</code>. + */ + 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 <code>isListeningToPropertyEvents == false</code>. + */ + 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<String> getCustomAttributes() { + Collection<String> 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 <T> + * 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<T> extends Component, BufferedValidatable, + Property<T>, Property.ValueChangeNotifier, Property.ValueChangeListener, + Property.Editor, Focusable, HasRequired { + /** + * Is this field required. + * + * Required fields must filled by the user. + * + * @return <code>true</code> if the field is required,otherwise + * <code>false</code>. + * @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 <code>Event</code> 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. + * <p> + * 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<Class<? extends Field>> 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 + * <code>super.setValue(LegacyAbstractField)</code>. Also, subclasses should + * typically override {@link #setValue(AbstractField)} to set the field + * value via <code>changeVariables()</code>. + */ +public abstract class AbstractFieldValueChangeTestBase<T> { + + private AbstractField<T> field; + private ValueChangeListener listener; + + protected void setUp(AbstractField<T> 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<String>("")); + 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<String>("")); + 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<String>("")); + 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<T> getField() { + return field; + } + + /** + * Override in subclasses to set value with changeVariables(). + */ + protected void setValue(AbstractField<T> 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<Field<?>> createFields() + throws InstantiationException, IllegalAccessException { + List<Field<?>> fieldInstances = new ArrayList<Field<?>>(); + + for (Class<? extends Field> 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; |