Mercurial > projects > dwt2
diff org.eclipse.core.databinding/src/org/eclipse/core/databinding/validation/MultiValidator.d @ 78:0a55d2d5a946
Added file for databinding
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Tue, 14 Apr 2009 11:35:29 +0200 |
parents | |
children | 383ce7bd736b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding/src/org/eclipse/core/databinding/validation/MultiValidator.d Tue Apr 14 11:35:29 2009 +0200 @@ -0,0 +1,371 @@ +/******************************************************************************* + * Copyright (c) 2008 Matthew Hall and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Hall - initial API and implementation (bug 218269) + * Boris Bokowski - bug 218269 + ******************************************************************************/ + +module org.eclipse.core.databinding.validation.MultiValidator; + +import java.lang.all; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.eclipse.core.databinding.ValidationStatusProvider; +import org.eclipse.core.databinding.observable.ChangeEvent; +import org.eclipse.core.databinding.observable.IChangeListener; +import org.eclipse.core.databinding.observable.IObservable; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.Observables; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.list.IListChangeListener; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.list.ListChangeEvent; +import org.eclipse.core.databinding.observable.list.ListDiffVisitor; +import org.eclipse.core.databinding.observable.list.WritableList; +import org.eclipse.core.databinding.observable.map.IObservableMap; +import org.eclipse.core.databinding.observable.set.IObservableSet; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.WritableValue; +import org.eclipse.core.internal.databinding.observable.ValidatedObservableList; +import org.eclipse.core.internal.databinding.observable.ValidatedObservableMap; +import org.eclipse.core.internal.databinding.observable.ValidatedObservableSet; +import org.eclipse.core.internal.databinding.observable.ValidatedObservableValue; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IStatus; + +/** + * A validator for cross-constraints between observables. + * + * <p> + * Some practical examples of cross-constraints: + * <ul> + * <li>A start date cannot be later than an end date + * <li>A list of percentages should add up to 100% + * </ul> + * <p> + * Example: require two integer fields to contain either both even or both odd + * numbers. + * + * <pre> + * DataBindingContext dbc = new DataBindingContext(); + * + * IObservableValue target0 = SWTObservables.observeText(text0, SWT.Modify); + * IObservableValue target1 = SWTObservables.observeText(text1, SWT.Modify); + * + * // Binding in two stages (from target to middle, then from middle to model) + * // simplifies the validation logic. Using the middle observables saves + * // the trouble of converting the target values cast(Strings) to the model type + * // (integers) manually during validation. + * final IObservableValue middle0 = new WritableValue(null, Integer.TYPE); + * final IObservableValue middle1 = new WritableValue(null, Integer.TYPE); + * dbc.bind(target0, middle0, null, null); + * dbc.bind(target1, middle1, null, null); + * + * // Create the multi-validator + * MultiValidator validator = new class() MultiValidator { + * protected IStatus validate() { + * // Calculate the validation status + * Integer value0 = cast(Integer) middle0.getValue(); + * Integer value1 = cast(Integer) middle1.getValue(); + * if (Math.abs(value0.intValue()) % 2 !is Math.abs(value1.intValue()) % 2) + * return ValidationStatus + * .error("Values must be both even or both odd"); + * return ValidationStatus.ok(); + * } + * }; + * dbc.addValidationStatusProvider(validator); + * + * // Bind the middle observables to the model observables. + * IObservableValue model0 = new WritableValue(new Integer(2), Integer.TYPE); + * IObservableValue model1 = new WritableValue(new Integer(4), Integer.TYPE); + * dbc.bind(middle0, model0, null, null); + * dbc.bind(middle1, model1, null, null); + * </pre> + * + * <p> + * MultiValidator can also prevent invalid data from being copied to model. This + * is done by wrapping each target observable in a validated observable, and + * then binding the validated observable to the model. + * + * <pre> + * + * ... + * + * // Validated observables do not change value until the validator passes. + * IObservableValue validated0 = validator.observeValidatedValue(middle0); + * IObservableValue validated1 = validator.observeValidatedValue(middle1); + * IObservableValue model0 = new WritableValue(new Integer(2), Integer.TYPE); + * IObservableValue model1 = new WritableValue(new Integer(4), Integer.TYPE); + * // Bind to the validated value, not the middle/target + * dbc.bind(validated0, model0, null, null); + * dbc.bind(validated1, model1, null, null); + * </pre> + * + * Note: No guarantee is made as to the order of updates when multiple validated + * observables change value at once (i.e. multiple updates pending when the + * status becomes valid). Therefore the model may be in an invalid state after + * the first but before the last pending update. + * + * @since 1.1 + */ +public abstract class MultiValidator : ValidationStatusProvider { + private Realm realm; + private IObservableValue validationStatus; + private IObservableValue unmodifiableValidationStatus; + private WritableList targets; + private IObservableList unmodifiableTargets; + private IObservableList models; + + IListChangeListener targetsListener = new class() IListChangeListener { + public void handleListChange(ListChangeEvent event) { + event.diff.accept(new class() ListDiffVisitor { + public void handleAdd(int index, Object element) { + (cast(IObservable) element) + .addChangeListener(dependencyListener); + } + + public void handleRemove(int index, Object element) { + (cast(IObservable) element) + .removeChangeListener(dependencyListener); + } + }); + } + }; + + private IChangeListener dependencyListener = new class() IChangeListener { + public void handleChange(ChangeEvent event) { + revalidate(); + } + }; + + /** + * Constructs a MultiValidator on the default realm. + */ + public this() { + this(Realm.getDefault()); + } + + /** + * Constructs a MultiValidator on the given realm. + * + * @param realm + * the realm on which validation takes place. + */ + public this(Realm realm) { + Assert.isNotNull(realm, "Realm cannot be null"); //$NON-NLS-1$ + this.realm = realm; + + validationStatus = new WritableValue(realm, ValidationStatus.ok(), + IStatus.classinfo); + + targets = new WritableList(realm, new ArrayList(), IObservable.classinfo); + targets.addListChangeListener(targetsListener); + unmodifiableTargets = Observables.unmodifiableObservableList(targets); + + models = Observables.emptyObservableList(realm); + } + + private void checkObservable(IObservable target) { + Assert.isNotNull(target, "Target observable cannot be null"); //$NON-NLS-1$ + Assert + .isTrue(realm.equals(target.getRealm()), + "Target observable must be in the same realm as MultiValidator"); //$NON-NLS-1$ + } + + /** + * Returns an {@link IObservableValue} whose value is always the current + * validation status of this MultiValidator. The returned observable is in + * the same realm as this MultiValidator. + * + * @return an {@link IObservableValue} whose value is always the current + * validation status of this MultiValidator. + */ + public IObservableValue getValidationStatus() { + if (unmodifiableValidationStatus is null) { + revalidate(); + unmodifiableValidationStatus = Observables + .unmodifiableObservableValue(validationStatus); + } + return unmodifiableValidationStatus; + } + + private void revalidate() { + final IObservable[] dependencies = ObservableTracker.runAndMonitor( + new class() Runnable { + public void run() { + try { + IStatus status = validate(); + if (status is null) + status = ValidationStatus.ok(); + validationStatus.setValue(status); + } catch (RuntimeException e) { + // Usually an NPE as dependencies are + // init'ed + validationStatus.setValue(ValidationStatus.error(e + .getMessage(), e)); + } + } + }, null, null); + ObservableTracker.runAndIgnore(new class() Runnable { + public void run() { + targets.clear(); + targets.addAll(Arrays.asList(dependencies)); + } + }); + } + + /** + * Return the current validation status. + * <p> + * Note: To ensure that the validation status is kept current, all + * dependencies used to calculate status should be accessed through + * {@link IObservable} instances. Each dependency observable must be in the + * same realm as the MultiValidator. + * + * @return the current validation status. + */ + protected abstract IStatus validate(); + + /** + * Returns a wrapper {@link IObservableValue} which stays in sync with the + * given target observable only when the validation status is valid. + * Statuses of {@link IStatus#OK OK}, {@link IStatus#INFO INFO} or + * {@link IStatus#WARNING WARNING} severity are considered valid. + * <p> + * The wrapper behaves as follows with respect to the validation status: + * <ul> + * <li>While valid, the wrapper stays in sync with its target observable. + * <li>While invalid, the wrapper's value is the target observable's last + * valid value. If the target changes value, a stale event is fired + * signaling that a change is pending. + * <li>When status changes from invalid to valid, the wrapper takes the + * value of the target observable, and synchronization resumes. + * </ul> + * + * @param target + * the target observable being wrapped. Must be in the same realm + * as the MultiValidator. + * @return an IObservableValue which stays in sync with the given target + * observable only with the validation status is valid. + */ + public IObservableValue observeValidatedValue(IObservableValue target) { + checkObservable(target); + return new ValidatedObservableValue(target, getValidationStatus()); + } + + /** + * Returns a wrapper {@link IObservableList} which stays in sync with the + * given target observable only when the validation status is valid. + * Statuses of {@link IStatus#OK OK}, {@link IStatus#INFO INFO} or + * {@link IStatus#WARNING WARNING} severity are considered valid. + * <p> + * The wrapper behaves as follows with respect to the validation status: + * <ul> + * <li>While valid, the wrapper stays in sync with its target observable. + * <li>While invalid, the wrapper's elements are the target observable's + * last valid elements. If the target changes elements, a stale event is + * fired signaling that a change is pending. + * <li>When status changes from invalid to valid, the wrapper takes the + * elements of the target observable, and synchronization resumes. + * </ul> + * + * @param target + * the target observable being wrapped. Must be in the same realm + * as the MultiValidator. + * @return an IObservableValue which stays in sync with the given target + * observable only with the validation status is valid. + */ + public IObservableList observeValidatedList(IObservableList target) { + checkObservable(target); + return new ValidatedObservableList(target, getValidationStatus()); + } + + /** + * Returns a wrapper {@link IObservableSet} which stays in sync with the + * given target observable only when the validation status is valid. + * Statuses of {@link IStatus#OK OK}, {@link IStatus#INFO INFO} or + * {@link IStatus#WARNING WARNING} severity are considered valid. + * <p> + * The wrapper behaves as follows with respect to the validation status: + * <ul> + * <li>While valid, the wrapper stays in sync with its target observable. + * <li>While invalid, the wrapper's elements are the target observable's + * last valid elements. If the target changes elements, a stale event is + * fired signaling that a change is pending. + * <li>When status changes from invalid to valid, the wrapper takes the + * elements of the target observable, and synchronization resumes. + * </ul> + * + * @param target + * the target observable being wrapped. Must be in the same realm + * as the MultiValidator. + * @return an IObservableValue which stays in sync with the given target + * observable only with the validation status is valid. + */ + public IObservableSet observeValidatedSet(IObservableSet target) { + checkObservable(target); + return new ValidatedObservableSet(target, getValidationStatus()); + } + + /** + * Returns a wrapper {@link IObservableMap} which stays in sync with the + * given target observable only when the validation status is valid. + * Statuses of {@link IStatus#OK OK}, {@link IStatus#INFO INFO} or + * {@link IStatus#WARNING WARNING} severity are considered valid. + * <p> + * The wrapper behaves as follows with respect to the validation status: + * <ul> + * <li>While valid, the wrapper stays in sync with its target observable. + * <li>While invalid, the wrapper's entries are the target observable's + * last valid entries. If the target changes entries, a stale event is fired + * signaling that a change is pending. + * <li>When status changes from invalid to valid, the wrapper takes the + * entries of the target observable, and synchronization resumes. + * </ul> + * + * @param target + * the target observable being wrapped. Must be in the same realm + * as the MultiValidator. + * @return an IObservableValue which stays in sync with the given target + * observable only with the validation status is valid. + */ + public IObservableMap observeValidatedMap(IObservableMap target) { + checkObservable(target); + return new ValidatedObservableMap(target, getValidationStatus()); + } + + public IObservableList getTargets() { + return unmodifiableTargets; + } + + public IObservableList getModels() { + return models; + } + + public void dispose() { + targets.clear(); // Remove listeners from dependencies + + unmodifiableValidationStatus.dispose(); + validationStatus.dispose(); + unmodifiableTargets.dispose(); + targets.dispose(); + models.dispose(); + + realm = null; + validationStatus = null; + unmodifiableValidationStatus = null; + targets = null; + unmodifiableTargets = null; + models = null; + + super.dispose(); + } + +}