com.vaadin.ui.AbstractField Maven / Gradle / Ivy
/*
* Copyright 2000-2014 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.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.data.Buffered;
import com.vaadin.data.Property;
import com.vaadin.data.Validatable;
import com.vaadin.data.Validator;
import com.vaadin.data.Validator.InvalidValueException;
import com.vaadin.data.util.LegacyPropertyHelper;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.Converter.ConversionException;
import com.vaadin.data.util.converter.ConverterUtil;
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.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
/**
*
* 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.data.Property}interface.
* AbstractField
implements that interface itself, too, so
* accessing the Property value represented by it is straightforward.
*
*
*
* AbstractField also provides the {@link com.vaadin.data.Buffered} interface
* for buffering the data source value. By default the Field is in write
* through-mode and {@link #setWriteThrough(boolean)}should be called to enable
* buffering.
*
*
*
* The class also supports {@link com.vaadin.data.Validator validators} to make
* sure the value contained in the field is valid.
*
*
* @author Vaadin Ltd.
* @since 3.0
*/
@SuppressWarnings("serial")
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 Field. 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 Field
*/
@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.data.BufferedValidatable#isInvalidCommitted()
*/
@Override
public boolean isInvalidCommitted() {
return invalidCommitted;
}
/**
* Sets if the invalid data should be committed to datasource.
*
* @see com.vaadin.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 Field.
*
* 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 Field.
*
* @return true if buffered mode is on, false otherwise
*/
@Override
public boolean isBuffered() {
return buffered;
}
/**
* Returns a string representation of this object. The returned string
* representation depends on if the legacy Property toString mode is enabled
* or disabled.
*
* If legacy Property toString mode is enabled, returns the value of this
* Field
converted to a String.
*
*
* If legacy Property toString mode is disabled, the string representation
* has no special meaning
*
*
* @see LegacyPropertyHelper#isLegacyToStringEnabled()
*
* @return A string representation of the value value stored in the Property
* or a string representation of the Property object.
* @deprecated As of 7.0. Use {@link #getValue()} to get the value of the
* field, {@link #getConvertedValue()} to get the field value
* converted to the data model type or
* {@link #getPropertyDataSource()} .getValue() to get the value
* of the data source.
*/
@Deprecated
@Override
public String toString() {
if (!LegacyPropertyHelper.isLegacyToStringEnabled()) {
return super.toString();
} else {
return LegacyPropertyHelper.legacyPropertyToString(this);
}
}
/* 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
*/
protected void setValue(T newFieldValue, boolean repaintIsNotNeeded)
throws Property.ReadOnlyException, Converter.ConversionException,
InvalidValueException {
if (!SharedUtil.equals(newFieldValue, getInternalValue())) {
// Read only fields can not be changed
if (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() || getValidators() != null || 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.data.Property.ValueChangeNotifier} and/or
* {@link com.vaadin.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