/* * 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 AbstractField extends AbstractComponent implements Field, Property.ReadOnlyStatusChangeListener, Property.ReadOnlyStatusChangeNotifier, Action.ShortcutNotifier { /* Private members */ private static final Logger logger = Logger.getLogger(AbstractField.class .getName()); /** * 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; /** * Auto commit mode. */ private boolean writeThroughMode = true; /** * Reads the value from data-source, when it is not modified. */ private boolean readThroughMode = true; /** * 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; /* 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 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, 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().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 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, getApplication()); 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 convertFromDataSource(Object newValue) { return ConverterUtil.convertFromModel(newValue, getType(), getConverter(), 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()} * @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 { try { Class modelType = null; Property pd = getPropertyDataSource(); if (pd != null) { modelType = pd.getType(); } else if (getConverter() != null) { modelType = getConverter().getModelType(); } return ConverterUtil.convertToModel(fieldValue, (Class) modelType, getConverter(), getLocale()); } catch (ConversionException e) { throw new ConversionException( getConversionError(converter.getModelType()), e); } } /** * Returns the conversion error with {0} replaced by the data source type. * * @param dataSourceType * The type of the data source * @return The value conversion error string with parameters replaced. */ protected String getConversionError(Class dataSourceType) { if (dataSourceType == null) { return getConversionError(); } else { return getConversionError().replace("{0}", dataSourceType.getSimpleName()); } } /** * Returns the current value (as returned by {@link #getValue()}) converted * to the data source type. *

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

* * @return The converted value that is compatible with the data source type */ public Object getConvertedValue() { return convertToModel(getFieldValue()); } /** * Sets the value of the field using a value of the data source type. The * value given is converted to the field type and then assigned to the * field. This will update the property data source in the same way as when * {@link #setValue(Object)} is called. * * @param value * The value to set. Must be the same type as the data source. */ public void setConvertedValue(Object value) { setValue(convertFromDataSource(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); requestRepaint(); } /** * Gets the validators of the field. * * @return the Unmodifiable collection that holds all validators for the * field. */ @Override public Collection getValidators() { if (validators == null || validators.isEmpty()) { return null; } return Collections.unmodifiableCollection(validators); } /** * Removes the validator from the field. * * @param validator * the validator to remove. */ @Override public void removeValidator(Validator validator) { if (validators != null) { validators.remove(validator); } requestRepaint(); } /** * Removes all validators from the field. */ public void removeAllValidators() { if (validators != null) { validators.clear(); } requestRepaint(); } /** * Tests the current value against registered validators if the field is not * empty. If the field is empty it is considered valid if it is not required * and invalid otherwise. Validators are never checked for empty fields. * * In most cases, {@link #validate()} should be used instead of * {@link #isValid()} to also get the error message. * * @return true if all registered validators claim that the * current value is valid or if the field is empty and not required, * false otherwise. */ @Override public boolean isValid() { try { validate(); return true; } catch (InvalidValueException e) { return false; } } /** * Checks the validity of the Field. * * 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.data.Validatable#validate() */ @Override public void validate() throws Validator.InvalidValueException { if (isRequired() && isEmpty()) { throw new Validator.EmptyValueException(requiredError); } validate(getFieldValue()); } /** * Validates that the given value pass the validators for the field. *

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

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

* * @see com.vaadin.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 AbstractField"); } } /* * 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 addListener(Property.ValueChangeListener listener) { addListener(AbstractField.ValueChangeEvent.class, listener, VALUE_CHANGE_METHOD); } /* * 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 removeListener(Property.ValueChangeListener listener) { removeListener(AbstractField.ValueChangeEvent.class, listener, VALUE_CHANGE_METHOD); } /** * 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) { requestRepaint(); } } /* 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 AbstractField"); } } /** * React to read only status changes of the property by requesting a * repaint. * * @see Property.ReadOnlyStatusChangeListener */ @Override public void readOnlyStatusChange(Property.ReadOnlyStatusChangeEvent event) { getState().setPropertyReadOnly(event.getProperty().isReadOnly()); requestRepaint(); } /** * An Event object specifying the Property whose read-only * status has changed. * * @author Vaadin Ltd. * @since 3.0 */ public static class ReadOnlyStatusChangeEvent extends Component.Event implements Property.ReadOnlyStatusChangeEvent, Serializable { /** * New instance of text change event. * * @param source * the Source of the event. */ public ReadOnlyStatusChangeEvent(AbstractField source) { super(source); } /** * Property where the event occurred. * * @return the Source of the event. */ @Override public Property getProperty() { return (Property) getSource(); } } /* * Adds a read-only status change listener for the field. Don't add a * JavaDoc comment here, we use the default documentation from the * implemented interface. */ @Override public void addListener(Property.ReadOnlyStatusChangeListener listener) { addListener(Property.ReadOnlyStatusChangeEvent.class, listener, READ_ONLY_STATUS_CHANGE_METHOD); } /* * 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 removeListener(Property.ReadOnlyStatusChangeListener listener) { removeListener(Property.ReadOnlyStatusChangeEvent.class, listener, READ_ONLY_STATUS_CHANGE_METHOD); } /** * 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 (isReadThrough()) { if (committingValueToDataSource) { boolean propertyNotifiesOfTheBufferedValue = 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(convertFromDataSource(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().getTabIndex(); } /* * (non-Javadoc) * * @see com.vaadin.ui.Component.Focusable#setTabIndex(int) */ @Override public void setTabIndex(int tabIndex) { getState().setTabIndex(tabIndex); requestRepaint(); } /** * 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 AbstractField to * change the internal Field 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; if (validators != null && !validators.isEmpty()) { requestRepaint(); } } /** * Notifies the component that it is connected to an application. * * @see com.vaadin.ui.Component#attach() */ @Override public void attach() { super.attach(); if (!isListeningToPropertyEvents) { addPropertyListeners(); if (!isModified() && isReadThrough()) { // Update value from data source discard(); } } } @Override public void detach() { super.detach(); // Stop listening to data source events on detach to avoid a potential // memory leak. See #6155. removePropertyListeners(); } /** * Is this field required. Required fields must filled by the user. * * If the field is required, it is visually indicated in the user interface. * Furthermore, setting field to be required implicitly adds "non-empty" * validator and thus isValid() == false or any isEmpty() fields. In those * cases validation errors are not painted as it is obvious that the user * must fill in the required fields. * * On the other hand, for the non-required fields isValid() == true if the * field isEmpty() regardless of any attached validators. * * * @return true if the field is required, otherwise * false. */ @Override public boolean isRequired() { return getState().isRequired(); } /** * 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().setRequired(required); requestRepaint(); } /** * 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; requestRepaint(); } @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. * * @param valueConversionError * Message to be shown when conversion of the value fails */ public void setConversionError(String valueConversionError) { this.conversionError = valueConversionError; requestRepaint(); } /** * Is the field empty? * * In general, "empty" state is same as null. As an exception, TextField * also treats empty string as "empty". */ protected boolean isEmpty() { return (getFieldValue() == 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) { requestRepaint(); validationVisible = validateAutomatically; } } /** * Sets the current buffered source exception. * * @param currentBufferedSourceException */ public void setCurrentBufferedSourceException( Buffered.SourceException currentBufferedSourceException) { this.currentBufferedSourceException = currentBufferedSourceException; requestRepaint(); } /** * 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(); } } /** * Gets the converter used to convert the property data source value to the * field value. * * @return The converter or null if none is set. */ public Converter getConverter() { return converter; } /** * Sets the converter used to convert the field value to property data * source type. The converter must have a presentation type that matches the * field type. * * @param converter * The new converter to use. */ public void setConverter(Converter converter) { this.converter = (Converter) converter; requestRepaint(); } @Override public AbstractFieldState getState() { return (AbstractFieldState) super.getState(); } @Override public void updateState() { super.updateState(); // Hide the error indicator if needed getState().setHideErrors(shouldHideErrors()); } /** * Registers this as an event listener for events sent by the data source * (if any). Does nothing if * isListeningToPropertyEvents == true. */ private void addPropertyListeners() { if (!isListeningToPropertyEvents) { if (dataSource instanceof Property.ValueChangeNotifier) { ((Property.ValueChangeNotifier) dataSource).addListener(this); } if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { ((Property.ReadOnlyStatusChangeNotifier) dataSource) .addListener(this); } isListeningToPropertyEvents = true; } } /** * Stops listening to events sent by the data source (if any). Does nothing * if isListeningToPropertyEvents == false. */ private void removePropertyListeners() { if (isListeningToPropertyEvents) { if (dataSource instanceof Property.ValueChangeNotifier) { ((Property.ValueChangeNotifier) dataSource) .removeListener(this); } if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { ((Property.ReadOnlyStatusChangeNotifier) dataSource) .removeListener(this); } isListeningToPropertyEvents = false; } } }