Mercurial > projects > dwt2
comparison 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 |
comparison
equal
deleted
inserted
replaced
76:f05e6e8b2f2d | 78:0a55d2d5a946 |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2008 Matthew Hall and others. | |
3 * All rights reserved. This program and the accompanying materials | |
4 * are made available under the terms of the Eclipse Public License v1.0 | |
5 * which accompanies this distribution, and is available at | |
6 * http://www.eclipse.org/legal/epl-v10.html | |
7 * | |
8 * Contributors: | |
9 * Matthew Hall - initial API and implementation (bug 218269) | |
10 * Boris Bokowski - bug 218269 | |
11 ******************************************************************************/ | |
12 | |
13 module org.eclipse.core.databinding.validation.MultiValidator; | |
14 | |
15 import java.lang.all; | |
16 | |
17 import java.util.ArrayList; | |
18 import java.util.Arrays; | |
19 | |
20 import org.eclipse.core.databinding.ValidationStatusProvider; | |
21 import org.eclipse.core.databinding.observable.ChangeEvent; | |
22 import org.eclipse.core.databinding.observable.IChangeListener; | |
23 import org.eclipse.core.databinding.observable.IObservable; | |
24 import org.eclipse.core.databinding.observable.ObservableTracker; | |
25 import org.eclipse.core.databinding.observable.Observables; | |
26 import org.eclipse.core.databinding.observable.Realm; | |
27 import org.eclipse.core.databinding.observable.list.IListChangeListener; | |
28 import org.eclipse.core.databinding.observable.list.IObservableList; | |
29 import org.eclipse.core.databinding.observable.list.ListChangeEvent; | |
30 import org.eclipse.core.databinding.observable.list.ListDiffVisitor; | |
31 import org.eclipse.core.databinding.observable.list.WritableList; | |
32 import org.eclipse.core.databinding.observable.map.IObservableMap; | |
33 import org.eclipse.core.databinding.observable.set.IObservableSet; | |
34 import org.eclipse.core.databinding.observable.value.IObservableValue; | |
35 import org.eclipse.core.databinding.observable.value.WritableValue; | |
36 import org.eclipse.core.internal.databinding.observable.ValidatedObservableList; | |
37 import org.eclipse.core.internal.databinding.observable.ValidatedObservableMap; | |
38 import org.eclipse.core.internal.databinding.observable.ValidatedObservableSet; | |
39 import org.eclipse.core.internal.databinding.observable.ValidatedObservableValue; | |
40 import org.eclipse.core.runtime.Assert; | |
41 import org.eclipse.core.runtime.IStatus; | |
42 | |
43 /** | |
44 * A validator for cross-constraints between observables. | |
45 * | |
46 * <p> | |
47 * Some practical examples of cross-constraints: | |
48 * <ul> | |
49 * <li>A start date cannot be later than an end date | |
50 * <li>A list of percentages should add up to 100% | |
51 * </ul> | |
52 * <p> | |
53 * Example: require two integer fields to contain either both even or both odd | |
54 * numbers. | |
55 * | |
56 * <pre> | |
57 * DataBindingContext dbc = new DataBindingContext(); | |
58 * | |
59 * IObservableValue target0 = SWTObservables.observeText(text0, SWT.Modify); | |
60 * IObservableValue target1 = SWTObservables.observeText(text1, SWT.Modify); | |
61 * | |
62 * // Binding in two stages (from target to middle, then from middle to model) | |
63 * // simplifies the validation logic. Using the middle observables saves | |
64 * // the trouble of converting the target values cast(Strings) to the model type | |
65 * // (integers) manually during validation. | |
66 * final IObservableValue middle0 = new WritableValue(null, Integer.TYPE); | |
67 * final IObservableValue middle1 = new WritableValue(null, Integer.TYPE); | |
68 * dbc.bind(target0, middle0, null, null); | |
69 * dbc.bind(target1, middle1, null, null); | |
70 * | |
71 * // Create the multi-validator | |
72 * MultiValidator validator = new class() MultiValidator { | |
73 * protected IStatus validate() { | |
74 * // Calculate the validation status | |
75 * Integer value0 = cast(Integer) middle0.getValue(); | |
76 * Integer value1 = cast(Integer) middle1.getValue(); | |
77 * if (Math.abs(value0.intValue()) % 2 !is Math.abs(value1.intValue()) % 2) | |
78 * return ValidationStatus | |
79 * .error("Values must be both even or both odd"); | |
80 * return ValidationStatus.ok(); | |
81 * } | |
82 * }; | |
83 * dbc.addValidationStatusProvider(validator); | |
84 * | |
85 * // Bind the middle observables to the model observables. | |
86 * IObservableValue model0 = new WritableValue(new Integer(2), Integer.TYPE); | |
87 * IObservableValue model1 = new WritableValue(new Integer(4), Integer.TYPE); | |
88 * dbc.bind(middle0, model0, null, null); | |
89 * dbc.bind(middle1, model1, null, null); | |
90 * </pre> | |
91 * | |
92 * <p> | |
93 * MultiValidator can also prevent invalid data from being copied to model. This | |
94 * is done by wrapping each target observable in a validated observable, and | |
95 * then binding the validated observable to the model. | |
96 * | |
97 * <pre> | |
98 * | |
99 * ... | |
100 * | |
101 * // Validated observables do not change value until the validator passes. | |
102 * IObservableValue validated0 = validator.observeValidatedValue(middle0); | |
103 * IObservableValue validated1 = validator.observeValidatedValue(middle1); | |
104 * IObservableValue model0 = new WritableValue(new Integer(2), Integer.TYPE); | |
105 * IObservableValue model1 = new WritableValue(new Integer(4), Integer.TYPE); | |
106 * // Bind to the validated value, not the middle/target | |
107 * dbc.bind(validated0, model0, null, null); | |
108 * dbc.bind(validated1, model1, null, null); | |
109 * </pre> | |
110 * | |
111 * Note: No guarantee is made as to the order of updates when multiple validated | |
112 * observables change value at once (i.e. multiple updates pending when the | |
113 * status becomes valid). Therefore the model may be in an invalid state after | |
114 * the first but before the last pending update. | |
115 * | |
116 * @since 1.1 | |
117 */ | |
118 public abstract class MultiValidator : ValidationStatusProvider { | |
119 private Realm realm; | |
120 private IObservableValue validationStatus; | |
121 private IObservableValue unmodifiableValidationStatus; | |
122 private WritableList targets; | |
123 private IObservableList unmodifiableTargets; | |
124 private IObservableList models; | |
125 | |
126 IListChangeListener targetsListener = new class() IListChangeListener { | |
127 public void handleListChange(ListChangeEvent event) { | |
128 event.diff.accept(new class() ListDiffVisitor { | |
129 public void handleAdd(int index, Object element) { | |
130 (cast(IObservable) element) | |
131 .addChangeListener(dependencyListener); | |
132 } | |
133 | |
134 public void handleRemove(int index, Object element) { | |
135 (cast(IObservable) element) | |
136 .removeChangeListener(dependencyListener); | |
137 } | |
138 }); | |
139 } | |
140 }; | |
141 | |
142 private IChangeListener dependencyListener = new class() IChangeListener { | |
143 public void handleChange(ChangeEvent event) { | |
144 revalidate(); | |
145 } | |
146 }; | |
147 | |
148 /** | |
149 * Constructs a MultiValidator on the default realm. | |
150 */ | |
151 public this() { | |
152 this(Realm.getDefault()); | |
153 } | |
154 | |
155 /** | |
156 * Constructs a MultiValidator on the given realm. | |
157 * | |
158 * @param realm | |
159 * the realm on which validation takes place. | |
160 */ | |
161 public this(Realm realm) { | |
162 Assert.isNotNull(realm, "Realm cannot be null"); //$NON-NLS-1$ | |
163 this.realm = realm; | |
164 | |
165 validationStatus = new WritableValue(realm, ValidationStatus.ok(), | |
166 IStatus.classinfo); | |
167 | |
168 targets = new WritableList(realm, new ArrayList(), IObservable.classinfo); | |
169 targets.addListChangeListener(targetsListener); | |
170 unmodifiableTargets = Observables.unmodifiableObservableList(targets); | |
171 | |
172 models = Observables.emptyObservableList(realm); | |
173 } | |
174 | |
175 private void checkObservable(IObservable target) { | |
176 Assert.isNotNull(target, "Target observable cannot be null"); //$NON-NLS-1$ | |
177 Assert | |
178 .isTrue(realm.equals(target.getRealm()), | |
179 "Target observable must be in the same realm as MultiValidator"); //$NON-NLS-1$ | |
180 } | |
181 | |
182 /** | |
183 * Returns an {@link IObservableValue} whose value is always the current | |
184 * validation status of this MultiValidator. The returned observable is in | |
185 * the same realm as this MultiValidator. | |
186 * | |
187 * @return an {@link IObservableValue} whose value is always the current | |
188 * validation status of this MultiValidator. | |
189 */ | |
190 public IObservableValue getValidationStatus() { | |
191 if (unmodifiableValidationStatus is null) { | |
192 revalidate(); | |
193 unmodifiableValidationStatus = Observables | |
194 .unmodifiableObservableValue(validationStatus); | |
195 } | |
196 return unmodifiableValidationStatus; | |
197 } | |
198 | |
199 private void revalidate() { | |
200 final IObservable[] dependencies = ObservableTracker.runAndMonitor( | |
201 new class() Runnable { | |
202 public void run() { | |
203 try { | |
204 IStatus status = validate(); | |
205 if (status is null) | |
206 status = ValidationStatus.ok(); | |
207 validationStatus.setValue(status); | |
208 } catch (RuntimeException e) { | |
209 // Usually an NPE as dependencies are | |
210 // init'ed | |
211 validationStatus.setValue(ValidationStatus.error(e | |
212 .getMessage(), e)); | |
213 } | |
214 } | |
215 }, null, null); | |
216 ObservableTracker.runAndIgnore(new class() Runnable { | |
217 public void run() { | |
218 targets.clear(); | |
219 targets.addAll(Arrays.asList(dependencies)); | |
220 } | |
221 }); | |
222 } | |
223 | |
224 /** | |
225 * Return the current validation status. | |
226 * <p> | |
227 * Note: To ensure that the validation status is kept current, all | |
228 * dependencies used to calculate status should be accessed through | |
229 * {@link IObservable} instances. Each dependency observable must be in the | |
230 * same realm as the MultiValidator. | |
231 * | |
232 * @return the current validation status. | |
233 */ | |
234 protected abstract IStatus validate(); | |
235 | |
236 /** | |
237 * Returns a wrapper {@link IObservableValue} which stays in sync with the | |
238 * given target observable only when the validation status is valid. | |
239 * Statuses of {@link IStatus#OK OK}, {@link IStatus#INFO INFO} or | |
240 * {@link IStatus#WARNING WARNING} severity are considered valid. | |
241 * <p> | |
242 * The wrapper behaves as follows with respect to the validation status: | |
243 * <ul> | |
244 * <li>While valid, the wrapper stays in sync with its target observable. | |
245 * <li>While invalid, the wrapper's value is the target observable's last | |
246 * valid value. If the target changes value, a stale event is fired | |
247 * signaling that a change is pending. | |
248 * <li>When status changes from invalid to valid, the wrapper takes the | |
249 * value of the target observable, and synchronization resumes. | |
250 * </ul> | |
251 * | |
252 * @param target | |
253 * the target observable being wrapped. Must be in the same realm | |
254 * as the MultiValidator. | |
255 * @return an IObservableValue which stays in sync with the given target | |
256 * observable only with the validation status is valid. | |
257 */ | |
258 public IObservableValue observeValidatedValue(IObservableValue target) { | |
259 checkObservable(target); | |
260 return new ValidatedObservableValue(target, getValidationStatus()); | |
261 } | |
262 | |
263 /** | |
264 * Returns a wrapper {@link IObservableList} which stays in sync with the | |
265 * given target observable only when the validation status is valid. | |
266 * Statuses of {@link IStatus#OK OK}, {@link IStatus#INFO INFO} or | |
267 * {@link IStatus#WARNING WARNING} severity are considered valid. | |
268 * <p> | |
269 * The wrapper behaves as follows with respect to the validation status: | |
270 * <ul> | |
271 * <li>While valid, the wrapper stays in sync with its target observable. | |
272 * <li>While invalid, the wrapper's elements are the target observable's | |
273 * last valid elements. If the target changes elements, a stale event is | |
274 * fired signaling that a change is pending. | |
275 * <li>When status changes from invalid to valid, the wrapper takes the | |
276 * elements of the target observable, and synchronization resumes. | |
277 * </ul> | |
278 * | |
279 * @param target | |
280 * the target observable being wrapped. Must be in the same realm | |
281 * as the MultiValidator. | |
282 * @return an IObservableValue which stays in sync with the given target | |
283 * observable only with the validation status is valid. | |
284 */ | |
285 public IObservableList observeValidatedList(IObservableList target) { | |
286 checkObservable(target); | |
287 return new ValidatedObservableList(target, getValidationStatus()); | |
288 } | |
289 | |
290 /** | |
291 * Returns a wrapper {@link IObservableSet} which stays in sync with the | |
292 * given target observable only when the validation status is valid. | |
293 * Statuses of {@link IStatus#OK OK}, {@link IStatus#INFO INFO} or | |
294 * {@link IStatus#WARNING WARNING} severity are considered valid. | |
295 * <p> | |
296 * The wrapper behaves as follows with respect to the validation status: | |
297 * <ul> | |
298 * <li>While valid, the wrapper stays in sync with its target observable. | |
299 * <li>While invalid, the wrapper's elements are the target observable's | |
300 * last valid elements. If the target changes elements, a stale event is | |
301 * fired signaling that a change is pending. | |
302 * <li>When status changes from invalid to valid, the wrapper takes the | |
303 * elements of the target observable, and synchronization resumes. | |
304 * </ul> | |
305 * | |
306 * @param target | |
307 * the target observable being wrapped. Must be in the same realm | |
308 * as the MultiValidator. | |
309 * @return an IObservableValue which stays in sync with the given target | |
310 * observable only with the validation status is valid. | |
311 */ | |
312 public IObservableSet observeValidatedSet(IObservableSet target) { | |
313 checkObservable(target); | |
314 return new ValidatedObservableSet(target, getValidationStatus()); | |
315 } | |
316 | |
317 /** | |
318 * Returns a wrapper {@link IObservableMap} which stays in sync with the | |
319 * given target observable only when the validation status is valid. | |
320 * Statuses of {@link IStatus#OK OK}, {@link IStatus#INFO INFO} or | |
321 * {@link IStatus#WARNING WARNING} severity are considered valid. | |
322 * <p> | |
323 * The wrapper behaves as follows with respect to the validation status: | |
324 * <ul> | |
325 * <li>While valid, the wrapper stays in sync with its target observable. | |
326 * <li>While invalid, the wrapper's entries are the target observable's | |
327 * last valid entries. If the target changes entries, a stale event is fired | |
328 * signaling that a change is pending. | |
329 * <li>When status changes from invalid to valid, the wrapper takes the | |
330 * entries of the target observable, and synchronization resumes. | |
331 * </ul> | |
332 * | |
333 * @param target | |
334 * the target observable being wrapped. Must be in the same realm | |
335 * as the MultiValidator. | |
336 * @return an IObservableValue which stays in sync with the given target | |
337 * observable only with the validation status is valid. | |
338 */ | |
339 public IObservableMap observeValidatedMap(IObservableMap target) { | |
340 checkObservable(target); | |
341 return new ValidatedObservableMap(target, getValidationStatus()); | |
342 } | |
343 | |
344 public IObservableList getTargets() { | |
345 return unmodifiableTargets; | |
346 } | |
347 | |
348 public IObservableList getModels() { | |
349 return models; | |
350 } | |
351 | |
352 public void dispose() { | |
353 targets.clear(); // Remove listeners from dependencies | |
354 | |
355 unmodifiableValidationStatus.dispose(); | |
356 validationStatus.dispose(); | |
357 unmodifiableTargets.dispose(); | |
358 targets.dispose(); | |
359 models.dispose(); | |
360 | |
361 realm = null; | |
362 validationStatus = null; | |
363 unmodifiableValidationStatus = null; | |
364 targets = null; | |
365 unmodifiableTargets = null; | |
366 models = null; | |
367 | |
368 super.dispose(); | |
369 } | |
370 | |
371 } |