/* * Copyright 2011 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.logging.Logger; import com.vaadin.data.Buffered; import com.vaadin.data.Property; import com.vaadin.data.Validatable; import com.vaadin.data.Validator; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.util.converter.Converter; import com.vaadin.data.util.converter.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.shared.AbstractFieldState; import com.vaadin.terminal.AbstractErrorMessage; import com.vaadin.terminal.CompositeErrorMessage; import com.vaadin.terminal.ErrorMessage; /** *
* 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 {
if (dataSource != null) {
// Gets the correct value from datasource
T newFieldValue;
try {
// Discards buffer by overwriting from datasource
newFieldValue = convertFromDataSource(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);
requestRepaint();
// Throws the source exception
throw currentBufferedSourceException;
}
final boolean wasModified = isModified();
setModified(false);
// If the new value differs from the previous one
if (!equals(newFieldValue, getInternalValue())) {
setInternalValue(newFieldValue);
fireValueChange(false);
} else if (wasModified) {
// If the value did not change, but the modification status did
requestRepaint();
}
}
}
/**
* 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 || !isReadThrough() || isModified()) {
return getInternalValue();
}
// There is no buffered value so use whatever the data model provides
return convertFromDataSource(getDataSourceValue());
}
/*
* Has the field been modified since the last commit()? Don't add a JavaDoc
* comment here, we use the default documentation from the implemented
* interface.
*/
@Override
public boolean isModified() {
return getState().isModified();
}
private void setModified(boolean modified) {
getState().setModified(modified);
requestRepaint();
}
/*
* Tests if the field is in write-through mode. Don't add a JavaDoc comment
* here, we use the default documentation from the implemented interface.
*/
@Override
public boolean isWriteThrough() {
return writeThroughMode;
}
/**
* Sets the field's write-through mode to the specified status. When
* switching the write-through mode on, a {@link #commit()} will be
* performed.
*
* @see #setBuffered(boolean) for an easier way to control read through and
* write through modes
*
* @param writeThrough
* Boolean value to indicate if the object should be in
* write-through mode after the call.
* @throws SourceException
* If the operation fails because of an exception is thrown by
* the data source.
* @throws InvalidValueException
* If the implicit commit operation fails because of a
* validation error.
* @deprecated Use {@link #setBuffered(boolean)} instead. Note that
* setReadThrough(true), setWriteThrough(true) equals
* setBuffered(false)
*/
@Override
@Deprecated
public void setWriteThrough(boolean writeThrough)
throws Buffered.SourceException, InvalidValueException {
if (writeThroughMode == writeThrough) {
return;
}
writeThroughMode = writeThrough;
if (writeThroughMode) {
commit();
}
}
/*
* Tests if the field is in read-through mode. Don't add a JavaDoc comment
* here, we use the default documentation from the implemented interface.
*/
@Override
public boolean isReadThrough() {
return readThroughMode;
}
/**
* Sets the field's read-through mode to the specified status. When
* switching read-through mode on, the object's value is updated from the
* data source.
*
* @see #setBuffered(boolean) for an easier way to control read through and
* write through modes
*
* @param readThrough
* Boolean value to indicate if the object should be in
* read-through mode after the call.
*
* @throws SourceException
* If the operation fails because of an exception is thrown by
* the data source. The cause is included in the exception.
* @deprecated Use {@link #setBuffered(boolean)} instead. Note that
* setReadThrough(true), setWriteThrough(true) equals
* setBuffered(false)
*/
@Override
@Deprecated
public void setReadThrough(boolean readThrough)
throws Buffered.SourceException {
if (readThroughMode == readThrough) {
return;
}
readThroughMode = readThrough;
if (!isModified() && readThroughMode && getPropertyDataSource() != null) {
setInternalValue(convertFromDataSource(getDataSourceValue()));
fireValueChange(false);
}
}
/**
* 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. *
** Changing buffered mode will change the read through and write through * state for the field. *
** Mixing calls to {@link #setBuffered(boolean)} and * {@link #setReadThrough(boolean)} or {@link #setWriteThrough(boolean)} is * generally a bad idea. *
* * @param buffered * true if buffered mode should be turned on, false otherwise */ @Override public void setBuffered(boolean buffered) { setReadThrough(!buffered); setWriteThrough(!buffered); } /** * Checks the buffered mode of this Field. ** This method only returns true if both read and write buffering is used. * * @return true if buffered mode is on, false otherwise */ @Override public boolean isBuffered() { return !isReadThrough() && !isWriteThrough(); } /* Property interface implementation */ /** * Returns the (field) value converted to a String using toString(). * * @see java.lang.Object#toString() * @deprecated Instead 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() { logger.warning("You are using AbstractField.toString() to get the value for a " + getClass().getSimpleName() + ". This is not recommended and will not be supported in future versions."); final Object value = getFieldValue(); if (value == null) { return null; } return value.toString(); } /** * 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(Object newFieldValue) throws Property.ReadOnlyException, Converter.ConversionException { // This check is needed as long as setValue accepts Object instead of T if (newFieldValue != null) { if (!getType().isAssignableFrom(newFieldValue.getClass())) { throw new Converter.ConversionException("Value of type " + newFieldValue.getClass() + " cannot be assigned to " + getType().getName()); } } setValue((T) 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 (!equals(newFieldValue, getInternalValue())) { // Read only fields can not be changed if (isReadOnly()) { throw new Property.ReadOnlyException(); } // 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 write through mode , try to commit if (isWriteThrough() && 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); requestRepaint(); // 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); } } private static boolean equals(Object value1, Object value2) { if (value1 == null) { return value2 == null; } return value1.equals(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().setPropertyReadOnly( dataSource == null ? false : dataSource.isReadOnly()); // Check if the current converter is compatible. if (newDataSource != null && !ConverterUtil.canConverterHandle(getConverter(), getType(), newDataSource.getType())) { // Changing from e.g. Number -> Double should set a new converter, // changing from Double -> Number can keep the old one (Property // accepts Number) // Set a new converter if there is a new data source and // there is no old converter or the old is incompatible. setConverter(newDataSource.getType()); } // Gets the value from source try { if (dataSource != null) { T fieldValue = convertFromDataSource(getDataSourceValue()); setInternalValue(fieldValue); } setModified(false); if (getCurrentBufferedSourceException() != null) { setCurrentBufferedSourceException(null); } } catch (final Throwable e) { setCurrentBufferedSourceException(new Buffered.SourceException( this, e)); setModified(true); } // Listen to new data source if possible addPropertyListeners(); // Copy the validators from the data source if (dataSource instanceof Validatable) { final Collection