### Eclipse Workspace Patch 1.0 #P org.eclipse.core.databinding Index: src/org/eclipse/core/databinding/validation/MultiValidator.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/databinding/validation/MultiValidator.java,v retrieving revision 1.1 diff -u -r1.1 MultiValidator.java --- src/org/eclipse/core/databinding/validation/MultiValidator.java 24 Mar 2008 22:55:58 -0000 1.1 +++ src/org/eclipse/core/databinding/validation/MultiValidator.java 17 Jun 2008 16:13:53 -0000 @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.eclipse.core.databinding.ValidationStatusProvider; import org.eclipse.core.databinding.observable.ChangeEvent; @@ -195,14 +196,22 @@ } private void revalidate() { + final StatusHandler handler = new StatusHandler() { + public void handleStatus(final IStatus status) { + realm.exec(new Runnable() { + public void run() { + validationStatus + .setValue(status == null ? ValidationStatus + .ok() : status); + } + }); + } + }; final IObservable[] dependencies = ObservableTracker.runAndMonitor( new Runnable() { public void run() { try { - IStatus status = validate(); - if (status == null) - status = ValidationStatus.ok(); - validationStatus.setValue(status); + validate(handler); } catch (RuntimeException e) { // Usually an NPE as dependencies are // init'ed @@ -213,12 +222,71 @@ }, null, null); ObservableTracker.runAndIgnore(new Runnable() { public void run() { - targets.clear(); - targets.addAll(Arrays.asList(dependencies)); + List dependencyList = new ArrayList(Arrays.asList(dependencies)); + // remove old targets not in the new dependency list + targets.retainAll(dependencyList); + // remove targets in the new dependency list which are already + // known + dependencyList.removeAll(targets); + // add the new dependencies + targets.addAll(dependencyList); } }); } + interface StatusHandler { + void handleStatus(IStatus status); + } + + /** + * Calculates and passes the current validation status to the given + * callback. + *

+ * 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. + *

+ * If validation is being performed asynchronously (i.e. in a different + * thread), then the observables which are used to calculate validation + * status should be accessed before forking, as follows: + * + *

+	 * MultiValidator validator = new MultiValidator() {
+	 * 	protected void validate(final StatusHandler callback) {
+	 *      // Observables must be accessed in the calling thread
+	 * 		final Integer value0 = (Integer) middle0.getValue();
+	 * 		final Integer value1 = (Integer) middle1.getValue();
+	 * 		new Thread(new Runnable() {
+	 * 			public void run() {
+	 * 		        // Calculate the validation status
+	 * 				if (Math.abs(value0.intValue()) % 2 != Math.abs(value1
+	 * 						.intValue()) % 2) {
+	 *                  callback.handleStatus(ValidationStatus.error(
+	 *                      "Values must be both even or both odd");
+	 *              } else {
+	 *                  callback.handleStatus(ValidationStatus.ok());
+	 *              }
+	 * 			}
+	 * 		}).start();
+	 * 	}
+	 * };
+	 * 
+ * + * @param callback + * the callback which is to receive the validation status. The + * callback may be called from outside the realm. + */ + protected void validate(StatusHandler callback) { + try { + callback.handleStatus(validate()); + } catch (RuntimeException e) { + // Usually an NPE as dependencies are + // init'ed + callback.handleStatus(ValidationStatus.error(e.getMessage(), e)); + } + } + /** * Return the current validation status. *

@@ -229,7 +297,10 @@ * * @return the current validation status. */ - protected abstract IStatus validate(); + protected IStatus validate() { + throw new IllegalStateException( + "Subclasses must override one of the validate() methods"); //$NON-NLS-1$ + } /** * Returns a wrapper {@link IObservableValue} which stays in sync with the