/* @ITMillApache2LicenseForJavaFiles@ */ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; 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.event.Action; import com.vaadin.event.ActionManager; import com.vaadin.event.ShortcutAction; import com.vaadin.event.ShortcutListener; import com.vaadin.terminal.CompositeErrorMessage; import com.vaadin.terminal.ErrorMessage; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; /** *
* 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 IT Mill Ltd. * @version * @VERSION@ * @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 Object value; /** * Connected data-source. */ private Property dataSource = null; /** * The list of validators. */ private LinkedList* This is the visible, modified and possible invalid value the user have * entered to the field. In the read-through mode, the abstract buffer is * also updated and validation is performed. *
* ** 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 datasources * native type, use getPropertyDatasource().getValue() instead. *
* ** Note that when you extend AbstractField, you must reimplement this method * if datasource.getValue() is not assignable to class returned by getType() * AND getType() is not String. In case of Strings, getValue() calls * datasource.toString() instead of datasource.getValue(). *
* * @return the current value of the field. */ public Object getValue() { // Give the value from abstract buffers if the field if possible if (dataSource == null || !isReadThrough() || isModified()) { return value; } Object newValue = String.class == getType() ? dataSource.toString() : dataSource.getValue(); return newValue; } /** * Sets the value of the field. * * @param newValue * the New value of the field. * @throws Property.ReadOnlyException * @throws Property.ConversionException */ public void setValue(Object newValue) throws Property.ReadOnlyException, Property.ConversionException { setValue(newValue, false); } /** * Sets the value of the field. * * @param newValue * the New value of the field. * @param repaintIsNotNeeded * True iff caller is sure that repaint is not needed. * @throws Property.ReadOnlyException * @throws Property.ConversionException */ protected void setValue(Object newValue, boolean repaintIsNotNeeded) throws Property.ReadOnlyException, Property.ConversionException { if ((newValue == null && value != null) || (newValue != null && !newValue.equals(value))) { // 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)) { repaintIsNotNeeded = false; } // If invalid values are not allowed, the value must be checked if (!isInvalidAllowed()) { final Collectionnull
if
* none defined.
*/
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. *
* ** 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. */ public void setPropertyDataSource(Property newDataSource) { // Saves the old value final Object oldValue = value; // Stops listening the old data source changes if (dataSource != null && Property.ValueChangeNotifier.class .isAssignableFrom(dataSource.getClass())) { ((Property.ValueChangeNotifier) dataSource).removeListener(this); } if (dataSource != null && Property.ReadOnlyStatusChangeNotifier.class .isAssignableFrom(dataSource.getClass())) { ((Property.ReadOnlyStatusChangeNotifier) dataSource) .removeListener(this); } // Sets the new data source dataSource = newDataSource; // Gets the value from source try { if (dataSource != null) { setInternalValue(String.class == getType() ? dataSource .toString() : dataSource.getValue()); } modified = false; } catch (final Throwable e) { currentBufferedSourceException = new Buffered.SourceException(this, e); modified = true; } // Listens the new data source if possible if (dataSource instanceof Property.ValueChangeNotifier) { ((Property.ValueChangeNotifier) dataSource).addListener(this); } if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { ((Property.ReadOnlyStatusChangeNotifier) dataSource) .addListener(this); } // Copy the validators from the data source if (dataSource instanceof Validatable) { final Collectiontrue
if all registered validators claim that the
* current value is valid or if the field is empty and not required,
* false
otherwise.
*/
public boolean isValid() {
if (isEmpty()) {
if (isRequired()) {
return false;
} else {
return true;
}
}
if (validators == null) {
return true;
}
final Object value = getValue();
for (final Iterator* 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) */ 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. */ ErrorMessage 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 && currentBufferedSourceException == null) { return null; } // Throw combination of the error types return new CompositeErrorMessage(new ErrorMessage[] { superError, validationError, currentBufferedSourceException }); } /* 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. */ 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. */ 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 */ public void readOnlyStatusChange(Property.ReadOnlyStatusChangeEvent event) { requestRepaint(); } /** * AnEvent
object specifying the Property whose read-only
* status has changed.
*
* @author IT Mill Ltd.
* @version
* @VERSION@
* @since 3.0
*/
public 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.
*/
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.
*/
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.
*/
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.
*/
public void valueChange(Property.ValueChangeEvent event) {
if (isReadThrough()) {
if (committingValueToDataSource) {
boolean propertyNotifiesOfTheBufferedValue = event
.getProperty().getValue() == value
|| (value != null && value.equals(event.getProperty()
.getValue()));
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(event.getProperty().getValue());
}
@Override
public void changeVariables(Object source, Map* This returns most suitable field type for editing property of given type. *
* * @param propertyType * the Type of the property, that needs to be edited. * @deprecated use e.g. * {@link DefaultFieldFactory#createFieldByPropertyType(Class)} * instead */ @Deprecated public static AbstractField constructField(Class> propertyType) { return (AbstractField) DefaultFieldFactory .createFieldByPropertyType(propertyType); } /* * (non-Javadoc) * * @see com.vaadin.ui.Component.Focusable#getTabIndex() */ public int getTabIndex() { return tabIndex; } /* * (non-Javadoc) * * @see com.vaadin.ui.Component.Focusable#setTabIndex(int) */ public void setTabIndex(int tabIndex) { this.tabIndex = tabIndex; requestRepaint(); } /** * 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. * * @param newValue * the new value to be set. */ protected void setInternalValue(Object 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 (actionManager != null) { actionManager.setViewer(getWindow()); } } @Override public void detach() { super.detach(); if (actionManager != null) { actionManager.setViewer((Window) null); } } /** * 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. * * * @returntrue
if the field is required .otherwise
* false
.
*/
public boolean isRequired() {
return required;
}
/**
* Sets the field required. Required fields must filled by the user.
*
* If the field is required, it is visually indicated in the user interface.
* Furthermore, setting field to be required implicitly adds "non-empty"
* validator and thus isValid() == false or any isEmpty() fields. In those
* cases validation errors are not painted as it is obvious that the user
* must fill in the required fields.
*
* On the other hand, for the non-required fields isValid() == true if the
* field isEmpty() regardless of any attached validators.
*
* @param required
* Is the field required.
*/
public void setRequired(boolean required) {
this.required = 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.
*/
public void setRequiredError(String requiredMessage) {
requiredError = requiredMessage;
requestRepaint();
}
public String getRequiredError() {
return requiredError;
}
/**
* 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 (getValue() == 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();
}
/*
* Actions
*/
/**
* Gets the {@link ActionManager} used to manage the
* {@link ShortcutListener}s added to this {@link Field}.
*
* @return the ActionManager in use
*/
protected ActionManager getActionManager() {
if (actionManager == null) {
actionManager = new ActionManager();
if (getWindow() != null) {
actionManager.setViewer(getWindow());
}
}
return actionManager;
}
public void addShortcutListener(ShortcutListener shortcut) {
getActionManager().addAction(shortcut);
}
public void removeShortcutListener(ShortcutListener shortcut) {
if (actionManager != null) {
actionManager.removeAction(shortcut);
}
}
/**
* 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();
}
}
}