Improving reuse of JFace Data Binding validators
August 22, 2012 | 3 min ReadJFace Data Binding allows you to attach only one validator to a binding. Putting all constraints into one validator is essentially bad design and compromises reusability. Here’s a trick for dealing with this limitation of the data binding API and at the same time, keeping your validators small and reusable.
When binding a source observable to a target observable in JFace Data Binding, it is possible to attach a validator to the corresponding UpdateValueStrategy
. The validator is triggered when the value of the source observable is updated and the change is propagated to the target observable. The API of the UpdateValueStrategy
offers three methods to attach a validator, where each one is executed at a different point in time in the update workflow:
setAfterGetValidator
setAfterConvertValidator
setBeforeSetValidator
These methods are setters, so you can attach at most three different validators. Most of the time you will want to use only one of the methods, namely the one that fits best with the way you use data binding in your Eclipse RCP application (update immediately or on request), and how you write your validators (using the source or the target data type). So in practice, the API forces you to limit yourself to one validator per binding.
Let’s assume you have a text field where the user can enter a customer ID. There are usually different constraints on the ID, e.g. an obligatory number of characters to be entered or uniqueness among the existing IDs. All these constraints could be put into one implementation of IValidator
, but this would mean mixing concerns that are independent of each other. Apart from the design flaw, writing unit tests for this validator would be complicated and being able to reuse it for other bindings is very unlikely.
The solution is to create an IValidator
implementation whose only job is to execute a list of validators one after the other and return their aggregated results:
public class CompoundValidator implements IValidator {
private final IValidator[] validators;
public CompoundValidator(final IValidator... validators) {
this.validators = validators;
}
public IStatus validate(final Object value) {
IStatus result = ValidationStatus.ok();
for (IValidator validator : validators) {
IStatus status = validator.validate(value);
if (status.getSeverity() > result.getSeverity()) {
result = status;
}
}
return result;
}
}
To attach multiple validators to a binding, the CompoundValidator
is initialized with all the required validators and passed to the UpdateValueStrategy
:
DataBindingContext dbc = new DataBindingContext();
LengthValidator lengthValidator = new LengthValidator(5);
UniquenessValidator uniquenessValidator = new UniquenessValidator("11111", "12345", "11880");
CompoundValidator compoundValidator = new CompoundValidator(lengthValidator, uniquenessValidator);
dbc.bindValue(SWTObservables.observeText(text, SWT.Modify), value,
new UpdateValueStrategy().setAfterGetValidator(compoundValidator), null);
The above implementation of the CompoundValidator
returns the (first) validation status with the highest severity. Depending on your use case, you might choose different aggregation strategies. For example, if you have enough space in your user interface, returning a MultiStatus
with all (non-ok) validation results would allow you to display all relevant validation results to the user at once.
This small class helped us a lot. Hope you can benefit from it as well.