Download
Getting Started
Members
Projects
Community
Marketplace
Events
Planet Eclipse
Newsletter
Videos
Participate
Report a Bug
Forums
Mailing Lists
Wiki
IRC
How to Contribute
Working Groups
Automotive
Internet of Things
LocationTech
Long-Term Support
PolarSys
Science
OpenMDM
More
Community
Marketplace
Events
Planet Eclipse
Newsletter
Videos
Participate
Report a Bug
Forums
Mailing Lists
Wiki
IRC
How to Contribute
Working Groups
Automotive
Internet of Things
LocationTech
Long-Term Support
PolarSys
Science
OpenMDM
Toggle navigation
Bugzilla – Attachment 105098 Details for
Bug 233191
[DataBinding] Support for asynchronous validation/conversion on a Binding
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
Log In
[x]
|
Terms of Use
|
Copyright Agent
[patch]
patch introducing asynchronous validations/conversions on a ValueBinding (includes some snippets)
patch_233191.txt (text/plain), 120.86 KB, created by
Ovidio Mallo
on 2008-06-16 18:03:45 EDT
(
hide
)
Description:
patch introducing asynchronous validations/conversions on a ValueBinding (includes some snippets)
Filename:
MIME Type:
Creator:
Ovidio Mallo
Created:
2008-06-16 18:03:45 EDT
Size:
120.86 KB
patch
obsolete
>### Eclipse Workspace Patch 1.0 >#P org.eclipse.jface.tests.databinding >Index: src/org/eclipse/core/tests/databinding/validation/MultiValidatorTest.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/databinding/validation/MultiValidatorTest.java,v >retrieving revision 1.1 >diff -u -r1.1 MultiValidatorTest.java >--- src/org/eclipse/core/tests/databinding/validation/MultiValidatorTest.java 24 Mar 2008 22:55:55 -0000 1.1 >+++ src/org/eclipse/core/tests/databinding/validation/MultiValidatorTest.java 16 Jun 2008 20:08:52 -0000 >@@ -7,12 +7,15 @@ > * > * Contributors: > * Matthew Hall - initial API and implementation (bug 218269) >+ * Ovidio Mallo - bug 233191 > ******************************************************************************/ > > package org.eclipse.core.tests.databinding.validation; > > import org.eclipse.core.databinding.DataBindingContext; >+import org.eclipse.core.databinding.observable.IStaleListener; > import org.eclipse.core.databinding.observable.Realm; >+import org.eclipse.core.databinding.observable.StaleEvent; > import org.eclipse.core.databinding.observable.value.IObservableValue; > import org.eclipse.core.databinding.observable.value.WritableValue; > import org.eclipse.core.databinding.validation.MultiValidator; >@@ -25,13 +28,13 @@ > > public class MultiValidatorTest extends AbstractDefaultRealmTestCase { > private WritableValue dependency; >- private MultiValidator validator; >+ private TestMultiValidator validator; > private IObservableValue validationStatus; > > protected void setUp() throws Exception { > super.setUp(); > dependency = new WritableValue(null, IStatus.class); >- validator = new MultiValidator() { >+ validator = new TestMultiValidator() { > protected IStatus validate() { > return (IStatus) dependency.getValue(); > } >@@ -58,7 +61,7 @@ > > public void testGetValidationStatus_ExceptionThrownYieldsErrorStatus() { > final RuntimeException e = new RuntimeException("message"); >- validator = new MultiValidator() { >+ validator = new TestMultiValidator() { > protected IStatus validate() { > throw e; > } >@@ -115,4 +118,49 @@ > assertEquals(target.getValue(), validated.getValue()); > assertFalse(validated.isStale()); > } >+ >+ public void testEnterExitStale_ValidationStatusStaleness() { >+ StaleCounter staleCounter = new StaleCounter(); >+ validationStatus.addStaleListener(staleCounter); >+ >+ assertEquals(0, staleCounter.count); >+ >+ validator.enterStaleDelegate(); >+ assertEquals(1, staleCounter.count); >+ assertTrue(validationStatus.isStale()); >+ >+ validator.exitStaleDelegate((IStatus) validationStatus.getValue()); >+ assertFalse(validationStatus.isStale()); >+ } >+ >+ public void testEnterExitStale_ValidationStatusValue() { >+ validator.enterStaleDelegate(); >+ IStatus status = ValidationStatus.error("done"); >+ validator.exitStaleDelegate(status); >+ assertSame(status, validationStatus.getValue()); >+ } >+ >+ /** >+ * Simple extension of the MultiValidator class which is functionally >+ * equivalent while making some methods accessible to the unit tests. >+ */ >+ private static abstract class TestMultiValidator extends MultiValidator { >+ >+ void enterStaleDelegate() { >+ enterStale(); >+ } >+ >+ void exitStaleDelegate(IStatus status) { >+ exitStale(status); >+ } >+ } >+ >+ private static class StaleCounter implements IStaleListener { >+ >+ int count; >+ >+ public void handleStale(StaleEvent event) { >+ count++; >+ } >+ } > } >Index: src/org/eclipse/core/tests/internal/databinding/QueueTest.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/QueueTest.java,v >retrieving revision 1.1 >diff -u -r1.1 QueueTest.java >--- src/org/eclipse/core/tests/internal/databinding/QueueTest.java 2 Oct 2007 19:33:52 -0000 1.1 >+++ src/org/eclipse/core/tests/internal/databinding/QueueTest.java 16 Jun 2008 20:08:52 -0000 >@@ -64,5 +64,14 @@ > assertEquals("moo", queue.dequeue()); > assertTrue(queue.isEmpty()); > } >- >+ >+ public void testClear() { >+ assertTrue(queue.isEmpty()); >+ queue.enqueue("foo"); >+ assertFalse(queue.isEmpty()); >+ queue.clear(); >+ assertTrue(queue.isEmpty()); >+ queue.clear(); >+ assertTrue(queue.isEmpty()); >+ } > } >Index: src/org/eclipse/jface/tests/databinding/scenarios/PropertyScenarios.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/databinding/scenarios/PropertyScenarios.java,v >retrieving revision 1.34 >diff -u -r1.34 PropertyScenarios.java >--- src/org/eclipse/jface/tests/databinding/scenarios/PropertyScenarios.java 21 Mar 2007 20:46:31 -0000 1.34 >+++ src/org/eclipse/jface/tests/databinding/scenarios/PropertyScenarios.java 16 Jun 2008 20:08:52 -0000 >@@ -203,6 +203,10 @@ > String remainingChars = modelValue.substring(1); > return firstChar.toUpperCase() + remainingChars.toLowerCase(); > } >+ >+ public boolean isAsync() { >+ return false; >+ } > }; > IConverter converter2 = new IConverter() { > public Object getFromType() { >@@ -216,6 +220,10 @@ > public Object convert(Object fromObject) { > return ((String) fromObject).toUpperCase(); > } >+ >+ public boolean isAsync() { >+ return false; >+ } > }; > > getDbc().bindValue(SWTObservables.observeText(text, SWT.Modify), >Index: src/org/eclipse/jface/tests/databinding/BindingTestSuite.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/databinding/BindingTestSuite.java,v >retrieving revision 1.84 >diff -u -r1.84 BindingTestSuite.java >--- src/org/eclipse/jface/tests/databinding/BindingTestSuite.java 25 Apr 2008 23:16:36 -0000 1.84 >+++ src/org/eclipse/jface/tests/databinding/BindingTestSuite.java 16 Jun 2008 20:08:52 -0000 >@@ -61,6 +61,8 @@ > import org.eclipse.core.tests.internal.databinding.BindingStatusTest; > import org.eclipse.core.tests.internal.databinding.QueueTest; > import org.eclipse.core.tests.internal.databinding.RandomAccessListIteratorTest; >+import org.eclipse.core.tests.internal.databinding.UpdateExecutorTest; >+import org.eclipse.core.tests.internal.databinding.UpdateValidationObservableValueTest; > import org.eclipse.core.tests.internal.databinding.beans.BeanObservableListDecoratorTest; > import org.eclipse.core.tests.internal.databinding.beans.BeanObservableSetDecoratorTest; > import org.eclipse.core.tests.internal.databinding.beans.BeanObservableValueDecoratorTest; >@@ -132,6 +134,7 @@ > import org.eclipse.jface.tests.databinding.viewers.ObservableSetContentProviderTest; > import org.eclipse.jface.tests.databinding.viewers.ObservableSetTreeContentProviderTest; > import org.eclipse.jface.tests.databinding.viewers.ViewersObservablesTest; >+import org.eclipse.jface.tests.databinding.wizard.WizardPageSupportTest; > import org.eclipse.jface.tests.examples.databinding.mask.internal.EditMaskLexerAndTokenTest; > import org.eclipse.jface.tests.examples.databinding.mask.internal.EditMaskParserTest; > import org.eclipse.jface.tests.internal.databinding.swt.ButtonObservableValueTest; >@@ -242,6 +245,8 @@ > addTestSuite(BindingStatusTest.class); > addTestSuite(RandomAccessListIteratorTest.class); > addTestSuite(QueueTest.class); >+ addTestSuite(UpdateExecutorTest.class); >+ addTest(UpdateValidationObservableValueTest.suite()); > > // org.eclipse.core.tests.internal.databinding.conversion > addTestSuite(DateConversionSupportTest.class); >@@ -332,7 +337,10 @@ > addTestSuite(ObservableSetContentProviderTest.class); > addTestSuite(ObservableSetTreeContentProviderTest.class); > addTestSuite(ViewersObservablesTest.class); >- >+ >+ // org.eclipse.jface.tests.databinding.wizard >+ addTestSuite(WizardPageSupportTest.class); >+ > //org.eclipse.jface.tests.example.databinding.mask.internal > addTestSuite(EditMaskLexerAndTokenTest.class); > addTestSuite(EditMaskParserTest.class); >Index: src/org/eclipse/core/tests/databinding/UpdateValueStrategyTest.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/databinding/UpdateValueStrategyTest.java,v >retrieving revision 1.2 >diff -u -r1.2 UpdateValueStrategyTest.java >--- src/org/eclipse/core/tests/databinding/UpdateValueStrategyTest.java 29 Apr 2007 04:12:54 -0000 1.2 >+++ src/org/eclipse/core/tests/databinding/UpdateValueStrategyTest.java 16 Jun 2008 20:08:52 -0000 >@@ -16,9 +16,13 @@ > import java.util.Date; > > import org.eclipse.core.databinding.UpdateValueStrategy; >+import org.eclipse.core.databinding.conversion.Converter; >+import org.eclipse.core.databinding.conversion.IConverter; > import org.eclipse.core.databinding.observable.value.IObservableValue; > import org.eclipse.core.databinding.observable.value.WritableValue; > import org.eclipse.core.databinding.validation.IValidator; >+import org.eclipse.core.databinding.validation.IValidator2; >+import org.eclipse.core.databinding.validation.ValidationStatus; > import org.eclipse.core.internal.databinding.validation.NumberToByteValidator; > import org.eclipse.core.internal.databinding.validation.NumberToDoubleValidator; > import org.eclipse.core.internal.databinding.validation.NumberToFloatValidator; >@@ -33,6 +37,7 @@ > import org.eclipse.core.internal.databinding.validation.StringToIntegerValidator; > import org.eclipse.core.internal.databinding.validation.StringToLongValidator; > import org.eclipse.core.internal.databinding.validation.StringToShortValidator; >+import org.eclipse.core.runtime.IStatus; > import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase; > > /** >@@ -138,7 +143,46 @@ > > assertSame(validator,strategy.validator); > } >- >+ >+ public void testIsAsync_DefaultDelegatesToValidatorsAndConverter() throws Exception { >+ IValidator2 asyncValidator = new IValidator2() { >+ public IStatus validate(Object value) { >+ return ValidationStatus.ok(); >+ } >+ >+ public boolean isAsync() { >+ return true; >+ } >+ }; >+ >+ IConverter asyncConverter = new Converter(null, null) { >+ public Object convert(Object fromObject) { >+ return null; >+ } >+ >+ public boolean isAsync() { >+ return true; >+ } >+ }; >+ >+ UpdateValueStrategy strategy; >+ >+ strategy = new UpdateValueStrategy(); >+ assertFalse(strategy.isAsync()); >+ >+ strategy = new UpdateValueStrategy().setAfterGetValidator(asyncValidator); >+ assertTrue(strategy.isAsync()); >+ >+ strategy = new UpdateValueStrategy().setConverter(asyncConverter); >+ assertTrue(strategy.isAsync()); >+ >+ strategy = new UpdateValueStrategy().setAfterConvertValidator(asyncValidator); >+ assertTrue(strategy.isAsync()); >+ >+ strategy = new UpdateValueStrategy().setBeforeSetValidator(asyncValidator); >+ assertTrue(strategy.isAsync()); >+ } >+ > private void assertDefaultValidator(Class fromType, Class toType, Class validatorType) { > WritableValue source = WritableValue.withValueType(fromType); > WritableValue destination = WritableValue.withValueType(toType); >Index: src/org/eclipse/core/tests/databinding/ValueBindingTest.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/databinding/ValueBindingTest.java,v >retrieving revision 1.2 >diff -u -r1.2 ValueBindingTest.java >--- src/org/eclipse/core/tests/databinding/ValueBindingTest.java 21 Apr 2007 22:35:33 -0000 1.2 >+++ src/org/eclipse/core/tests/databinding/ValueBindingTest.java 16 Jun 2008 20:08:52 -0000 >@@ -9,16 +9,24 @@ > * Brad Reynolds - initial API and implementation > * Brad Reynolds - bug 116920 > * Brad Reynolds - bug 164653, 159768 >+ * Ovidio Mallo - bug 233191 > ******************************************************************************/ > > package org.eclipse.core.tests.databinding; > >+import java.util.Random; >+ >+import org.eclipse.core.databinding.AggregateValidationStatus; > import org.eclipse.core.databinding.Binding; > import org.eclipse.core.databinding.DataBindingContext; > import org.eclipse.core.databinding.UpdateValueStrategy; >+import org.eclipse.core.databinding.conversion.Converter; >+import org.eclipse.core.databinding.conversion.IConverter; > import org.eclipse.core.databinding.observable.Diffs; > import org.eclipse.core.databinding.observable.value.AbstractObservableValue; > import org.eclipse.core.databinding.observable.value.IObservableValue; >+import org.eclipse.core.databinding.observable.value.IValueChangeListener; >+import org.eclipse.core.databinding.observable.value.ValueChangeEvent; > import org.eclipse.core.databinding.observable.value.ValueDiff; > import org.eclipse.core.databinding.observable.value.WritableValue; > import org.eclipse.core.databinding.validation.IValidator; >@@ -27,6 +35,7 @@ > import org.eclipse.core.runtime.IStatus; > import org.eclipse.core.runtime.MultiStatus; > import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase; >+import org.eclipse.swt.widgets.Display; > > /** > * @since 1.1 >@@ -228,7 +237,127 @@ > target.fireValueChange(Diffs.createValueDiff("", "")); > assertEquals("update does not occur", count, strategy.afterGetCount); > } >- >+ >+ public void testIsAsync() throws Exception { >+ UpdateValueStrategy asyncStrategy = new UpdateValueStrategy() { >+ public boolean isAsync() { >+ return true; >+ } >+ }; >+ >+ assertFalse(dbc.bindValue(target, model, null, null).isAsync()); >+ assertTrue(dbc.bindValue(target, model, asyncStrategy, null).isAsync()); >+ assertTrue(dbc.bindValue(target, model, null, asyncStrategy).isAsync()); >+ assertTrue(dbc.bindValue(target, model, asyncStrategy, asyncStrategy) >+ .isAsync()); >+ } >+ >+ public void testAsyncExecutesInSeparateThread() throws Exception { >+ final Thread realmThread = Thread.currentThread(); >+ >+ IConverter asyncConverter = new Converter(null, null) { >+ public Object convert(Object fromObject) { >+ assertNotSame(Thread.currentThread(), realmThread); >+ return null; >+ } >+ >+ public boolean isAsync() { >+ return true; >+ } >+ }; >+ >+ UpdateValueStrategy targetToModel = new UpdateValueStrategy(); >+ targetToModel.setConverter(asyncConverter); >+ UpdateValueStrategy modelToTarget = new UpdateValueStrategy(); >+ modelToTarget.setConverter(asyncConverter); >+ >+ Binding binding = dbc.bindValue(target, model, targetToModel, modelToTarget); >+ >+ assertTrue(binding.isAsync()); >+ >+ binding.updateTargetToModel(); >+ binding.updateModelToTarget(); >+ } >+ >+ public void testAsyncUpdateSerialization() throws Exception { >+ UpdateValueStrategy targetToModel = new UpdateValueStrategy(); >+ targetToModel.setConverter(asyncConverter()); >+ >+ UpdateValueStrategy modelToTarget = new UpdateValueStrategy(); >+ modelToTarget.setConverter(asyncConverter()); >+ >+ dbc.bindValue(target, model, targetToModel, modelToTarget); >+ >+ // Write to the target. >+ for (int i = 0; i <= 10; i++) { >+ target.setValue(String.valueOf(i)); >+ } >+ awaitPendingValidations(dbc); >+ // Check that the updates to the model have been correctly serialized. >+ assertEquals("10", model.getValue()); >+ >+ // Write to the model. >+ for (int i = 0; i <= 10; i++) { >+ model.setValue(String.valueOf(i)); >+ } >+ awaitPendingValidations(dbc); >+ // Check that the updates to the target have been correctly serialized. >+ assertEquals("10", target.getValue()); >+ >+ // Alternatively write to the target and model. >+ for (int i = 0; i <= 10; i++) { >+ if (i % 2 == 0) { >+ target.setValue(String.valueOf(i)); >+ } else { >+ model.setValue(String.valueOf(i)); >+ } >+ } >+ awaitPendingValidations(dbc); >+ // Check that target and model end up having the correct value. >+ assertEquals("10", target.getValue()); >+ assertEquals("10", model.getValue()); >+ } >+ >+ public void testDisposeCancelsPendingUpdates() throws Exception { >+ UpdateValueStrategy targetToModel = new UpdateValueStrategy(); >+ targetToModel.setConverter(asyncConverter()); >+ >+ final Binding binding = dbc.bindValue(target, model, targetToModel, >+ null); >+ >+ model.addValueChangeListener(new IValueChangeListener() { >+ public void handleValueChange(ValueChangeEvent event) { >+ fail("No update should get to the model if we dispose the Binding."); >+ } >+ }); >+ >+ // Note that neither v1 nor v2 should make their way to the model since >+ // we only dispatch the events on the UI thread by calling the method >+ // awaitPendingValidations(...) below after having disposed the binding. >+ target.setValue("v1"); >+ binding.dispose(); >+ target.setValue("v2"); >+ >+ awaitPendingValidations(dbc); >+ } >+ >+ private void awaitPendingValidations(DataBindingContext dbc) { >+ AggregateValidationStatus validation = new AggregateValidationStatus( >+ dbc, AggregateValidationStatus.MERGED); >+ >+ while (validation.isStale()) { >+ // By dispatching on the Display, we get the pending runnables >+ // executed on the Realm belonging to the Display. >+ Display display = Display.getCurrent(); >+ while (display.readAndDispatch()) { >+ // just dispatch >+ } >+ if (validation.isStale()) { >+ display.sleep(); >+ } >+ } >+ } >+ > private IValidator warningValidator() { > return new IValidator() { > public IStatus validate(Object value) { >@@ -261,6 +390,26 @@ > }; > } > >+ private static IConverter asyncConverter() { >+ return new Converter(String.class, String.class) { >+ >+ private final Random random = new Random(System.currentTimeMillis()); >+ >+ public Object convert(Object fromObject) { >+ try { >+ Thread.sleep(random.nextInt(100)); >+ } catch (InterruptedException e) { >+ // do nothing >+ } >+ return fromObject; >+ } >+ >+ public boolean isAsync() { >+ return true; >+ } >+ }; >+ } >+ > private static class ObservableValueStub extends AbstractObservableValue { > protected Object doGetValue() { > // do nothing >Index: src/org/eclipse/core/tests/internal/databinding/UpdateValidationObservableValueTest.java >=================================================================== >RCS file: src/org/eclipse/core/tests/internal/databinding/UpdateValidationObservableValueTest.java >diff -N src/org/eclipse/core/tests/internal/databinding/UpdateValidationObservableValueTest.java >--- /dev/null 1 Jan 1970 00:00:00 -0000 >+++ src/org/eclipse/core/tests/internal/databinding/UpdateValidationObservableValueTest.java 1 Jan 1970 00:00:00 -0000 >@@ -0,0 +1,134 @@ >+/******************************************************************************* >+ * Copyright (c) 2007 Ovidio Mallo 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: >+ * Ovidio Mallo - initial API and implementation (bug 233191) >+ ******************************************************************************/ >+ >+package org.eclipse.core.tests.internal.databinding; >+ >+import junit.framework.Test; >+import junit.framework.TestSuite; >+ >+import org.eclipse.core.databinding.observable.IObservable; >+import org.eclipse.core.databinding.observable.IStaleListener; >+import org.eclipse.core.databinding.observable.ObservableTracker; >+import org.eclipse.core.databinding.observable.Realm; >+import org.eclipse.core.databinding.observable.StaleEvent; >+import org.eclipse.core.databinding.observable.value.IObservableValue; >+import org.eclipse.core.databinding.observable.value.IValueChangeListener; >+import org.eclipse.core.databinding.observable.value.ValueChangeEvent; >+import org.eclipse.core.databinding.validation.ValidationStatus; >+import org.eclipse.core.internal.databinding.UpdateValidationObservableValue; >+import org.eclipse.core.runtime.IStatus; >+import org.eclipse.jface.databinding.conformance.MutableObservableValueContractTest; >+import org.eclipse.jface.databinding.conformance.delegate.AbstractObservableValueContractDelegate; >+import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase; >+ >+/** >+ * @since 1.2 >+ */ >+public class UpdateValidationObservableValueTest extends >+ AbstractDefaultRealmTestCase { >+ >+ public static Test suite() { >+ TestSuite suite = new TestSuite( >+ UpdateValidationObservableValueTest.class.getName()); >+ suite.addTestSuite(UpdateValidationObservableValueTest.class); >+ suite.addTest(MutableObservableValueContractTest.suite(new Delegate())); >+ return suite; >+ } >+ >+ public void testStaleness() { >+ UpdateValidationObservableValueStub observable = new UpdateValidationObservableValueStub( >+ Realm.getDefault()); >+ >+ ValueChangeCounter valueChangeCounter = new ValueChangeCounter(); >+ observable.addValueChangeListener(valueChangeCounter); >+ >+ StaleCounter staleCounter = new StaleCounter(); >+ observable.addStaleListener(staleCounter); >+ >+ assertEquals(0, valueChangeCounter.count); >+ assertEquals(0, staleCounter.count); >+ assertFalse(observable.isStale()); >+ >+ observable.setStale(true); >+ assertEquals(0, valueChangeCounter.count); >+ assertEquals(1, staleCounter.count); >+ assertTrue(observable.isStale()); >+ >+ observable.setStale(false); >+ assertEquals(1, valueChangeCounter.count); >+ assertEquals(1, staleCounter.count); >+ assertFalse(observable.isStale()); >+ } >+ >+ /* package */static class Delegate extends >+ AbstractObservableValueContractDelegate { >+ >+ public IObservableValue createObservableValue(Realm realm) { >+ return new UpdateValidationObservableValueStub(realm); >+ } >+ >+ public void change(IObservable observable) { >+ IObservableValue observableValue = (IObservableValue) observable; >+ observableValue.setValue(createValue(observableValue)); >+ } >+ >+ public void setStale(IObservable observable, boolean stale) { >+ ((UpdateValidationObservableValueStub) observable).setStale(stale); >+ } >+ >+ public Object getValueType(IObservableValue observable) { >+ return IStatus.class; >+ } >+ >+ public Object createValue(IObservableValue observable) { >+ IStatus status = (IStatus) observable.getValue(); >+ return ValidationStatus.error(status.getMessage() + "a"); >+ } >+ } >+ >+ private static class UpdateValidationObservableValueStub extends >+ UpdateValidationObservableValue { >+ >+ private boolean stale = false; >+ >+ public UpdateValidationObservableValueStub(Realm realm) { >+ super(realm); >+ } >+ >+ public boolean isStale() { >+ ObservableTracker.getterCalled(this); >+ return stale; >+ } >+ >+ public void setStale(boolean stale) { >+ this.stale = stale; >+ updateStaleness(); >+ } >+ } >+ >+ private static class ValueChangeCounter implements IValueChangeListener { >+ >+ int count; >+ >+ public void handleValueChange(ValueChangeEvent event) { >+ count++; >+ } >+ } >+ >+ private static class StaleCounter implements IStaleListener { >+ >+ int count; >+ >+ public void handleStale(StaleEvent event) { >+ count++; >+ } >+ } >+} >Index: src/org/eclipse/core/tests/internal/databinding/UpdateExecutorTest.java >=================================================================== >RCS file: src/org/eclipse/core/tests/internal/databinding/UpdateExecutorTest.java >diff -N src/org/eclipse/core/tests/internal/databinding/UpdateExecutorTest.java >--- /dev/null 1 Jan 1970 00:00:00 -0000 >+++ src/org/eclipse/core/tests/internal/databinding/UpdateExecutorTest.java 1 Jan 1970 00:00:00 -0000 >@@ -0,0 +1,250 @@ >+/******************************************************************************* >+ * Copyright (c) 2007 Ovidio Mallo 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: >+ * Ovidio Mallo - initial API and implementation (bug 233191) >+ ******************************************************************************/ >+ >+package org.eclipse.core.tests.internal.databinding; >+ >+import java.util.ArrayList; >+ >+import junit.framework.TestCase; >+ >+import org.eclipse.core.internal.databinding.UpdateExecutor; >+import org.eclipse.core.internal.databinding.UpdateRunnable; >+ >+/** >+ * @since 1.2 >+ */ >+public class UpdateExecutorTest extends TestCase { >+ >+ public void testSyncExecutesInSameThread() throws Throwable { >+ UpdateExecutor executor = new UpdateExecutor(false, null); >+ >+ final Thread mainThread = Thread.currentThread(); >+ TestUpdateRunnable update = new TestUpdateRunnable() { >+ public void doRun() { >+ assertSame(mainThread, Thread.currentThread()); >+ notifyDone(); >+ } >+ }; >+ >+ executor.execute(update, false); >+ >+ awaitPendingUpdates(executor); >+ assertNotFailed(update); >+ } >+ >+ public void testAsyncExecutesInSeparateThread() throws Throwable { >+ UpdateExecutor executor = new UpdateExecutor(true, null); >+ >+ final Thread mainThread = Thread.currentThread(); >+ TestUpdateRunnable update = new TestUpdateRunnable() { >+ public void doRun() { >+ assertNotSame(mainThread, Thread.currentThread()); >+ notifyDone(); >+ } >+ }; >+ >+ executor.execute(update, false); >+ >+ awaitPendingUpdates(executor); >+ assertNotFailed(update); >+ } >+ >+ public void testHasPendingUpdates() throws Throwable { >+ // Test for both, synchronous and asynchronous executions. >+ testHasPendingUpdates(false); >+ testHasPendingUpdates(true); >+ } >+ >+ private static void testHasPendingUpdates(boolean isAsync) throws Throwable { >+ final UpdateExecutor executor = new UpdateExecutor(isAsync, null); >+ >+ TestUpdateRunnable update = new TestUpdateRunnable() { >+ public void doRun() { >+ assertTrue(executor.hasPendingUpdates()); >+ notifyDone(); >+ assertFalse(executor.hasPendingUpdates()); >+ } >+ }; >+ >+ assertFalse(executor.hasPendingUpdates()); >+ executor.execute(update, false); >+ >+ awaitPendingUpdates(executor); >+ assertNotFailed(update); >+ } >+ >+ public void testSchedulingEventCallbackInvoked() throws Throwable { >+ // Test for both, synchronous and asynchronous executions. >+ testSchedulingEventCallbackInvoked(false); >+ testSchedulingEventCallbackInvoked(true); >+ } >+ >+ private static void testSchedulingEventCallbackInvoked(boolean isAsync) >+ throws Throwable { >+ final SchedulingEventCallbackCounter callbackCounter = new SchedulingEventCallbackCounter(); >+ UpdateExecutor executor = new UpdateExecutor(isAsync, callbackCounter); >+ >+ assertEquals(0, callbackCounter.count); >+ >+ TestUpdateRunnable update = new TestUpdateRunnable() { >+ public void doRun() { >+ // At this point, we must already have received the scheduling >+ // event of having started this update. >+ assertEquals(1, callbackCounter.count); >+ notifyDone(); >+ // At this point, we must have received the scheduling event >+ // about the update having terminated. >+ assertEquals(2, callbackCounter.count); >+ } >+ }; >+ >+ executor.execute(update, false); >+ >+ awaitPendingUpdates(executor); >+ assertNotFailed(update); >+ } >+ >+ public void testAsyncExecuteInOrder() throws Throwable { >+ UpdateExecutor executor = new UpdateExecutor(true, null); >+ >+ final ArrayList updateList = new ArrayList(); >+ for (int i = 0; i < 10; i++) { >+ updateList.add(new TestUpdateRunnable() { >+ public void doRun() { >+ assertSame(updateList.remove(0), this); >+ notifyDone(); >+ } >+ }); >+ } >+ >+ // The original list is modified by the updates, so we make a copy. >+ ArrayList updateListCopy = new ArrayList(updateList); >+ for (int i = 0; i < updateListCopy.size(); i++) { >+ UpdateRunnable update = (UpdateRunnable) updateListCopy.get(i); >+ executor.execute(update, false); >+ } >+ >+ awaitPendingUpdates(executor); >+ for (int i = 0; i < updateListCopy.size(); i++) { >+ assertNotFailed((TestUpdateRunnable) updateListCopy.get(i)); >+ } >+ } >+ >+ public void testCancelPendingUpdates() throws Throwable { >+ // Test for both, canceling and not canceling pending updates. >+ testCancelPendingUpdates(false); >+ testCancelPendingUpdates(true); >+ } >+ >+ private void testCancelPendingUpdates(boolean doCancel) throws Throwable { >+ UpdateExecutor executor = new UpdateExecutor(true, null); >+ >+ // In order to reliably ensure that update1 has not already terminated >+ // when we request its canceling, we start the two updates while holding >+ // a lock which is also tried to be acquired by update1. >+ synchronized (this) { >+ TestUpdateRunnable update1 = new TestUpdateRunnable() { >+ public void doRun() { >+ synchronized (UpdateExecutorTest.this) { >+ // just try to acquire the lock >+ } >+ notifyDone(); >+ } >+ }; >+ >+ executor.execute(update1, false); >+ >+ assertFalse(update1.isCanceled()); >+ executor.execute(new TestUpdateRunnable(), doCancel); >+ assertEquals(doCancel, update1.isCanceled()); >+ } >+ } >+ >+ public void testTerminateCancelsPendingUpdates() throws Throwable { >+ UpdateExecutor executor = new UpdateExecutor(true, null); >+ >+ // In order to reliably ensure that the update has not already >+ // terminated when we request its canceling, we start the two updates >+ // while holding a lock which is also tried to be acquired by the >+ // update. >+ synchronized (this) { >+ TestUpdateRunnable update = new TestUpdateRunnable() { >+ public void doRun() { >+ synchronized (UpdateExecutorTest.this) { >+ // just try to acquire the lock >+ } >+ notifyDone(); >+ } >+ }; >+ >+ executor.execute(update, false); >+ >+ assertFalse(update.isCanceled()); >+ executor.terminate(); >+ assertTrue(update.isCanceled()); >+ } >+ } >+ >+ private static void awaitPendingUpdates(UpdateExecutor executor) { >+ while (executor.hasPendingUpdates()) { >+ try { >+ // avoid continuous polling >+ Thread.sleep(20); >+ } catch (InterruptedException e) { >+ // just go ahead >+ } >+ } >+ } >+ >+ private static void assertNotFailed(TestUpdateRunnable update) >+ throws Throwable { >+ if (update.throwable != null) { >+ throw update.throwable; >+ } >+ } >+ >+ /** >+ * Simple extension of an UpdateRunnable which catches any Throwable thrown >+ * during the execution of the {@link #run()} method and stores it in a >+ * class field for later querying. We do this to be able to use JUnit >+ * assertions from within the run method since when running an >+ * UpdateRunnable asynchronously, the UpdateExecutor class executes it from >+ * within a Job which would otherwise swallow all Throwables. >+ * >+ * TODO Maybe, this could be done more elegantly? >+ */ >+ private static class TestUpdateRunnable extends UpdateRunnable { >+ >+ public Throwable throwable = null; >+ >+ public void run() { >+ try { >+ doRun(); >+ } catch (Throwable t) { >+ throwable = t; >+ notifyDone(); >+ } >+ } >+ >+ public void doRun() { >+ // do nothing >+ } >+ } >+ >+ private static class SchedulingEventCallbackCounter implements Runnable { >+ >+ public int count = 0; >+ >+ public void run() { >+ count++; >+ } >+ } >+} >Index: src/org/eclipse/jface/tests/databinding/wizard/WizardPageSupportTest.java >=================================================================== >RCS file: src/org/eclipse/jface/tests/databinding/wizard/WizardPageSupportTest.java >diff -N src/org/eclipse/jface/tests/databinding/wizard/WizardPageSupportTest.java >--- /dev/null 1 Jan 1970 00:00:00 -0000 >+++ src/org/eclipse/jface/tests/databinding/wizard/WizardPageSupportTest.java 1 Jan 1970 00:00:00 -0000 >@@ -0,0 +1,138 @@ >+/******************************************************************************* >+ * Copyright (c) 2008 Ovidio Mallo 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: >+ * Ovidio Mallo - initial API and implementation (bug 233191) >+ ******************************************************************************/ >+ >+package org.eclipse.jface.tests.databinding.wizard; >+ >+import org.eclipse.core.databinding.DataBindingContext; >+import org.eclipse.core.databinding.ValidationStatusProvider; >+import org.eclipse.core.databinding.observable.Diffs; >+import org.eclipse.core.databinding.observable.Observables; >+import org.eclipse.core.databinding.observable.Realm; >+import org.eclipse.core.databinding.observable.list.IObservableList; >+import org.eclipse.core.databinding.observable.value.AbstractObservableValue; >+import org.eclipse.core.databinding.observable.value.IObservableValue; >+import org.eclipse.core.databinding.validation.ValidationStatus; >+import org.eclipse.core.internal.commands.util.Util; >+import org.eclipse.core.runtime.IStatus; >+import org.eclipse.jface.databinding.wizard.WizardPageSupport; >+import org.eclipse.jface.tests.databinding.AbstractSWTTestCase; >+import org.eclipse.jface.wizard.IWizardPage; >+import org.eclipse.jface.wizard.Wizard; >+import org.eclipse.jface.wizard.WizardDialog; >+import org.eclipse.jface.wizard.WizardPage; >+import org.eclipse.swt.widgets.Composite; >+ >+/** >+ * @since 1.2 >+ */ >+public class WizardPageSupportTest extends AbstractSWTTestCase { >+ >+ public void testPageCompleteOnValidationStaleness() { >+ IWizardPage page = new WizardPage("Page") { >+ public void createControl(Composite parent) { >+ setControl(parent); >+ >+ ValidationObservable validation = new ValidationObservable(); >+ >+ DataBindingContext dbc = new DataBindingContext(); >+ dbc.addValidationStatusProvider(new ValidationProvider( >+ validation)); >+ >+ WizardPageSupport.create(this, dbc); >+ >+ assertTrue(isPageComplete()); >+ >+ validation.setStale(true); >+ assertFalse(isPageComplete()); >+ >+ validation.setStale(false); >+ assertTrue(isPageComplete()); >+ } >+ }; >+ >+ loadWizardPage(page); >+ } >+ >+ private void loadWizardPage(IWizardPage page) { >+ Wizard wizard = new Wizard() { >+ public boolean performFinish() { >+ return true; >+ } >+ }; >+ wizard.addPage(page); >+ >+ WizardDialog dialog = new WizardDialog(getShell(), wizard); >+ dialog.create(); >+ } >+ >+ private static class ValidationObservable extends AbstractObservableValue { >+ >+ private Object value = ValidationStatus.ok(); >+ >+ private boolean stale = false; >+ >+ public ValidationObservable() { >+ super(Realm.getDefault()); >+ } >+ >+ protected Object doGetValue() { >+ return value; >+ } >+ >+ protected void doSetValue(Object value) { >+ Object oldValue = this.value; >+ this.value = value; >+ if (!Util.equals(oldValue, value)) { >+ fireValueChange(Diffs.createValueDiff(oldValue, value)); >+ } >+ } >+ >+ public boolean isStale() { >+ return stale; >+ } >+ >+ public void setStale(boolean stale) { >+ if (this.stale != stale) { >+ this.stale = stale; >+ if (stale) { >+ fireStale(); >+ } else { >+ fireValueChange(Diffs.createValueDiff(value, value)); >+ } >+ } >+ } >+ >+ public Object getValueType() { >+ return IStatus.class; >+ } >+ } >+ >+ private static class ValidationProvider extends ValidationStatusProvider { >+ >+ private final IObservableValue validation; >+ >+ public ValidationProvider(IObservableValue validation) { >+ this.validation = validation; >+ } >+ >+ public IObservableValue getValidationStatus() { >+ return validation; >+ } >+ >+ public IObservableList getTargets() { >+ return Observables.emptyObservableList(); >+ } >+ >+ public IObservableList getModels() { >+ return Observables.emptyObservableList(); >+ } >+ } >+} >#P org.eclipse.jface.databinding >Index: src/org/eclipse/jface/databinding/wizard/WizardPageSupport.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.jface.databinding/src/org/eclipse/jface/databinding/wizard/WizardPageSupport.java,v >retrieving revision 1.5 >diff -u -r1.5 WizardPageSupport.java >--- src/org/eclipse/jface/databinding/wizard/WizardPageSupport.java 15 May 2008 23:19:43 -0000 1.5 >+++ src/org/eclipse/jface/databinding/wizard/WizardPageSupport.java 16 Jun 2008 20:08:54 -0000 >@@ -9,7 +9,8 @@ > * IBM Corporation - initial API and implementation > * Boris Bokowski - bug 218269 > * Matthew Hall - bug 218269 >- * Ashley Cambrell - bug 199179 >+ * Ashley Cambrell - bug 199179 >+ * Ovidio Mallo - bug 233191 > *******************************************************************************/ > package org.eclipse.jface.databinding.wizard; > >@@ -21,6 +22,8 @@ > 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.IStaleListener; >+import org.eclipse.core.databinding.observable.StaleEvent; > import org.eclipse.core.databinding.observable.list.IListChangeListener; > import org.eclipse.core.databinding.observable.list.IObservableList; > import org.eclipse.core.databinding.observable.list.ListChangeEvent; >@@ -41,6 +44,17 @@ > * given wizard page, updating the wizard page's completion state and its error > * message accordingly. > * >+ * <p> >+ * The completion state of the wizard page will only be set to <code>true</code> >+ * if <i>all</i> of the following conditions are met: >+ * <ul> >+ * <li>The validation result from the data binding context has none of the >+ * severities {@link IStatus#ERROR} and {@link IStatus#CANCEL}.</li> >+ * <li>None of the validation status observables of the data binding context is >+ * stale.</li> >+ * </ul> >+ * </p> >+ * > * @noextend This class is not intended to be subclassed by clients. > * > * @since 1.1 >@@ -137,6 +151,11 @@ > handleStatusChanged(); > } > }); >+ aggregateStatus.addStaleListener(new IStaleListener() { >+ public void handleStale(StaleEvent staleEvent) { >+ handleStatusChanged(); >+ } >+ }); > currentStatus = (IStatus) aggregateStatus.getValue(); > handleStatusChanged(); > dbc.getValidationStatusProviders().addListChangeListener( >@@ -188,7 +207,8 @@ > } else if (currentStatus != null > && currentStatus.getSeverity() != IStatus.OK) { > int severity = currentStatus.getSeverity(); >- wizardPage.setPageComplete((severity & IStatus.CANCEL) != 0); >+ wizardPage.setPageComplete(((severity & IStatus.CANCEL) == 0) >+ && !aggregateStatus.isStale()); > int type; > switch (severity) { > case IStatus.OK: >@@ -213,7 +233,7 @@ > wizardPage.setErrorMessage(null); > wizardPage.setMessage(currentStatus.getMessage(), type); > } else { >- wizardPage.setPageComplete(true); >+ wizardPage.setPageComplete(!aggregateStatus.isStale()); > wizardPage.setMessage(null); > wizardPage.setErrorMessage(null); > } >#P org.eclipse.core.databinding >Index: src/org/eclipse/core/databinding/Binding.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/databinding/Binding.java,v >retrieving revision 1.13 >diff -u -r1.13 Binding.java >--- src/org/eclipse/core/databinding/Binding.java 9 May 2008 14:13:00 -0000 1.13 >+++ src/org/eclipse/core/databinding/Binding.java 16 Jun 2008 20:08:55 -0000 >@@ -10,6 +10,7 @@ > * Brad Reynolds - bug 159768 > * Boris Bokowski - bug 218269 > * Matthew Hall - bug 218269 >+ * Ovidio Mallo - bug 233191 > *******************************************************************************/ > > package org.eclipse.core.databinding; >@@ -110,7 +111,26 @@ > * by the time this call returns. > */ > public abstract void validateModelToTarget(); >- >+ >+ /** >+ * Returns whether the updates performed by this binding are executed >+ * asynchronously. >+ * >+ * <p> >+ * By default, this method returns <code>false</code>. Subclasses will >+ * typically want to overwrite this method to return whether <i>any</i> of >+ * the binding's update strategies is intended to be run asynchronously. >+ * However, subclasses may always decide not to execute the updates >+ * asynchronously. >+ * </p> >+ * >+ * @return whether the updates performed by this binding are executed >+ * asynchronously. >+ */ >+ public boolean isAsync() { >+ return false; >+ } >+ > /** > * Disposes of this Binding. Subclasses may extend, but must call super.dispose(). > */ >Index: src/org/eclipse/core/databinding/UpdateListStrategy.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/databinding/UpdateListStrategy.java,v >retrieving revision 1.7 >diff -u -r1.7 UpdateListStrategy.java >--- src/org/eclipse/core/databinding/UpdateListStrategy.java 9 May 2008 14:13:00 -0000 1.7 >+++ src/org/eclipse/core/databinding/UpdateListStrategy.java 16 Jun 2008 20:08:55 -0000 >@@ -229,4 +229,37 @@ > } > return Status.OK_STATUS; > } >+ >+ /** >+ * Returns whether the list update defined by this strategy is intended to >+ * be executed asynchronously. >+ * >+ * <p> >+ * By default, this method returns <code>true</code> if and only if the >+ * converter associated to this strategy is intended to be executed >+ * asynchronously. Clients may extend without having to call the super >+ * implementation. >+ * </p> >+ * >+ * <p> >+ * Note that even if this method returns <code>true</code>, there is no >+ * guarantee as of whether the list update will indeed by executed >+ * asynchronously or not. >+ * </p> >+ * >+ * @return whether the list update defined by this strategy is intended to >+ * be executed asynchronously. >+ * >+ * @see IConverter#isAsync() >+ */ >+ public boolean isAsync() { >+ return isAsyncConverter(converter); >+ } >+ >+ private boolean isAsyncConverter(IConverter converter) { >+ if (converter != null) { >+ return converter.isAsync(); >+ } >+ return false; >+ } > } >Index: src/org/eclipse/core/databinding/UpdateStrategy.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/databinding/UpdateStrategy.java,v >retrieving revision 1.14 >diff -u -r1.14 UpdateStrategy.java >--- src/org/eclipse/core/databinding/UpdateStrategy.java 9 May 2008 14:13:00 -0000 1.14 >+++ src/org/eclipse/core/databinding/UpdateStrategy.java 16 Jun 2008 20:08:55 -0000 >@@ -68,6 +68,21 @@ > > private static Map converterMap; > >+ /** >+ * Returns whether the update defined by this strategy is intended to be >+ * executed asynchronously. >+ * >+ * <p> >+ * By default, this method returns <code>false</code>. >+ * </p> >+ * >+ * @return whether the update defined by this strategy is intended to be >+ * executed asynchronously. >+ */ >+ public boolean isAsync() { >+ return false; >+ } >+ > private static Class autoboxed(Class clazz) { > if (clazz == Float.TYPE) > return Float.class; >@@ -705,6 +720,10 @@ > public Object getToType() { > return toType; > } >+ >+ public boolean isAsync() { >+ return false; >+ } > } > > } >\ No newline at end of file >Index: src/org/eclipse/core/databinding/UpdateValueStrategy.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/databinding/UpdateValueStrategy.java,v >retrieving revision 1.15 >diff -u -r1.15 UpdateValueStrategy.java >--- src/org/eclipse/core/databinding/UpdateValueStrategy.java 9 May 2008 14:13:00 -0000 1.15 >+++ src/org/eclipse/core/databinding/UpdateValueStrategy.java 16 Jun 2008 20:08:55 -0000 >@@ -9,6 +9,7 @@ > * IBM Corporation - initial API and implementation > * Matt Carter - Character support completed (bug 197679) > * Tom Schindl<tom.schindl@bestsolution.at> - bugfix for 217940 >+ * Ovidio Mallo - bug 233191 > *******************************************************************************/ > > package org.eclipse.core.databinding; >@@ -19,6 +20,7 @@ > import org.eclipse.core.databinding.conversion.IConverter; > import org.eclipse.core.databinding.observable.value.IObservableValue; > import org.eclipse.core.databinding.validation.IValidator; >+import org.eclipse.core.databinding.validation.IValidator2; > import org.eclipse.core.databinding.validation.ValidationStatus; > import org.eclipse.core.internal.databinding.BindingMessages; > import org.eclipse.core.internal.databinding.Pair; >@@ -490,6 +492,50 @@ > return Status.OK_STATUS; > } > >+ /** >+ * Returns whether the value update defined by this strategy is intended to >+ * be executed asynchronously. >+ * >+ * <p> >+ * By default, this method returns <code>true</code> if and only if >+ * <i>any</i> of the validators or the converter associated to this strategy >+ * is intended to be executed asynchronously. Clients may extend without >+ * having to call the super implementation. >+ * </p> >+ * >+ * <p> >+ * Note that even if this method returns <code>true</code>, there is no >+ * guarantee as of whether the value update will indeed by executed >+ * asynchronously or not. >+ * </p> >+ * >+ * @return whether the value update defined by this strategy is intended to >+ * be executed asynchronously. >+ * >+ * @see IValidator2#isAsync() >+ * @see IConverter#isAsync() >+ */ >+ public boolean isAsync() { >+ return isAsyncValidator(afterGetValidator) >+ || isAsyncConverter(converter) >+ || isAsyncValidator(afterConvertValidator) >+ || isAsyncValidator(beforeSetValidator); >+ } >+ >+ private boolean isAsyncValidator(IValidator validator) { >+ if (validator instanceof IValidator2) { >+ return ((IValidator2) validator).isAsync(); >+ } >+ return false; >+ } >+ >+ private boolean isAsyncConverter(IConverter converter) { >+ if (converter != null) { >+ return converter.isAsync(); >+ } >+ return false; >+ } >+ > private static class ValidatorRegistry { > > private HashMap validators = new HashMap(); >Index: src/org/eclipse/core/databinding/ListBinding.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/databinding/ListBinding.java,v >retrieving revision 1.4 >diff -u -r1.4 ListBinding.java >--- src/org/eclipse/core/databinding/ListBinding.java 5 Apr 2007 01:50:07 -0000 1.4 >+++ src/org/eclipse/core/databinding/ListBinding.java 16 Jun 2008 20:08:55 -0000 >@@ -14,17 +14,19 @@ > import java.util.Collections; > > import org.eclipse.core.databinding.observable.Diffs; >+import org.eclipse.core.databinding.observable.ObservableTracker; > 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.ListDiff; > import org.eclipse.core.databinding.observable.list.ListDiffEntry; > import org.eclipse.core.databinding.observable.value.IObservableValue; >-import org.eclipse.core.databinding.observable.value.WritableValue; > import org.eclipse.core.internal.databinding.BindingStatus; >+import org.eclipse.core.internal.databinding.UpdateExecutor; >+import org.eclipse.core.internal.databinding.UpdateRunnable; >+import org.eclipse.core.internal.databinding.UpdateValidationObservableValue; > import org.eclipse.core.runtime.IStatus; > import org.eclipse.core.runtime.MultiStatus; >-import org.eclipse.core.runtime.Status; > > /** > * @since 1.0 >@@ -34,9 +36,10 @@ > > private UpdateListStrategy targetToModel; > private UpdateListStrategy modelToTarget; >- private IObservableValue validationStatusObservable; >+ private UpdateValidationObservableValue validationStatusObservable; > private boolean updatingTarget; > private boolean updatingModel; >+ private UpdateExecutor updateExecutor; > > private IListChangeListener targetChangeListener = new IListChangeListener() { > public void handleListChange(ListChangeEvent event) { >@@ -86,8 +89,30 @@ > } > > protected void preInit() { >- validationStatusObservable = new WritableValue(context >- .getValidationRealm(), Status.OK_STATUS, IStatus.class); >+ updateExecutor = new UpdateExecutor(isAsync(), new Runnable() { >+ public void run() { >+ // If we are in asynchronous mode, we must update the staleness >+ // of the validation observable upon every scheduling event. >+ if (isAsync()) { >+ validationStatusObservable.getRealm().exec(new Runnable() { >+ public void run() { >+ validationStatusObservable.updateStaleness(); >+ } >+ }); >+ } >+ } >+ }); >+ >+ validationStatusObservable = new UpdateValidationObservableValue( >+ context.getValidationRealm()) { >+ public boolean isStale() { >+ ObservableTracker.getterCalled(this); >+ // If not in asynchronous mode, we never set the validation >+ // observable to be stale. >+ return ListBinding.this.isAsync() >+ && updateExecutor.hasPendingUpdates(); >+ } >+ }; > } > > protected void postInit() { >@@ -131,6 +156,10 @@ > // nothing for now > } > >+ public boolean isAsync() { >+ return targetToModel.isAsync() || modelToTarget.isAsync(); >+ } >+ > /* > * This method may be moved to UpdateListStrategy in the future if clients > * need more control over how the two lists are kept in sync. >@@ -140,10 +169,33 @@ > final UpdateListStrategy updateListStrategy, > final boolean explicit, final boolean clearDestination) { > final int policy = updateListStrategy.getUpdatePolicy(); >- if (policy != UpdateListStrategy.POLICY_NEVER) { >- if (policy != UpdateListStrategy.POLICY_ON_REQUEST || explicit) { >+ if (policy == UpdateListStrategy.POLICY_NEVER) >+ return; >+ if (policy == UpdateListStrategy.POLICY_ON_REQUEST && !explicit) >+ return; >+ >+ UpdateRunnable update = new UpdateRunnable() { >+ public void run() { >+ final ListDiffEntry[] diffEntries = diff.getDifferences(); >+ for (int i = 0; i < diffEntries.length; i++) { >+ ListDiffEntry entry = diffEntries[i]; >+ if (entry.isAddition()) { >+ diffEntries[i] = Diffs.createListDiffEntry(entry >+ .getPosition(), entry.isAddition(), >+ updateListStrategy.convert(entry.getElement())); >+ } >+ } >+ > destination.getRealm().exec(new Runnable() { > public void run() { >+ // If the update has been canceled before writing to the >+ // destination observable, we do not set the validation >+ // status, so we return outside the below try block. >+ if (isCanceled()) { >+ notifyDone(); >+ return; >+ } >+ > if (destination == getTarget()) { > updatingTarget = true; > } else { >@@ -155,29 +207,27 @@ > if (clearDestination) { > destination.clear(); > } >- ListDiffEntry[] diffEntries = diff.getDifferences(); > for (int i = 0; i < diffEntries.length; i++) { > ListDiffEntry listDiffEntry = diffEntries[i]; > if (listDiffEntry.isAddition()) { > IStatus setterStatus = updateListStrategy >- .doAdd( >- destination, >- updateListStrategy >- .convert(listDiffEntry >- .getElement()), >+ .doAdd(destination, listDiffEntry >+ .getElement(), > listDiffEntry.getPosition()); > > mergeStatus(multiStatus, setterStatus); > // TODO - at this point, the two lists >- // will be out of sync if an error occurred... >+ // will be out of sync if an error >+ // occurred... > } else { > IStatus setterStatus = updateListStrategy > .doRemove(destination, > listDiffEntry.getPosition()); >- >+ > mergeStatus(multiStatus, setterStatus); > // TODO - at this point, the two lists >- // will be out of sync if an error occurred... >+ // will be out of sync if an error >+ // occurred... > } > } > } finally { >@@ -188,11 +238,15 @@ > } else { > updatingModel = false; > } >+ >+ notifyDone(); > } > } > }); > } >- } >+ }; >+ >+ updateExecutor.execute(update, false); > } > > /** >@@ -209,6 +263,8 @@ > } > > public void dispose() { >+ updateExecutor.terminate(); >+ > if (targetChangeListener != null) { > ((IObservableList)getTarget()).removeListChangeListener(targetChangeListener); > targetChangeListener = null; >Index: src/org/eclipse/core/databinding/ValueBinding.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/databinding/ValueBinding.java,v >retrieving revision 1.8 >diff -u -r1.8 ValueBinding.java >--- src/org/eclipse/core/databinding/ValueBinding.java 24 Mar 2008 19:13:39 -0000 1.8 >+++ src/org/eclipse/core/databinding/ValueBinding.java 16 Jun 2008 20:08:55 -0000 >@@ -8,16 +8,20 @@ > * Contributors: > * IBM Corporation - initial API and implementation > * Matthew Hall - bug 220700 >+ * Ovidio Mallo - bug 233191 > *******************************************************************************/ > > package org.eclipse.core.databinding; > >+import org.eclipse.core.databinding.observable.ObservableTracker; > import org.eclipse.core.databinding.observable.value.IObservableValue; > import org.eclipse.core.databinding.observable.value.IValueChangeListener; > import org.eclipse.core.databinding.observable.value.ValueChangeEvent; >-import org.eclipse.core.databinding.observable.value.WritableValue; > import org.eclipse.core.databinding.util.Policy; > import org.eclipse.core.internal.databinding.BindingStatus; >+import org.eclipse.core.internal.databinding.UpdateExecutor; >+import org.eclipse.core.internal.databinding.UpdateRunnable; >+import org.eclipse.core.internal.databinding.UpdateValidationObservableValue; > import org.eclipse.core.internal.databinding.Util; > import org.eclipse.core.runtime.IStatus; > import org.eclipse.core.runtime.MultiStatus; >@@ -30,9 +34,10 @@ > class ValueBinding extends Binding { > private final UpdateValueStrategy targetToModel; > private final UpdateValueStrategy modelToTarget; >- private WritableValue validationStatusObservable; >+ private UpdateValidationObservableValue validationStatusObservable; > private IObservableValue target; > private IObservableValue model; >+ private UpdateExecutor updateExecutor; > > private boolean updatingTarget; > private boolean updatingModel; >@@ -78,8 +83,30 @@ > } > > protected void preInit() { >- validationStatusObservable = new WritableValue(context >- .getValidationRealm(), Status.OK_STATUS, IStatus.class); >+ updateExecutor = new UpdateExecutor(isAsync(), new Runnable() { >+ public void run() { >+ // If we are in asynchronous mode, we must update the staleness >+ // of the validation observable upon every scheduling event. >+ if (isAsync()) { >+ validationStatusObservable.getRealm().exec(new Runnable() { >+ public void run() { >+ validationStatusObservable.updateStaleness(); >+ } >+ }); >+ } >+ } >+ }); >+ >+ validationStatusObservable = new UpdateValidationObservableValue( >+ context.getValidationRealm()) { >+ public boolean isStale() { >+ ObservableTracker.getterCalled(this); >+ // If not in asynchronous mode, we never set the validation >+ // observable to be stale. >+ return ValueBinding.this.isAsync() >+ && updateExecutor.hasPendingUpdates(); >+ } >+ }; > } > > protected void postInit() { >@@ -104,6 +131,35 @@ > } > > /** >+ * Returns whether the updates performed by this binding are executed >+ * asynchronously. >+ * >+ * <p> >+ * This method returns <code>true</code> if and only if <i>any</i> of the >+ * binding's update strategies is intended to be run asynchronously. >+ * Thereby, the individual updates of this binding are always guaranteed to >+ * be executed in the correct order, thus ensuring that the target and model >+ * values will never get out of sync due to the asynchronous nature of the >+ * updates. >+ * </p> >+ * >+ * <p> >+ * In case the updates are executed asynchronously, the staleness state of >+ * the validation status observable can be used to determine whether there >+ * are any pending updates. This allows for awaiting the termination of >+ * those updates, if desired, by tracking the observable's staleness state. >+ * </p> >+ * >+ * @return whether the updates performed by this binding are executed >+ * asynchronously. >+ * >+ * @see UpdateValueStrategy#isAsync() >+ */ >+ public boolean isAsync() { >+ return targetToModel.isAsync() || modelToTarget.isAsync(); >+ } >+ >+ /** > * Incorporates the provided <code>newStats</code> into the > * <code>multieStatus</code>. > * >@@ -128,7 +184,6 @@ > final IObservableValue destination, > final UpdateValueStrategy updateValueStrategy, > final boolean explicit, final boolean validateOnly) { >- > final int policy = updateValueStrategy.getUpdatePolicy(); > if (policy == UpdateValueStrategy.POLICY_NEVER) > return; >@@ -137,78 +192,99 @@ > > source.getRealm().exec(new Runnable() { > public void run() { >- boolean destinationRealmReached = false; >- final MultiStatus multiStatus = BindingStatus.ok(); >- try { >- // Get value >- Object value = source.getValue(); >- >- // Validate after get >- IStatus status = updateValueStrategy >- .validateAfterGet(value); >- if (!mergeStatus(multiStatus, status)) >- return; >- >- // Convert value >- final Object convertedValue = updateValueStrategy >- .convert(value); >- >- // Validate after convert >- status = updateValueStrategy >- .validateAfterConvert(convertedValue); >- if (!mergeStatus(multiStatus, status)) >- return; >- if (policy == UpdateValueStrategy.POLICY_CONVERT >- && !explicit) >- return; >- >- // Validate before set >- status = updateValueStrategy >- .validateBeforeSet(convertedValue); >- if (!mergeStatus(multiStatus, status)) >- return; >- if (validateOnly) >- return; >- >- // Set value >- destinationRealmReached = true; >- destination.getRealm().exec(new Runnable() { >- public void run() { >- if (destination == target) { >- updatingTarget = true; >- } else { >- updatingModel = true; >- } >- try { >- IStatus setterStatus = updateValueStrategy >- .doSet(destination, convertedValue); >- >- mergeStatus(multiStatus, setterStatus); >- } finally { >- if (destination == target) { >- updatingTarget = false; >- } else { >- updatingModel = false; >+ // Get value >+ final Object value = source.getValue(); >+ >+ UpdateRunnable update = new UpdateRunnable() { >+ public void run() { >+ boolean destinationRealmReached = false; >+ final MultiStatus multiStatus = BindingStatus.ok(); >+ try { >+ // Validate after get >+ IStatus status = updateValueStrategy >+ .validateAfterGet(value); >+ if (!mergeStatus(multiStatus, status)) >+ return; >+ >+ // Convert value >+ final Object convertedValue = updateValueStrategy >+ .convert(value); >+ >+ // Validate after convert >+ status = updateValueStrategy >+ .validateAfterConvert(convertedValue); >+ if (!mergeStatus(multiStatus, status)) >+ return; >+ if (policy == UpdateValueStrategy.POLICY_CONVERT >+ && !explicit) >+ return; >+ >+ // Validate before set >+ status = updateValueStrategy >+ .validateBeforeSet(convertedValue); >+ if (!mergeStatus(multiStatus, status)) >+ return; >+ if (validateOnly) >+ return; >+ >+ // Set value >+ destinationRealmReached = true; >+ destination.getRealm().exec(new Runnable() { >+ public void run() { >+ // If the update has been canceled before >+ // writing to the destination observable, we >+ // do not set the validation status, so we >+ // return outside the below try block. >+ if (isCanceled()) { >+ notifyDone(); >+ return; >+ } >+ >+ if (destination == target) { >+ updatingTarget = true; >+ } else { >+ updatingModel = true; >+ } >+ try { >+ IStatus setterStatus = updateValueStrategy >+ .doSet(destination, >+ convertedValue); >+ >+ mergeStatus(multiStatus, setterStatus); >+ } finally { >+ if (destination == target) { >+ updatingTarget = false; >+ } else { >+ updatingModel = false; >+ } >+ setValidationStatus(multiStatus); >+ notifyDone(); >+ } >+ } >+ }); >+ } catch (Exception ex) { >+ // This check is necessary as in 3.2.2 Status >+ // doesn't accept a null message (bug 177264). >+ String message = (ex.getMessage() != null) ? ex >+ .getMessage() : ""; //$NON-NLS-1$ >+ >+ mergeStatus(multiStatus, new Status(IStatus.ERROR, >+ Policy.JFACE_DATABINDING, IStatus.ERROR, >+ message, ex)); >+ } finally { >+ if (!destinationRealmReached) { >+ // Set the validation status unless the update >+ // has been canceled in the meanwhile. >+ if (!isCanceled()) { >+ setValidationStatus(multiStatus); > } >- setValidationStatus(multiStatus); >+ notifyDone(); > } > } >- }); >- } catch (Exception ex) { >- // This check is necessary as in 3.2.2 Status >- // doesn't accept a null message (bug 177264). >- String message = (ex.getMessage() != null) ? ex >- .getMessage() : ""; //$NON-NLS-1$ >- >- mergeStatus(multiStatus, new Status(IStatus.ERROR, >- Policy.JFACE_DATABINDING, IStatus.ERROR, message, >- ex)); >- } finally { >- if (!destinationRealmReached) { >- setValidationStatus(multiStatus); > } >+ }; > >- } >+ updateExecutor.execute(update, !explicit); > } > }); > } >@@ -230,6 +306,8 @@ > } > > public void dispose() { >+ updateExecutor.terminate(); >+ > if (targetChangeListener != null) { > target.removeValueChangeListener(targetChangeListener); > targetChangeListener = null; >Index: src/org/eclipse/core/internal/databinding/conversion/ObjectToStringConverter.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/conversion/ObjectToStringConverter.java,v >retrieving revision 1.1 >diff -u -r1.1 ObjectToStringConverter.java >--- src/org/eclipse/core/internal/databinding/conversion/ObjectToStringConverter.java 16 Mar 2007 21:19:39 -0000 1.1 >+++ src/org/eclipse/core/internal/databinding/conversion/ObjectToStringConverter.java 16 Jun 2008 20:08:56 -0000 >@@ -53,4 +53,7 @@ > return String.class; > } > >+ public boolean isAsync() { >+ return false; >+ } > } >Index: src/org/eclipse/core/internal/databinding/conversion/StringToBooleanPrimitiveConverter.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/conversion/StringToBooleanPrimitiveConverter.java,v >retrieving revision 1.3 >diff -u -r1.3 StringToBooleanPrimitiveConverter.java >--- src/org/eclipse/core/internal/databinding/conversion/StringToBooleanPrimitiveConverter.java 16 Apr 2008 02:51:52 -0000 1.3 >+++ src/org/eclipse/core/internal/databinding/conversion/StringToBooleanPrimitiveConverter.java 16 Jun 2008 20:08:56 -0000 >@@ -85,4 +85,7 @@ > return Boolean.TYPE; > } > >+ public boolean isAsync() { >+ return false; >+ } > } >Index: src/org/eclipse/core/internal/databinding/conversion/StringToDateConverter.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/conversion/StringToDateConverter.java,v >retrieving revision 1.1 >diff -u -r1.1 StringToDateConverter.java >--- src/org/eclipse/core/internal/databinding/conversion/StringToDateConverter.java 16 Mar 2007 21:19:38 -0000 1.1 >+++ src/org/eclipse/core/internal/databinding/conversion/StringToDateConverter.java 16 Jun 2008 20:08:56 -0000 >@@ -32,5 +32,9 @@ > > public Object getToType() { > return Date.class; >- } >+ } >+ >+ public boolean isAsync() { >+ return false; >+ } > } >Index: src/org/eclipse/core/internal/databinding/conversion/DateToStringConverter.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/conversion/DateToStringConverter.java,v >retrieving revision 1.1 >diff -u -r1.1 DateToStringConverter.java >--- src/org/eclipse/core/internal/databinding/conversion/DateToStringConverter.java 16 Mar 2007 21:19:38 -0000 1.1 >+++ src/org/eclipse/core/internal/databinding/conversion/DateToStringConverter.java 16 Jun 2008 20:08:56 -0000 >@@ -35,5 +35,9 @@ > > public Object getToType() { > return String.class; >- } >+ } >+ >+ public boolean isAsync() { >+ return false; >+ } > } >Index: src/org/eclipse/core/internal/databinding/conversion/IdentityConverter.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/conversion/IdentityConverter.java,v >retrieving revision 1.2 >diff -u -r1.2 IdentityConverter.java >--- src/org/eclipse/core/internal/databinding/conversion/IdentityConverter.java 7 Nov 2007 03:19:45 -0000 1.2 >+++ src/org/eclipse/core/internal/databinding/conversion/IdentityConverter.java 16 Jun 2008 20:08:56 -0000 >@@ -107,4 +107,7 @@ > return toType; > } > >+ public boolean isAsync() { >+ return false; >+ } > } >Index: src/org/eclipse/core/internal/databinding/conversion/StringToCharacterConverter.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/conversion/StringToCharacterConverter.java,v >retrieving revision 1.2 >diff -u -r1.2 StringToCharacterConverter.java >--- src/org/eclipse/core/internal/databinding/conversion/StringToCharacterConverter.java 7 Nov 2007 03:19:45 -0000 1.2 >+++ src/org/eclipse/core/internal/databinding/conversion/StringToCharacterConverter.java 16 Jun 2008 20:08:56 -0000 >@@ -70,6 +70,10 @@ > return primitiveTarget ? Character.TYPE : Character.class; > } > >+ public boolean isAsync() { >+ return false; >+ } >+ > /** > * @param primitive > * @return converter >Index: src/org/eclipse/core/internal/databinding/Queue.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/Queue.java,v >retrieving revision 1.3 >diff -u -r1.3 Queue.java >--- src/org/eclipse/core/internal/databinding/Queue.java 9 May 2008 14:13:00 -0000 1.3 >+++ src/org/eclipse/core/internal/databinding/Queue.java 16 Jun 2008 20:08:56 -0000 >@@ -72,4 +72,12 @@ > public boolean isEmpty() { > return first == null; > } >+ >+ /** >+ * Removes all elements from this queue. >+ */ >+ public void clear() { >+ first = null; >+ last = null; >+ } > } >\ No newline at end of file >Index: META-INF/MANIFEST.MF >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/META-INF/MANIFEST.MF,v >retrieving revision 1.14 >diff -u -r1.14 MANIFEST.MF >--- META-INF/MANIFEST.MF 11 Apr 2008 22:18:08 -0000 1.14 >+++ META-INF/MANIFEST.MF 16 Jun 2008 20:08:55 -0000 >@@ -22,7 +22,8 @@ > org.eclipse.core.internal.databinding.observable.masterdetail;x-friends:="org.eclipse.jface.tests.databinding", > org.eclipse.core.internal.databinding.observable.tree;x-friends:="org.eclipse.jface.databinding,org.eclipse.jface.tests.databinding", > org.eclipse.core.internal.databinding.validation;x-friends:="org.eclipse.jface.tests.databinding" >-Require-Bundle: org.eclipse.equinox.common;bundle-version="[3.2.0,4.0.0)" >+Require-Bundle: org.eclipse.equinox.common;bundle-version="[3.2.0,4.0.0)", >+ org.eclipse.core.jobs > Import-Package-Comment: see http://wiki.eclipse.org/ > Import-Package: com.ibm.icu.text, > org.osgi.framework;version="[1.4.0,2.0.0)";resolution:=optional, >Index: src/org/eclipse/core/databinding/conversion/Converter.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/databinding/conversion/Converter.java,v >retrieving revision 1.3 >diff -u -r1.3 Converter.java >--- src/org/eclipse/core/databinding/conversion/Converter.java 22 May 2007 19:22:18 -0000 1.3 >+++ src/org/eclipse/core/databinding/conversion/Converter.java 16 Jun 2008 20:08:55 -0000 >@@ -40,4 +40,10 @@ > return toType; > } > >+ /** >+ * By default, this method returns <code>false</code>. >+ */ >+ public boolean isAsync() { >+ return false; >+ } > } >Index: src/org/eclipse/core/databinding/conversion/IConverter.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/databinding/conversion/IConverter.java,v >retrieving revision 1.8 >diff -u -r1.8 IConverter.java >--- src/org/eclipse/core/databinding/conversion/IConverter.java 9 May 2008 14:13:00 -0000 1.8 >+++ src/org/eclipse/core/databinding/conversion/IConverter.java 16 Jun 2008 20:08:55 -0000 >@@ -50,4 +50,19 @@ > * @return the converted object, of type {@link #getToType()} > */ > public Object convert(Object fromObject); >+ >+ /** >+ * Returns whether the {@link #convert(Object)} method of this converter is >+ * intended to be executed asynchronously. >+ * >+ * <p> >+ * Note that even if this method returns <code>true</code>, there is no >+ * guarantee as of whether the conversion will indeed by executed >+ * asynchronously or not. >+ * </p> >+ * >+ * @return whether the {@link #convert(Object)} method of this converter is >+ * intended to be executed asynchronously. >+ */ >+ public boolean isAsync(); > } >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 16 Jun 2008 20:08:56 -0000 >@@ -8,6 +8,7 @@ > * Contributors: > * Matthew Hall - initial API and implementation (bug 218269) > * Boris Bokowski - bug 218269 >+ * Ovidio Mallo - bug 233191 > ******************************************************************************/ > > package org.eclipse.core.databinding.validation; >@@ -17,6 +18,7 @@ > > import org.eclipse.core.databinding.ValidationStatusProvider; > import org.eclipse.core.databinding.observable.ChangeEvent; >+import org.eclipse.core.databinding.observable.Diffs; > import org.eclipse.core.databinding.observable.IChangeListener; > import org.eclipse.core.databinding.observable.IObservable; > import org.eclipse.core.databinding.observable.ObservableTracker; >@@ -30,6 +32,7 @@ > 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.ValueDiff; > import org.eclipse.core.databinding.observable.value.WritableValue; > import org.eclipse.core.internal.databinding.observable.ValidatedObservableList; > import org.eclipse.core.internal.databinding.observable.ValidatedObservableMap; >@@ -115,11 +118,12 @@ > */ > public abstract class MultiValidator extends ValidationStatusProvider { > private Realm realm; >- private IObservableValue validationStatus; >+ private ValidationStatusObservableValue validationStatus; > private IObservableValue unmodifiableValidationStatus; > private WritableList targets; > private IObservableList unmodifiableTargets; > private IObservableList models; >+ private boolean stale = false; > > IListChangeListener targetsListener = new IListChangeListener() { > public void handleListChange(ListChangeEvent event) { >@@ -160,8 +164,7 @@ > Assert.isNotNull(realm, "Realm cannot be null"); //$NON-NLS-1$ > this.realm = realm; > >- validationStatus = new WritableValue(realm, ValidationStatus.ok(), >- IStatus.class); >+ validationStatus = new ValidationStatusObservableValue(realm); > > targets = new WritableList(realm, new ArrayList(), IObservable.class); > targets.addListChangeListener(targetsListener); >@@ -182,6 +185,13 @@ > * validation status of this MultiValidator. The returned observable is in > * the same realm as this MultiValidator. > * >+ * <p> >+ * In case the validation of this MultiValidator is performed >+ * asynchronously, the staleness state of the validation status observable >+ * can be used to determine whether there are any pending validations. This >+ * allows for awaiting the termination of those validations, if desired, by >+ * tracking the observable's staleness state. >+ * > * @return an {@link IObservableValue} whose value is always the current > * validation status of this MultiValidator. > */ >@@ -232,6 +242,68 @@ > protected abstract IStatus validate(); > > /** >+ * Notifies the MultiValidator that the current validation status is being >+ * computed asynchronously and is not available yet. >+ * >+ * <p> >+ * Subclasses should invoke this method whenever the {@link #validate() >+ * validation} is performed asynchronously. Calling this method guarantees >+ * that the {@link #getValidationStatus() validation status observable} is >+ * correctly set to be stale. >+ * >+ * <p> >+ * Typically, this method should be called from within the >+ * {@link #validate()} method when the asynchronous validation is started >+ * while a reasonable validation status must always be returned to be used >+ * while the pending validation has not terminated. Once the validation is >+ * available, it can be passed to the MultiValidator by calling the >+ * {@link #exitStale(IStatus)} method. >+ * >+ * <p> >+ * Note: This method must be called from within the MultiValidator's realm. >+ * >+ * @see #exitStale(IStatus) >+ * @see IObservableValue#isStale() >+ */ >+ protected final void enterStale() { >+ stale = true; >+ validationStatus.fireStale(); >+ } >+ >+ /** >+ * Notifies the MultiValidator that an asynchronous validation has >+ * terminated which resulted in the given validation status. >+ * >+ * <p> >+ * This method sets the given <code>status</code> as the current validation >+ * status and sets the {@link #getValidationStatus() validation status >+ * observable} to not be stale anymore. >+ * >+ * <p> >+ * Note: This method must be called from within the MultiValidator's realm. >+ * >+ * @param status >+ * the resulting status of the terminated validation. >+ * >+ * @see #enterStale() >+ * @see IObservableValue#isStale() >+ */ >+ protected final void exitStale(IStatus status) { >+ stale = false; >+ Object oldStatus = validationStatus.getValue(); >+ validationStatus.setValue(status); >+ >+ // In order to signal that the validation status observable is not stale >+ // anymore, we must always fire a value change event, even if the >+ // validation status has not changed. In the latter case, we must do so >+ // explicitly. >+ if (oldStatus.equals(status)) { >+ validationStatus.fireValueChange(Diffs.createValueDiff(oldStatus, >+ status)); >+ } >+ } >+ >+ /** > * 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 >@@ -366,4 +438,25 @@ > super.dispose(); > } > >+ private class ValidationStatusObservableValue extends WritableValue { >+ >+ public ValidationStatusObservableValue(Realm realm) { >+ super(realm, ValidationStatus.ok(), IStatus.class); >+ } >+ >+ public boolean isStale() { >+ ObservableTracker.getterCalled(this); >+ return stale; >+ } >+ >+ protected void fireStale() { >+ // Make the method accessible to the MultiValidator class. >+ super.fireStale(); >+ } >+ >+ protected void fireValueChange(ValueDiff diff) { >+ // Make the method accessible to the MultiValidator class. >+ super.fireValueChange(diff); >+ } >+ } > } >Index: src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableValue.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableValue.java,v >retrieving revision 1.2 >diff -u -r1.2 UnmodifiableObservableValue.java >--- src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableValue.java 22 Feb 2008 05:33:48 -0000 1.2 >+++ src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableValue.java 16 Jun 2008 20:08:56 -0000 >@@ -55,4 +55,8 @@ > public Object getValueType() { > return wrappedValue.getValueType(); > } >+ >+ public boolean isStale() { >+ return wrappedValue.isStale(); >+ } > } >Index: src/org/eclipse/core/internal/databinding/UpdateExecutor.java >=================================================================== >RCS file: src/org/eclipse/core/internal/databinding/UpdateExecutor.java >diff -N src/org/eclipse/core/internal/databinding/UpdateExecutor.java >--- /dev/null 1 Jan 1970 00:00:00 -0000 >+++ src/org/eclipse/core/internal/databinding/UpdateExecutor.java 1 Jan 1970 00:00:00 -0000 >@@ -0,0 +1,234 @@ >+/******************************************************************************* >+ * Copyright (c) 2008 Ovidio Mallo 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: >+ * Ovidio Mallo - initial API and implementation (bug 233191) >+ ******************************************************************************/ >+ >+package org.eclipse.core.internal.databinding; >+ >+import java.util.HashSet; >+import java.util.Iterator; >+import java.util.Set; >+ >+import org.eclipse.core.runtime.IProgressMonitor; >+import org.eclipse.core.runtime.IStatus; >+import org.eclipse.core.runtime.Status; >+import org.eclipse.core.runtime.jobs.Job; >+ >+/** >+ * Simple class to manage the execution of {@link UpdateRunnable}s intended to >+ * be used for performing the updates of a binding. >+ * >+ * <p> >+ * This class is thread safe and supports the synchronous as well as >+ * asynchronous execution of a set of UpdateRunnables where consecutive updates >+ * are guaranteed to be executed in the correct order. In addition, this class >+ * allows for tracking the set of pending updates which can eventually be >+ * canceled upon scheduling a new update which makes previous updates obsolete. >+ * </p> >+ * >+ * @see #execute(UpdateRunnable, boolean) >+ * @see #hasPendingUpdates() >+ */ >+public class UpdateExecutor { >+ >+ private final boolean isAsync; >+ >+ private final Runnable schedulingEventCallback; >+ >+ private final Set pendingUpdates = new HashSet(); >+ >+ private final UpdateJob updateJob; >+ >+ /** >+ * Creates a new UpdateExecutor which will run the individual updates either >+ * synchronously or asynchronously. >+ * >+ * @param isAsync >+ * whether the UpdateRunnables passed to this UpdateExecutor >+ * should be executed asynchronously. >+ * @param schedulingEventCallback >+ * a callback Runnable which will be executed whenever some >+ * scheduling event in the execution of the updates happened. The >+ * code in the runnable can then use this classes API to retrieve >+ * the relevant information. May be <code>null</code>. >+ */ >+ public UpdateExecutor(boolean isAsync, Runnable schedulingEventCallback) { >+ this.isAsync = isAsync; >+ this.schedulingEventCallback = schedulingEventCallback; >+ >+ // Only instantiate the UpdateJob if we are in asynchronous mode. >+ if (isAsync) { >+ this.updateJob = new UpdateJob(); >+ this.updateJob.setSystem(true); >+ } else { >+ this.updateJob = null; >+ } >+ } >+ >+ /** >+ * Executes the given {@link UpdateRunnable update} and eventually tries to >+ * cancel previous, still pending updates, if desired. >+ * >+ * @param update >+ * the new update to execute. >+ * @param cancelPendingUpdates >+ * whether previous, still pending updates, should be requested >+ * to be {@link UpdateRunnable#cancel() canceled} before >+ * executing the new update. >+ * >+ * @see UpdateRunnable#run() >+ * @see UpdateRunnable#cancel() >+ */ >+ public void execute(UpdateRunnable update, boolean cancelPendingUpdates) { >+ // If requested, we try to cancel previous updates. >+ if (cancelPendingUpdates) { >+ cancelPendingUpdates(); >+ } >+ >+ // The executor must be set on the UpdateRunnable in order to get >+ // notified of the update's termination. >+ update.setExecutor(this); >+ >+ // Add the new update to the set of pending updates. >+ synchronized (pendingUpdates) { >+ pendingUpdates.add(update); >+ } >+ >+ // Right before starting the update, we notify about the new update. >+ if (schedulingEventCallback != null) { >+ schedulingEventCallback.run(); >+ } >+ >+ if (isAsync) { >+ synchronized (updateJob.updateQueue) { >+ // Whenever we are adding a new update to an empty queue, we >+ // must re-schedule the UpdateJob. Note that it is OK to do this >+ // before actually adding the update to the queue since we are >+ // already holding the lock on the queue at this point. >+ if (updateJob.updateQueue.isEmpty()) { >+ updateJob.schedule(); >+ } >+ updateJob.updateQueue.enqueue(update); >+ } >+ } else { >+ // In synchronous mode, we simply execute the run method. >+ update.run(); >+ } >+ } >+ >+ /** >+ * Indicates that the given update was running and has now terminated. >+ * >+ * @param update >+ * the update which has terminated. >+ * >+ * @see #hasPendingUpdates() >+ * @see UpdateRunnable#notifyDone() >+ */ >+ /* package */void endUpdate(UpdateRunnable update) { >+ synchronized (pendingUpdates) { >+ pendingUpdates.remove(update); >+ } >+ >+ if (schedulingEventCallback != null) { >+ schedulingEventCallback.run(); >+ } >+ } >+ >+ /** >+ * Returns whether there are any pending updates to be executed on behalf of >+ * this UpdateExecutor. >+ * >+ * <p> >+ * Note that an update is defined to be pending as soon as it has been >+ * passed to this UpdateExecutor for >+ * {@link #execute(UpdateRunnable, boolean) execution}, regardless of >+ * whether the update is already running or not. >+ * </p> >+ * >+ * @return whether there are any pending updates to be executed on behalf of >+ * this UpdateExecutor. >+ */ >+ public boolean hasPendingUpdates() { >+ synchronized (pendingUpdates) { >+ return !pendingUpdates.isEmpty(); >+ } >+ } >+ >+ /** >+ * Requests all the pending updates to be canceled. >+ * >+ * <p> >+ * This method guarantees that by the end of its execution, all the pending >+ * updates have been requested to be {@link UpdateRunnable#cancel() >+ * canceled} and that updates which have not been started yet (if in >+ * asynchronous mode) will never be run. >+ * </p> >+ * >+ * @see UpdateRunnable#cancel() >+ */ >+ private void cancelPendingUpdates() { >+ synchronized (pendingUpdates) { >+ // Cancel all the pending updates. >+ for (Iterator iter = pendingUpdates.iterator(); iter.hasNext();) { >+ UpdateRunnable update = (UpdateRunnable) iter.next(); >+ update.cancel(); >+ } >+ pendingUpdates.clear(); >+ } >+ >+ if (isAsync) { >+ // If in asynchronous mode, we must also clear the queue of updates >+ // waiting to be run. >+ synchronized (updateJob.updateQueue) { >+ updateJob.updateQueue.clear(); >+ } >+ } >+ } >+ >+ /** >+ * Terminates the UpdateExecutor by canceling any eventual pending update. >+ * >+ * <p> >+ * Note that once this method has been called, no further updates should be >+ * run on behalf of this UpdateExecutor. >+ * </p> >+ */ >+ public void terminate() { >+ cancelPendingUpdates(); >+ >+ if (schedulingEventCallback != null) { >+ schedulingEventCallback.run(); >+ } >+ } >+ >+ private class UpdateJob extends Job { >+ >+ private final Queue updateQueue = new Queue(); >+ >+ public UpdateJob() { >+ super("Update Job"); //$NON-NLS-1$ >+ } >+ >+ protected IStatus run(IProgressMonitor monitor) { >+ while (true) { >+ UpdateRunnable update; >+ synchronized (updateQueue) { >+ // As soon as we get out of work, we return in order to >+ // release the Thread on whose behalf the job is running. >+ if (updateQueue.isEmpty()) { >+ return Status.OK_STATUS; >+ } >+ update = (UpdateRunnable) updateQueue.dequeue(); >+ } >+ update.run(); >+ } >+ } >+ } >+} >Index: src/org/eclipse/core/databinding/validation/IValidator2.java >=================================================================== >RCS file: src/org/eclipse/core/databinding/validation/IValidator2.java >diff -N src/org/eclipse/core/databinding/validation/IValidator2.java >--- /dev/null 1 Jan 1970 00:00:00 -0000 >+++ src/org/eclipse/core/databinding/validation/IValidator2.java 1 Jan 1970 00:00:00 -0000 >@@ -0,0 +1,34 @@ >+/******************************************************************************* >+ * Copyright (c) 2008 Ovidio Mallo 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: >+ * Ovidio Mallo - initial API and implementation (bug 233191) >+ ******************************************************************************/ >+ >+package org.eclipse.core.databinding.validation; >+ >+/** >+ * Extension of the {@link IValidator} interface which adds API for expressing >+ * the intend of a validator to be executed asynchronously. >+ */ >+public interface IValidator2 extends IValidator { >+ >+ /** >+ * Returns whether the {@link #validate(Object)} method of this validator is >+ * intended to be executed asynchronously. >+ * >+ * <p> >+ * Note that even if this method returns <code>true</code>, there is no >+ * guarantee as of whether the validation will indeed by executed >+ * asynchronously or not. >+ * </p> >+ * >+ * @return whether the {@link #validate(Object)} method of this validator is >+ * intended to be executed asynchronously. >+ */ >+ public boolean isAsync(); >+} >Index: src/org/eclipse/core/internal/databinding/UpdateValidationObservableValue.java >=================================================================== >RCS file: src/org/eclipse/core/internal/databinding/UpdateValidationObservableValue.java >diff -N src/org/eclipse/core/internal/databinding/UpdateValidationObservableValue.java >--- /dev/null 1 Jan 1970 00:00:00 -0000 >+++ src/org/eclipse/core/internal/databinding/UpdateValidationObservableValue.java 1 Jan 1970 00:00:00 -0000 >@@ -0,0 +1,84 @@ >+/******************************************************************************* >+ * Copyright (c) 2008 Ovidio Mallo 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: >+ * Ovidio Mallo - initial API and implementation (bug 233191) >+ ******************************************************************************/ >+ >+package org.eclipse.core.internal.databinding; >+ >+import org.eclipse.core.databinding.observable.Diffs; >+import org.eclipse.core.databinding.observable.Realm; >+import org.eclipse.core.databinding.observable.value.AbstractObservableValue; >+import org.eclipse.core.databinding.observable.value.IObservableValue; >+import org.eclipse.core.runtime.IStatus; >+import org.eclipse.core.runtime.Status; >+ >+/** >+ * Simple {@link IObservableValue} of type {@link IStatus} intended to be used >+ * for the validation resulting from the update process of a binding. >+ * >+ * <p> >+ * This observable provides some convenience API to signal a possible change in >+ * its staleness state. Whenever the staleness of the validation observable >+ * changes (or may have changed), {@link #updateStaleness()} should be called >+ * which either fires a stale event in case the method {@link #isStale()} >+ * returns <code>true</code>, or else, a value change event is fired in order to >+ * signal that the observable is not stale anymore. Subclasses must implement >+ * the {@link #isStale()} method. >+ * </p> >+ */ >+public abstract class UpdateValidationObservableValue extends >+ AbstractObservableValue { >+ >+ private Object value = Status.OK_STATUS; >+ >+ /** >+ * Creates a new observable with the initial value >+ * <code>Status.OK_STATUS</code> in the given realm. >+ * >+ * @param realm >+ * the observable's realm. >+ */ >+ public UpdateValidationObservableValue(Realm realm) { >+ super(realm); >+ } >+ >+ protected Object doGetValue() { >+ return value; >+ } >+ >+ protected void doSetValue(Object value) { >+ Object oldValue = this.value; >+ this.value = value; >+ if (!Util.equals(oldValue, value)) { >+ fireValueChange(Diffs.createValueDiff(oldValue, value)); >+ } >+ } >+ >+ /** >+ * Updates the staleness of this validation by either firing a stale event >+ * in case the method {@link #isStale()} returns <code>true</code>, or else, >+ * by firing a value change event in order to signal that the observable is >+ * not stale anymore. >+ */ >+ public void updateStaleness() { >+ if (isStale()) { >+ fireStale(); >+ } else { >+ // This synthetic value change event is merely used to signal that >+ // the observable is not stale anymore. >+ fireValueChange(Diffs.createValueDiff(getValue(), getValue())); >+ } >+ } >+ >+ public abstract boolean isStale(); >+ >+ public Object getValueType() { >+ return IStatus.class; >+ } >+} >Index: src/org/eclipse/core/internal/databinding/UpdateRunnable.java >=================================================================== >RCS file: src/org/eclipse/core/internal/databinding/UpdateRunnable.java >diff -N src/org/eclipse/core/internal/databinding/UpdateRunnable.java >--- /dev/null 1 Jan 1970 00:00:00 -0000 >+++ src/org/eclipse/core/internal/databinding/UpdateRunnable.java 1 Jan 1970 00:00:00 -0000 >@@ -0,0 +1,92 @@ >+/******************************************************************************* >+ * Copyright (c) 2008 Ovidio Mallo 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: >+ * Ovidio Mallo - initial API and implementation (bug 233191) >+ ******************************************************************************/ >+ >+package org.eclipse.core.internal.databinding; >+ >+/** >+ * Simple implementation of a <code>Runnable</code> which can be used to have a >+ * binding update executed by an {@link UpdateExecutor}. >+ * >+ * <p> >+ * This thread safe class adds methods to support a collaborative >+ * {@link #cancel() canceling} of a running update. In addition, in order to >+ * allow for an <code>UpdateExecutor</code> to keep track of the updates >+ * currently running on its behalf, every <code>UpdateRunnable</code> is >+ * responsible for {@link #notifyDone() notifying} the executor about its >+ * completion. >+ * </p> >+ * >+ * @see #cancel() >+ * @see #notifyDone() >+ */ >+public abstract class UpdateRunnable implements Runnable { >+ >+ private UpdateExecutor executor; >+ >+ private volatile boolean canceled = false; >+ >+ /** >+ * Sets the {@link UpdateExecutor} on whose behalf this update is executed. >+ * >+ * @param executor >+ * the <code>UpdateExecutor</code> on whose behalf this update is >+ * executed. >+ */ >+ /* package */final synchronized void setExecutor(UpdateExecutor executor) { >+ this.executor = executor; >+ } >+ >+ /** >+ * Signals that this update has completed. >+ * >+ * <p> >+ * Calling this method is typically the responsibility of the running update >+ * itself and is used to allow for the {@link UpdateExecutor executor} of >+ * this update to keep track of the pending updates. >+ * </p> >+ * >+ * <p> >+ * Note that if an update terminates asynchronously, this method should only >+ * be called as soon as the update really terminates and not already when >+ * reaching the end of the {@link #run()} method. >+ * </p> >+ * >+ * @see #setExecutor(UpdateExecutor) >+ * @see UpdateExecutor#endUpdate(UpdateRunnable) >+ */ >+ protected final synchronized void notifyDone() { >+ if (executor != null) { >+ executor.endUpdate(this); >+ } >+ } >+ >+ /** >+ * Returns whether this update has been requested to be canceled. >+ * >+ * @return whether this update has been requested to be canceled. >+ * >+ * @see #cancel() >+ */ >+ public final boolean isCanceled() { >+ return canceled; >+ } >+ >+ /** >+ * Requests this update to be canceled while it is up to the running update >+ * to decide if and when the update can be canceled before its natural >+ * completion. >+ * >+ * @see #isCanceled() >+ */ >+ public final void cancel() { >+ this.canceled = true; >+ } >+} >#P org.eclipse.jface.examples.databinding >Index: src/org/eclipse/jface/examples/databinding/nestedselection/TestMasterDetail.java >=================================================================== >RCS file: /cvsroot/eclipse/org.eclipse.jface.examples.databinding/src/org/eclipse/jface/examples/databinding/nestedselection/TestMasterDetail.java,v >retrieving revision 1.18 >diff -u -r1.18 TestMasterDetail.java >--- src/org/eclipse/jface/examples/databinding/nestedselection/TestMasterDetail.java 16 Mar 2007 21:19:35 -0000 1.18 >+++ src/org/eclipse/jface/examples/databinding/nestedselection/TestMasterDetail.java 16 Jun 2008 20:09:00 -0000 >@@ -252,6 +252,10 @@ > public Object getToType() { > return String.class; > } >+ >+ public boolean isAsync() { >+ return false; >+ } > }; > IValidator vowelValidator = new IValidator() { > public IStatus validate(Object value) { >Index: src/org/eclipse/jface/examples/databinding/snippets/Snippet022AsyncUpdate.java >=================================================================== >RCS file: src/org/eclipse/jface/examples/databinding/snippets/Snippet022AsyncUpdate.java >diff -N src/org/eclipse/jface/examples/databinding/snippets/Snippet022AsyncUpdate.java >--- /dev/null 1 Jan 1970 00:00:00 -0000 >+++ src/org/eclipse/jface/examples/databinding/snippets/Snippet022AsyncUpdate.java 1 Jan 1970 00:00:00 -0000 >@@ -0,0 +1,320 @@ >+/******************************************************************************* >+ * Copyright (c) 2008 Ovidio Mallo 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: >+ * Ovidio Mallo - initial API and implementation (bug 233191) >+ ******************************************************************************/ >+ >+package org.eclipse.jface.examples.databinding.snippets; >+ >+import java.util.Random; >+ >+import org.eclipse.core.databinding.DataBindingContext; >+import org.eclipse.core.databinding.UpdateListStrategy; >+import org.eclipse.core.databinding.UpdateValueStrategy; >+import org.eclipse.core.databinding.conversion.Converter; >+import org.eclipse.core.databinding.conversion.IConverter; >+import org.eclipse.core.databinding.observable.Realm; >+import org.eclipse.core.databinding.observable.list.IObservableList; >+import org.eclipse.core.databinding.observable.list.WritableList; >+import org.eclipse.core.databinding.observable.value.IObservableValue; >+import org.eclipse.core.databinding.observable.value.WritableValue; >+import org.eclipse.jface.databinding.swt.SWTObservables; >+import org.eclipse.jface.databinding.viewers.ObservableListContentProvider; >+import org.eclipse.jface.layout.GridDataFactory; >+import org.eclipse.jface.layout.GridLayoutFactory; >+import org.eclipse.jface.viewers.IStructuredSelection; >+import org.eclipse.jface.viewers.LabelProvider; >+import org.eclipse.jface.viewers.ListViewer; >+import org.eclipse.swt.SWT; >+import org.eclipse.swt.events.KeyAdapter; >+import org.eclipse.swt.events.KeyEvent; >+import org.eclipse.swt.events.ModifyEvent; >+import org.eclipse.swt.events.ModifyListener; >+import org.eclipse.swt.events.SelectionAdapter; >+import org.eclipse.swt.events.SelectionEvent; >+import org.eclipse.swt.widgets.Button; >+import org.eclipse.swt.widgets.Composite; >+import org.eclipse.swt.widgets.Display; >+import org.eclipse.swt.widgets.Group; >+import org.eclipse.swt.widgets.Label; >+import org.eclipse.swt.widgets.Shell; >+import org.eclipse.swt.widgets.Spinner; >+import org.eclipse.swt.widgets.Text; >+ >+/** >+ * Snippet that demonstrates the usage of asynchronous bindings by defining an >+ * asynchronous converter which introduces some artificial delay during >+ * conversion. The example allows to manually change the target or model of a >+ * ValueBinding/ListBinding and to see how the counterpart gets updated >+ * asynchronously. In addition, automatic updates of the target and/or model can >+ * be triggered. >+ */ >+public class Snippet022AsyncUpdate { >+ >+ private int updateDelay = 1000; >+ >+ private boolean randomizeUpdateDelay = true; >+ >+ private DataBindingContext dbc; >+ >+ private WritableValue valueTarget; >+ >+ private WritableValue valueModel; >+ >+ private WritableList listTarget; >+ >+ private WritableList listModel; >+ >+ public void createControl(Composite parent) { >+ dbc = new DataBindingContext(); >+ >+ parent.setLayout(GridLayoutFactory.fillDefaults().margins(5, 5) >+ .spacing(15, 10).create()); >+ >+ createSettingsGroup(parent); >+ createValueBindingGroup(parent); >+ createListBindingGroup(parent); >+ createAutomaticUpdatesGroup(parent); >+ } >+ >+ private void createSettingsGroup(Composite parent) { >+ Group group = new Group(parent, SWT.NONE); >+ group.setText("Settings"); >+ group.setLayout(GridLayoutFactory.fillDefaults().numColumns(2).margins( >+ 5, 7).spacing(15, 10).create()); >+ GridDataFactory.fillDefaults().grab(true, false).applyTo(group); >+ >+ new Label(group, SWT.NONE).setText("Update delay"); >+ final Spinner updateDelaySpinner = new Spinner(group, SWT.BORDER); >+ GridDataFactory.fillDefaults().grab(true, false).applyTo( >+ updateDelaySpinner); >+ updateDelaySpinner.setMinimum(1); >+ updateDelaySpinner.setMaximum(Integer.MAX_VALUE); >+ updateDelaySpinner.setSelection(updateDelay); >+ updateDelaySpinner.addModifyListener(new ModifyListener() { >+ public void modifyText(ModifyEvent e) { >+ updateDelay = updateDelaySpinner.getSelection(); >+ } >+ }); >+ >+ final Button randomizeUpdateDelayButton = new Button(group, SWT.CHECK); >+ randomizeUpdateDelayButton.setText("Randomize update delay"); >+ GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo( >+ randomizeUpdateDelayButton); >+ randomizeUpdateDelayButton.setSelection(randomizeUpdateDelay); >+ randomizeUpdateDelayButton.addSelectionListener(new SelectionAdapter() { >+ public void widgetSelected(SelectionEvent e) { >+ randomizeUpdateDelay = randomizeUpdateDelayButton >+ .getSelection(); >+ } >+ }); >+ } >+ >+ private void createValueBindingGroup(Composite parent) { >+ Group group = new Group(parent, SWT.NONE); >+ group.setText("Value Binding"); >+ group.setLayout(GridLayoutFactory.fillDefaults().numColumns(2).margins( >+ 5, 7).spacing(15, 10).create()); >+ GridDataFactory.fillDefaults().grab(true, false).applyTo(group); >+ >+ valueTarget = WritableValue.withValueType(String.class); >+ createValueComponent(group, "Target", valueTarget); >+ >+ valueModel = WritableValue.withValueType(String.class); >+ createValueComponent(group, "Model", valueModel); >+ >+ dbc.bindValue(valueTarget, valueModel, new UpdateValueStrategy() >+ .setConverter(asyncConverter()), new UpdateValueStrategy() >+ .setConverter(asyncConverter())); >+ } >+ >+ private void createValueComponent(Composite parent, String title, >+ IObservableValue observableValue) { >+ new Label(parent, SWT.NONE).setText(title); >+ Text text = new Text(parent, SWT.BORDER); >+ GridDataFactory.fillDefaults().grab(true, false).applyTo(text); >+ >+ dbc.bindValue(SWTObservables.observeText(text, SWT.Modify), >+ observableValue, null, null); >+ } >+ >+ private void createListBindingGroup(Composite parent) { >+ Group group = new Group(parent, SWT.NONE); >+ group.setText("List Binding"); >+ group.setLayout(GridLayoutFactory.fillDefaults().numColumns(2).margins( >+ 5, 7).spacing(15, 10).create()); >+ GridDataFactory.fillDefaults().grab(true, true).applyTo(group); >+ >+ listTarget = WritableList.withElementType(String.class); >+ createListComponent(group, "Target", listTarget); >+ >+ listModel = WritableList.withElementType(String.class); >+ createListComponent(group, "Model", listModel); >+ >+ dbc.bindList(listTarget, listModel, new UpdateListStrategy() >+ .setConverter(asyncConverter()), new UpdateListStrategy() >+ .setConverter(asyncConverter())); >+ } >+ >+ private void createListComponent(Composite parent, String title, >+ final IObservableList observableList) { >+ new Label(parent, SWT.NONE).setText(title); >+ final Text targetText = new Text(parent, SWT.BORDER); >+ GridDataFactory.fillDefaults().grab(true, false).applyTo(targetText); >+ targetText.addKeyListener(new KeyAdapter() { >+ public void keyPressed(KeyEvent e) { >+ if (e.keyCode == SWT.CR) { >+ observableList.add(targetText.getText()); >+ targetText.selectAll(); >+ } >+ } >+ }); >+ new Label(parent, SWT.NONE); >+ final ListViewer targetList = new ListViewer(parent, SWT.BORDER >+ | SWT.MULTI | SWT.V_SCROLL); >+ GridDataFactory.fillDefaults().minSize(SWT.DEFAULT, 150).grab(true, >+ true).applyTo(targetList.getList()); >+ targetList.setContentProvider(new ObservableListContentProvider()); >+ targetList.setLabelProvider(new LabelProvider()); >+ targetList.setInput(observableList); >+ targetList.getList().addKeyListener(new KeyAdapter() { >+ public void keyPressed(KeyEvent e) { >+ if (e.keyCode == SWT.DEL) { >+ IStructuredSelection selection = (IStructuredSelection) targetList >+ .getSelection(); >+ observableList.removeAll(selection.toList()); >+ } >+ } >+ }); >+ } >+ >+ private void createAutomaticUpdatesGroup(Composite parent) { >+ Group group = new Group(parent, SWT.NONE); >+ group.setText("Automatic Updates"); >+ group.setLayout(GridLayoutFactory.fillDefaults().numColumns(3) >+ .equalWidth(true).margins(5, 5).create()); >+ GridDataFactory.fillDefaults().grab(true, false).applyTo(group); >+ >+ Button targetButton = new Button(group, SWT.PUSH); >+ targetButton.setText("Target"); >+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, >+ false).applyTo(targetButton); >+ targetButton.addSelectionListener(new SelectionAdapter() { >+ public void widgetSelected(SelectionEvent e) { >+ valueModel.setValue(""); >+ listTarget.clear(); >+ for (int i = 0; i < 10; i++) { >+ String value = String.valueOf(i); >+ valueTarget.setValue(value); >+ listTarget.add(value); >+ >+ Display.getCurrent().update(); >+ sleep(100); >+ } >+ } >+ }); >+ >+ Button modelButton = new Button(group, SWT.PUSH); >+ modelButton.setText("Model"); >+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, >+ false).applyTo(modelButton); >+ modelButton.addSelectionListener(new SelectionAdapter() { >+ public void widgetSelected(SelectionEvent e) { >+ valueTarget.setValue(""); >+ listModel.clear(); >+ for (int i = 0; i < 10; i++) { >+ String value = String.valueOf(i); >+ valueModel.setValue(value); >+ listModel.add(value); >+ >+ Display.getCurrent().update(); >+ sleep(100); >+ } >+ } >+ }); >+ >+ Button twowayButton = new Button(group, SWT.PUSH); >+ twowayButton.setText("Twoway"); >+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, >+ false).applyTo(twowayButton); >+ twowayButton.addSelectionListener(new SelectionAdapter() { >+ public void widgetSelected(SelectionEvent e) { >+ valueTarget.setValue(""); >+ valueModel.setValue(""); >+ listTarget.clear(); >+ listModel.clear(); >+ for (int i = 0; i < 10; i++) { >+ String value = String.valueOf(i); >+ if (i % 2 == 0) { >+ valueTarget.setValue(value); >+ listTarget.add(value); >+ } else { >+ valueModel.setValue(value); >+ listModel.add(value); >+ } >+ >+ Display.getCurrent().update(); >+ sleep(100); >+ } >+ } >+ }); >+ } >+ >+ private IConverter asyncConverter() { >+ return new Converter(null, null) { >+ private final Random random = new Random(System.currentTimeMillis()); >+ >+ public Object convert(Object fromObject) { >+ if (randomizeUpdateDelay) { >+ sleep(random.nextInt(updateDelay)); >+ } else { >+ sleep(updateDelay); >+ } >+ >+ return fromObject; >+ } >+ >+ public boolean isAsync() { >+ return true; >+ } >+ }; >+ } >+ >+ private static void sleep(long millis) { >+ try { >+ Thread.sleep(millis); >+ } catch (InterruptedException e1) { >+ // go ahead >+ } >+ } >+ >+ public static void main(String[] args) { >+ Display display = new Display(); >+ >+ Realm.runWithDefault(SWTObservables.getRealm(display), new Runnable() { >+ public void run() { >+ Display display = Display.getCurrent(); >+ final Shell shell = new Shell(display); >+ shell.setText("Asynchronous Bindings"); >+ new Snippet022AsyncUpdate().createControl(shell); >+ >+ shell.setMinimumSize(shell.computeSize(350, SWT.DEFAULT)); >+ shell.pack(); >+ shell.open(); >+ >+ // The SWT event loop >+ while (!shell.isDisposed()) { >+ if (!display.readAndDispatch()) { >+ display.sleep(); >+ } >+ } >+ display.dispose(); >+ } >+ }); >+ } >+} >Index: src/org/eclipse/jface/examples/databinding/snippets/Snippet023AsyncUpdateWizard.java >=================================================================== >RCS file: src/org/eclipse/jface/examples/databinding/snippets/Snippet023AsyncUpdateWizard.java >diff -N src/org/eclipse/jface/examples/databinding/snippets/Snippet023AsyncUpdateWizard.java >--- /dev/null 1 Jan 1970 00:00:00 -0000 >+++ src/org/eclipse/jface/examples/databinding/snippets/Snippet023AsyncUpdateWizard.java 1 Jan 1970 00:00:00 -0000 >@@ -0,0 +1,225 @@ >+/******************************************************************************* >+ * Copyright (c) 2008 Ovidio Mallo 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: >+ * Ovidio Mallo - initial API and implementation (bug 233191) >+ ******************************************************************************/ >+ >+package org.eclipse.jface.examples.databinding.snippets; >+ >+import java.util.Random; >+ >+import org.eclipse.core.databinding.DataBindingContext; >+import org.eclipse.core.databinding.UpdateValueStrategy; >+import org.eclipse.core.databinding.observable.Realm; >+import org.eclipse.core.databinding.observable.value.WritableValue; >+import org.eclipse.core.databinding.validation.IValidator2; >+import org.eclipse.core.databinding.validation.MultiValidator; >+import org.eclipse.core.databinding.validation.ValidationStatus; >+import org.eclipse.core.runtime.IProgressMonitor; >+import org.eclipse.core.runtime.IStatus; >+import org.eclipse.core.runtime.Status; >+import org.eclipse.core.runtime.jobs.Job; >+import org.eclipse.jface.databinding.swt.SWTObservables; >+import org.eclipse.jface.databinding.wizard.WizardPageSupport; >+import org.eclipse.jface.examples.databinding.decoration.ControlDecorator; >+import org.eclipse.jface.layout.GridDataFactory; >+import org.eclipse.jface.layout.GridLayoutFactory; >+import org.eclipse.jface.resource.ImageDescriptor; >+import org.eclipse.jface.util.Util; >+import org.eclipse.jface.wizard.Wizard; >+import org.eclipse.jface.wizard.WizardDialog; >+import org.eclipse.jface.wizard.WizardPage; >+import org.eclipse.swt.SWT; >+import org.eclipse.swt.graphics.Image; >+import org.eclipse.swt.widgets.Composite; >+import org.eclipse.swt.widgets.Display; >+import org.eclipse.swt.widgets.Label; >+import org.eclipse.swt.widgets.Shell; >+import org.eclipse.swt.widgets.Text; >+ >+/** >+ * Snippet that demonstrates the usage of asynchronous binding as well as cross >+ * field validations in the context of a wizard page. The example mainly tries >+ * to illustrate how the WizardPageSupport class tracks the staleness state of >+ * all the ValidationStatusProviders of the given data binding context, thus >+ * waiting for pending validations before allowing the user to flip to the next >+ * wizard page. >+ */ >+public class Snippet023AsyncUpdateWizard { >+ >+ private static class AsyncBindingWizardPage extends WizardPage { >+ >+ public AsyncBindingWizardPage() { >+ super("Asynchronous Bindings", "Asynchronous Bindings", >+ ImageDescriptor.createFromImage(new Image(Display >+ .getCurrent(), 16, 16))); >+ } >+ >+ public void createControl(Composite parent) { >+ Composite container = new Composite(parent, SWT.NONE); >+ container.setLayout(GridLayoutFactory.fillDefaults().numColumns(2) >+ .margins(5, 5).spacing(15, 10).create()); >+ GridDataFactory.fillDefaults().grab(true, false).applyTo(container); >+ >+ final DataBindingContext dbc = new DataBindingContext(); >+ >+ // We use a control decorator to better illustrate when validations >+ // are pending on the individual bindings. >+ ControlDecorator controlDecorator = new ControlDecorator(); >+ controlDecorator.addDbc(dbc); >+ >+ WizardPageSupport.create(this, dbc); >+ >+ new Label(container, SWT.NONE).setText("Value 1"); >+ Text v1Text = new Text(container, SWT.BORDER); >+ GridDataFactory.fillDefaults().grab(true, false).applyTo(v1Text); >+ >+ final WritableValue value1 = new WritableValue(); >+ dbc.bindValue(SWTObservables.observeText(v1Text, SWT.Modify), >+ value1, asyncStrategy(), null); >+ >+ new Label(container, SWT.NONE).setText("Value 2"); >+ Text v2Text = new Text(container, SWT.BORDER); >+ GridDataFactory.fillDefaults().grab(true, false).applyTo(v2Text); >+ >+ final WritableValue value2 = new WritableValue(); >+ dbc.bindValue(SWTObservables.observeText(v2Text, SWT.Modify), >+ value2, asyncStrategy(), null); >+ >+ // Define the cross field validator. >+ MultiValidator multiValidator = new MultiValidator() { >+ private Job validationJob; >+ >+ protected IStatus validate() { >+ // Cancel any previous job when re-evaluating the >+ // validation. >+ if (validationJob != null) { >+ validationJob.cancel(); >+ } >+ >+ // The observables cannot be accessed from within a >+ // different thread so we must extract their containing >+ // values at this point. >+ final Object v1 = value1.getValue(); >+ final Object v2 = value2.getValue(); >+ validationJob = new Job("Equality validation") { >+ protected IStatus run(final IProgressMonitor monitor) { >+ // Do the actual validation. >+ final IStatus validation; >+ if (!Util.equals(v1, v2)) { >+ validation = ValidationStatus >+ .error("The two values must be equal."); >+ } else { >+ validation = ValidationStatus.ok(); >+ } >+ >+ // Take it easy :-). >+ try { >+ Thread.sleep(1000); >+ } catch (InterruptedException e) { >+ // go ahead >+ } >+ >+ // Notify the MultiValidator about the completed >+ // validation. This must be done from within the >+ // MultiValidator's realm (here the validation >+ // realm). >+ dbc.getValidationRealm().exec(new Runnable() { >+ public void run() { >+ // Check if this validation has been >+ // canceled in the meanwhile. >+ if (!monitor.isCanceled()) { >+ exitStale(validation); >+ } >+ } >+ }); >+ >+ return Status.OK_STATUS; >+ } >+ }; >+ validationJob.setSystem(true); >+ validationJob.schedule(); >+ >+ // Notify the MultiValidator about the validation being >+ // performed asynchronously. >+ enterStale(); >+ >+ // Return a temporary validation to be shown to the user >+ // while the actual validation has not completed. >+ return ValidationStatus >+ .info("Cross field validation is pending..."); >+ } >+ >+ public void dispose() { >+ if (validationJob != null) { >+ validationJob.cancel(); >+ } >+ >+ super.dispose(); >+ } >+ }; >+ dbc.addValidationStatusProvider(multiValidator); >+ >+ setControl(container); >+ } >+ } >+ >+ private static UpdateValueStrategy asyncStrategy() { >+ IValidator2 asyncValidator = new IValidator2() { >+ private final Random random = new Random(System.currentTimeMillis()); >+ >+ public IStatus validate(Object value) { >+ try { >+ Thread.sleep(random.nextInt(1000)); >+ } catch (InterruptedException e) { >+ // go ahead >+ } >+ >+ String input = (String) value; >+ if (input != null) { >+ if (input.length() > 15) { >+ return ValidationStatus.error("Input is too long."); >+ } >+ } >+ >+ return ValidationStatus.ok(); >+ } >+ >+ public boolean isAsync() { >+ return true; >+ } >+ }; >+ >+ return new UpdateValueStrategy().setBeforeSetValidator(asyncValidator); >+ } >+ >+ public static void main(String[] args) { >+ Display display = new Display(); >+ >+ Realm.runWithDefault(SWTObservables.getRealm(display), new Runnable() { >+ public void run() { >+ Display display = Display.getCurrent(); >+ final Shell shell = new Shell(display); >+ shell.setText("Asynchronous Bindings"); >+ >+ Wizard wizard = new Wizard() { >+ public boolean performFinish() { >+ return true; >+ } >+ }; >+ wizard.setWindowTitle("Asynchronous Bindings"); >+ wizard.addPage(new AsyncBindingWizardPage()); >+ >+ WizardDialog dialog = new WizardDialog(shell, wizard); >+ dialog.open(); >+ >+ display.dispose(); >+ } >+ }); >+ } >+} >Index: src/org/eclipse/jface/examples/databinding/decoration/ControlDecorator.java >=================================================================== >RCS file: src/org/eclipse/jface/examples/databinding/decoration/ControlDecorator.java >diff -N src/org/eclipse/jface/examples/databinding/decoration/ControlDecorator.java >--- /dev/null 1 Jan 1970 00:00:00 -0000 >+++ src/org/eclipse/jface/examples/databinding/decoration/ControlDecorator.java 1 Jan 1970 00:00:00 -0000 >@@ -0,0 +1,153 @@ >+/******************************************************************************* >+ * Copyright (c) 2008 Ovidio Mallo 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: >+ * Ovidio Mallo - initial API and implementation >+ ******************************************************************************/ >+ >+package org.eclipse.jface.examples.databinding.decoration; >+ >+import java.util.HashMap; >+import java.util.Iterator; >+import java.util.Map; >+ >+import org.eclipse.core.databinding.Binding; >+import org.eclipse.core.databinding.DataBindingContext; >+import org.eclipse.core.databinding.observable.IStaleListener; >+import org.eclipse.core.databinding.observable.StaleEvent; >+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.ListDiff; >+import org.eclipse.core.databinding.observable.list.ListDiffEntry; >+import org.eclipse.core.databinding.observable.value.IObservableValue; >+import org.eclipse.core.databinding.observable.value.IValueChangeListener; >+import org.eclipse.core.databinding.observable.value.ValueChangeEvent; >+import org.eclipse.core.runtime.IStatus; >+import org.eclipse.jface.databinding.swt.ISWTObservable; >+import org.eclipse.jface.fieldassist.ControlDecoration; >+import org.eclipse.jface.fieldassist.FieldDecorationRegistry; >+import org.eclipse.swt.SWT; >+import org.eclipse.swt.graphics.Image; >+import org.eclipse.swt.widgets.Control; >+ >+/** >+ * Simple control decorator which visualizes the validation status of a binding >+ * on a control assuming the binding's target observable is of type >+ * {@link ISWTObservable} and {@link ISWTObservable#getWidget()} returns a >+ * {@link Control} instance to be decorated. In addition, the staleness state of >+ * a binding's validation status observable is also visualized on the control. >+ */ >+public class ControlDecorator { >+ >+ private static final int DECORATION_POSITION = SWT.LEFT | SWT.TOP; >+ >+ private final Map bindingToDecorationMap = new HashMap(); >+ >+ private IListChangeListener bindingsListener = new IListChangeListener() { >+ public void handleListChange(ListChangeEvent event) { >+ ListDiff diff = event.diff; >+ ListDiffEntry[] differences = diff.getDifferences(); >+ for (int i = 0; i < differences.length; i++) { >+ ListDiffEntry listDiffEntry = differences[i]; >+ Binding binding = (Binding) listDiffEntry.getElement(); >+ if (listDiffEntry.isAddition()) { >+ addBinding(binding); >+ } else { >+ removeBinding(binding); >+ } >+ } >+ } >+ }; >+ >+ public void addDbc(DataBindingContext dbc) { >+ IObservableList bindings = dbc.getBindings(); >+ for (Iterator iterator = bindings.iterator(); iterator.hasNext();) { >+ Binding binding = (Binding) iterator.next(); >+ addBinding(binding); >+ } >+ bindings.addListChangeListener(bindingsListener); >+ } >+ >+ private void addBinding(final Binding binding) { >+ if (binding.getTarget() instanceof ISWTObservable) { >+ ISWTObservable target = (ISWTObservable) binding.getTarget(); >+ if (target.getWidget() instanceof Control) { >+ Control control = (Control) target.getWidget(); >+ final ControlDecoration decoration = new ControlDecoration( >+ control, DECORATION_POSITION); >+ >+ binding.getValidationStatus().addValueChangeListener( >+ new IValueChangeListener() { >+ public void handleValueChange(ValueChangeEvent event) { >+ updateDecoration(decoration, binding); >+ } >+ }); >+ >+ binding.getValidationStatus().addStaleListener( >+ new IStaleListener() { >+ public void handleStale(StaleEvent staleEvent) { >+ updateDecoration(decoration, binding); >+ } >+ }); >+ >+ bindingToDecorationMap.put(binding, decoration); >+ updateDecoration(decoration, binding); >+ } >+ } >+ } >+ >+ private void removeBinding(Binding binding) { >+ ControlDecoration decoration = (ControlDecoration) bindingToDecorationMap >+ .get(binding); >+ if (decoration != null) { >+ decoration.hide(); >+ decoration.setDescriptionText(null); >+ >+ bindingToDecorationMap.remove(binding); >+ } >+ } >+ >+ private void updateDecoration(ControlDecoration decoration, Binding binding) { >+ IObservableValue validation = binding.getValidationStatus(); >+ if (validation.isStale()) { >+ showPendingUpdate(decoration); >+ return; >+ } >+ >+ IStatus validationStatus = (IStatus) validation.getValue(); >+ if (validationStatus.isOK()) { >+ decoration.hide(); >+ } else { >+ decoration.show(); >+ decoration.setImage(getStatusImage(validationStatus)); >+ decoration.setDescriptionText(validationStatus.getMessage()); >+ } >+ } >+ >+ private void showPendingUpdate(final ControlDecoration decoration) { >+ decoration.show(); >+ decoration.setImage(getImage(FieldDecorationRegistry.DEC_INFORMATION)); >+ decoration.setDescriptionText("Pending update..."); >+ } >+ >+ private Image getImage(String key) { >+ return FieldDecorationRegistry.getDefault().getFieldDecoration(key) >+ .getImage(); >+ } >+ >+ private Image getStatusImage(IStatus status) { >+ if (status.matches(IStatus.ERROR)) { >+ return getImage(FieldDecorationRegistry.DEC_ERROR); >+ } else if (status.matches(IStatus.WARNING)) { >+ return getImage(FieldDecorationRegistry.DEC_WARNING); >+ } else if (status.matches(IStatus.INFO)) { >+ return getImage(FieldDecorationRegistry.DEC_INFORMATION); >+ } >+ return null; >+ } >+}
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 233191
:
105098
|
105180
|
105188
|
105214
|
105579
|
106547
|
106548