Mercurial > projects > dwt2
view org.eclipse.core.databinding/src/org/eclipse/core/databinding/validation/MultiValidator.d @ 85:6be48cf9f95c
Work on databinding
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 18 Apr 2009 13:54:50 +0200 |
parents | 383ce7bd736b |
children | 9e0ab372d5d8 |
line wrap: on
line source
/******************************************************************************* * 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 org.eclipse.core.databinding.validation.ValidationStatus; 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; class TargetsListener : 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; class DependencyListener : 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) { targetsListener = new TargetsListener(); dependencyListener = new DependencyListener(); Assert.isNotNull(realm, "Realm cannot be null"); //$NON-NLS-1$ this.realm = realm; validationStatus = new WritableValue(realm, cast(Object)ValidationStatus.ok(), typeid(IStatus)); targets = new WritableList(realm, new ArrayList(), typeid(IObservable)); targets.addListChangeListener(targetsListener); unmodifiableTargets = Observables.unmodifiableObservableList(targets); models = Observables.emptyObservableList(realm); } private void checkObservable(IObservable target) { Assert.isNotNull(cast(Object)target, "Target observable cannot be null"); //$NON-NLS-1$ Assert .isTrue(cast(bool)realm.opEquals(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(cast(Object)status); } catch (RuntimeException e) { // Usually an NPE as dependencies are // init'ed validationStatus.setValue(cast(Object)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(); } }