/* * Copyright 2000-2013 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 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; /** *
* 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 AbstractFieldgetValue
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, ornull
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