/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* Copyright (c) 2013, MPL CodeInside http://codeinside.ru
*/
package ru.codeinside.gses.vaadin.customfield;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
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.terminal.CompositeErrorMessage;
import com.vaadin.terminal.ErrorMessage;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.ui.AbstractField;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.Field;
public abstract class CustomField extends CustomComponent implements Field {
private static final long serialVersionUID = 5457282096887625533L;
/**
* Value of the abstract field.
*/
private Object value;
/**
* Connected data-source.
*/
private Property dataSource = null;
/**
* The list of validators.
*/
private LinkedList<Validator> validators = null;
/**
* Auto commit mode.
*/
private boolean writeTroughMode = true;
/**
* Reads the value from data-source, when it is not modified.
*/
private boolean readTroughMode = true;
/**
* Is the field modified but not committed.
*/
private boolean modified = 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 tab order number of this field.
*/
private int tabIndex = 0;
/**
* Required field.
*/
private boolean required = false;
/**
* The error message for the exception that is thrown when the field is
* required but empty.
*/
private String requiredError = "";
/**
* Is automatic validation enabled.
*/
private boolean validationVisible = true;
@Override
public void paintContent(PaintTarget target) throws PaintException {
// The tab ordering number
if (tabIndex != 0) {
target.addAttribute("tabindex", tabIndex);
}
// If the field is modified, but not committed, set modified attribute
if (isModified()) {
target.addAttribute("modified", true);
}
// Adds the required attribute
if (!isReadOnly() && isRequired()) {
target.addAttribute("required", true);
}
// Hide the error indicator if needed
if (isRequired() && isEmpty() && getComponentError() == null
&& getErrorMessage() != null) {
target.addAttribute("hideErrors", true);
}
super.paintContent(target);
}
public abstract Class<?> getType();
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.AbstractComponent#isReadOnly()
*/
@Override
public boolean isReadOnly() {
return super.isReadOnly()
|| (dataSource != null && dataSource.isReadOnly());
}
/*
* (non-Javadoc)
*
* @see com.vaadin.data.BufferedValidatable#isInvalidCommitted()
*/
public boolean isInvalidCommitted() {
return invalidCommitted;
}
/*
* (non-Javadoc)
*
* @see com.vaadin.data.BufferedValidatable#setInvalidCommitted(boolean)
*/
public void setInvalidCommitted(boolean isCommitted) {
invalidCommitted = isCommitted;
}
/*
* (non-Javadoc)
*
* @see com.vaadin.data.Buffered#commit()
*/
public void commit() throws Buffered.SourceException, InvalidValueException {
if (dataSource != null && !dataSource.isReadOnly()) {
if ((isInvalidCommitted() || isValid())) {
final Object newValue = getValue();
try {
// Commits the value to datasource.
dataSource.setValue(newValue);
} catch (final Throwable e) {
// Sets the buffering state.
currentBufferedSourceException = new Buffered.SourceException(
this, e);
requestRepaint();
// Throws the source exception.
throw currentBufferedSourceException;
}
} else {
/* An invalid value and we don't allow them, throw the exception */
validate();
}
}
boolean repaintNeeded = false;
// The abstract field is not modified anymore
if (modified) {
modified = false;
repaintNeeded = true;
}
// If successful, remove set the buffering state to be ok
if (currentBufferedSourceException != null) {
currentBufferedSourceException = null;
repaintNeeded = true;
}
if (repaintNeeded) {
requestRepaint();
}
}
/*
* (non-Javadoc)
*
* @see com.vaadin.data.Buffered#discard()
*/
public void discard() throws Buffered.SourceException {
if (dataSource != null) {
// Gets the correct value from datasource
Object newValue;
try {
// Discards buffer by overwriting from datasource
newValue = String.class == getType() ? dataSource.toString()
: dataSource.getValue();
// If successful, remove set the buffering state to be ok
if (currentBufferedSourceException != null) {
currentBufferedSourceException = null;
requestRepaint();
}
} catch (final Throwable e) {
// Sets the buffering state
currentBufferedSourceException = new Buffered.SourceException(
this, e);
requestRepaint();
// Throws the source exception
throw currentBufferedSourceException;
}
final boolean wasModified = isModified();
modified = false;
// If the new value differs from the previous one
if ((newValue == null && value != null)
|| (newValue != null && !newValue.equals(value))) {
setInternalValue(newValue);
fireValueChange(false);
}
// If the value did not change, but the modification status did
else if (wasModified) {
requestRepaint();
}
}
}
/*
* (non-Javadoc)
*
* @see com.vaadin.data.Buffered#isModified()
*/
public boolean isModified() {
return modified;
}
/*
* (non-Javadoc)
*
* @see com.vaadin.data.Buffered#isWriteThrough()
*/
public boolean isWriteThrough() {
return writeTroughMode;
}
/*
* (non-Javadoc)
*
* @see com.vaadin.data.Buffered#setWriteThrough(boolean)
*/
public void setWriteThrough(boolean writeTrough)
throws Buffered.SourceException, InvalidValueException {
if (writeTroughMode == writeTrough) {
return;
}
writeTroughMode = writeTrough;
if (writeTroughMode) {
commit();
}
}
/*
* (non-Javadoc)
*
* @see com.vaadin.data.Buffered#isReadThrough()
*/
public boolean isReadThrough() {
return readTroughMode;
}
/*
* (non-Javadoc)
*
* @see com.vaadin.data.Buffered#setReadThrough(boolean)
*/
public void setReadThrough(boolean readTrough)
throws Buffered.SourceException {
if (readTroughMode == readTrough) {
return;
}
readTroughMode = readTrough;
if (!isModified() && readTroughMode && dataSource != null) {
setInternalValue(String.class == getType() ? dataSource.toString()
: dataSource.getValue());
fireValueChange(false);
}
}
/**
* Returns the value of the Property in human readable textual format.
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
final Object value = getValue();
if (value == null) {
return null;
}
return getValue().toString();
}
/**
* Gets the current value of the field.
*
* <p>
* 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.
* </p>
*
* <p>
* 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.
* </p>
*
* <p>
* Note that when you extend CustomField, 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().
* </p>
*
* @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();
if ((newValue == null && value != null)
|| (newValue != null && !newValue.equals(value))) {
setInternalValue(newValue);
fireValueChange(false);
}
return newValue;
}
/*
* (non-Javadoc)
*
* @see com.vaadin.data.Property#setValue(java.lang.Object)
*/
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 Collection<Validator> validators = getValidators();
if (validators != null) {
for (Validator v : validators) {
v.validate(newValue);
}
}
}
// Changes the value
setInternalValue(newValue);
modified = dataSource != null;
// In write trough mode , try to commit
if (isWriteThrough() && dataSource != null
&& (isInvalidCommitted() || isValid())) {
try {
// Commits the value to datasource
dataSource.setValue(newValue);
// The buffer is now unmodified
modified = false;
} catch (final Throwable e) {
// Sets the buffering state
currentBufferedSourceException = new Buffered.SourceException(
this, e);
requestRepaint();
// Throws the source exception
throw currentBufferedSourceException;
}
}
// If successful, remove set the buffering state to be ok
if (currentBufferedSourceException != null) {
currentBufferedSourceException = null;
requestRepaint();
}
// Fires the value change
fireValueChange(repaintIsNotNeeded);
}
}
public Property getPropertyDataSource() {
return dataSource;
}
/**
* <p>
* Sets the specified Property as the data source for the field. All
* uncommitted changes to the field are discarded and the value is refreshed
* from the new data source.
* </p>
*
* <p>
* 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.
* </p>
*
* @param newDataSource
* the new data source Property.
*/
public void setPropertyDataSource(Property newDataSource) {
// Saves the old value
final Object oldValue = value;
// Discards all changes to old datasource
try {
discard();
} catch (final Buffered.SourceException ignored) {
}
// Stops listening the old data source changes
if (dataSource != null
&& Property.ValueChangeNotifier.class
.isAssignableFrom(dataSource.getClass())) {
((Property.ValueChangeNotifier) 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);
}
// Copy the validators from the data source
if (dataSource instanceof Validatable) {
final Collection<Validator> validators = ((Validatable) dataSource)
.getValidators();
if (validators != null) {
for (final Iterator<Validator> i = validators.iterator(); i
.hasNext();) {
addValidator(i.next());
}
}
}
// Fires value change if the value has changed
if ((value != oldValue)
&& ((value != null && !value.equals(oldValue)) || value == null)) {
fireValueChange(false);
}
}
/*
* (non-Javadoc)
*
* @see com.vaadin.data.Validatable#addValidator(com.vaadin.data.Validator)
*/
public void addValidator(Validator validator) {
if (validators == null) {
validators = new LinkedList<Validator>();
}
validators.add(validator);
requestRepaint();
}
/**
* Gets the validators of the field.
*
* @return the Unmodifiable collection that holds all validators for the
* field, not null.
*/
public Collection<Validator> getValidators() {
if (validators == null || validators.isEmpty()) {
return Collections.emptyList();
}
return Collections.unmodifiableCollection(validators);
}
/**
* Removes the validator from the field.
*
* @param validator
* the validator to remove.
*/
public void removeValidator(Validator validator) {
if (validators != null) {
validators.remove(validator);
}
requestRepaint();
}
/**
* Tests the current value against all registered validators.
*
* @return <code>true</code> if all registered validators claim that the
* current value is valid, <code>false</code> 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<Validator> i = validators.iterator(); i.hasNext();) {
if (!(i.next()).isValid(value)) {
return false;
}
}
return true;
}
/**
* Checks the validity of the Validatable by validating the field with all
* attached validators.
*
* The "required" validation is a built-in validation feature. If the field
* is required, but empty, validation will throw an EmptyValueException with
* the error message set with setRequiredError().
*
* @see com.vaadin.data.Validatable#validate()
*/
public void validate() throws Validator.InvalidValueException {
if (isEmpty()) {
if (isRequired()) {
throw new Validator.EmptyValueException(requiredError);
} else {
return;
}
}
// If there is no validator, there can not be any errors
if (validators == null) {
return;
}
// Initialize temps
Validator.InvalidValueException firstError = null;
LinkedList<InvalidValueException> errors = null;
final Object value = getValue();
// Gets all the validation errors
for (final Iterator<Validator> i = validators.iterator(); i.hasNext();) {
try {
(i.next()).validate(value);
} catch (final Validator.InvalidValueException e) {
if (firstError == null) {
firstError = e;
} else {
if (errors == null) {
errors = new LinkedList<InvalidValueException>();
errors.add(firstError);
}
errors.add(e);
}
}
}
// If there were no error
if (firstError == null) {
return;
}
// If only one error occurred, throw it forwards
if (errors == null) {
throw firstError;
}
// Creates composite validator
final Validator.InvalidValueException[] exceptions = new Validator.InvalidValueException[errors
.size()];
int index = 0;
for (final Iterator<InvalidValueException> i = errors.iterator(); i
.hasNext();) {
exceptions[index++] = i.next();
}
throw new Validator.InvalidValueException(null, exceptions);
}
/**
* 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()
*/
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.
* <p>
* 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.
* </p>
*
* @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 */
/**
* This method listens to data source value changes and passes the changes
* forwards.
*
* @param event
* the value change event telling the data source contents have
* changed.
*/
public void valueChange(Property.ValueChangeEvent event) {
if (isReadThrough() || !isModified()) {
fireValueChange(false);
}
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.Component.Focusable#focus()
*/
@Override
public void focus() {
super.focus();
}
/*
* (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;
}
/**
* Sets the internal field value. This is purely used by CustomField to
* change the internal Field value. It does not trigger valuechange events.
* It can be overriden 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();
}
}
/**
* 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 <code>true</code> if the field is required .otherwise
* <code>false</code>.
*/
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();
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.Field#getRequiredError()
*/
public String getRequiredError() {
return requiredError;
}
/**
* Is the field empty?
*
* In general, "empty" state is same as null..
*/
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;
}
}
public void setCurrentBufferedSourceException(
Buffered.SourceException currentBufferedSourceException) {
this.currentBufferedSourceException = currentBufferedSourceException;
requestRepaint();
}
}