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