Bug 547422 - Fix i18n support and available values for Enum Renderer

Fix exception when model changed on a background thread that
causes the UI not to be updated.

Change-Id: Icb6aad8f42ca9f2c67b92e081b1eb530401a4049
Signed-off-by: Christian W. Damus <give.a.damus@gmail.com>
diff --git a/bundles/org.eclipse.emf.ecp.view.core.swt/src/org/eclipse/emf/ecp/view/internal/core/swt/renderer/EnumComboViewerSWTRenderer.java b/bundles/org.eclipse.emf.ecp.view.core.swt/src/org/eclipse/emf/ecp/view/internal/core/swt/renderer/EnumComboViewerSWTRenderer.java
index 329f383..915c5e7 100644
--- a/bundles/org.eclipse.emf.ecp.view.core.swt/src/org/eclipse/emf/ecp/view/internal/core/swt/renderer/EnumComboViewerSWTRenderer.java
+++ b/bundles/org.eclipse.emf.ecp.view.core.swt/src/org/eclipse/emf/ecp/view/internal/core/swt/renderer/EnumComboViewerSWTRenderer.java
@@ -12,6 +12,7 @@
  * Eugen - initial API and implementation
  * Christian Damus - enum choice filtering based on ItemPropertyDescriptor
  * Lucas Koehler - enum choice filtering based on ItemPropertyDescriptor
+ * Christian W. Damus - bug 547422
  ******************************************************************************/
 package org.eclipse.emf.ecp.view.internal.core.swt.renderer;
 
@@ -207,7 +208,7 @@
 			availableChoicesValue = new ComputedValue<Collection<?>>(Collection.class) {
 				private final Optional<IChangeNotifier> changeNotifier = propertySource
 					.filter(IChangeNotifier.class::isInstance).map(IChangeNotifier.class::cast);
-				private final INotifyChangedListener listener = __ -> makeDirty();
+				private final INotifyChangedListener listener = __ -> getRealm().exec(this::makeDirty);
 
 				{
 					changeNotifier.ifPresent(cn -> cn.addListener(listener));
diff --git a/tests/org.eclipse.emf.ecp.view.core.swt.tests/src/org/eclipse/emf/ecp/view/internal/core/swt/renderer/EnumComboViewerRenderer_PTest.java b/tests/org.eclipse.emf.ecp.view.core.swt.tests/src/org/eclipse/emf/ecp/view/internal/core/swt/renderer/EnumComboViewerRenderer_PTest.java
index 38918c5..cd0a6d0 100644
--- a/tests/org.eclipse.emf.ecp.view.core.swt.tests/src/org/eclipse/emf/ecp/view/internal/core/swt/renderer/EnumComboViewerRenderer_PTest.java
+++ b/tests/org.eclipse.emf.ecp.view.core.swt.tests/src/org/eclipse/emf/ecp/view/internal/core/swt/renderer/EnumComboViewerRenderer_PTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2011-2015 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,19 +10,31 @@
  *
  * Contributors:
  * Lucas Koehler - initial API and implementation
+ * Christian W. Damus - bug 547422
  ******************************************************************************/
 package org.eclipse.emf.ecp.view.internal.core.swt.renderer;
 
+import static org.hamcrest.CoreMatchers.anything;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
 
+import org.eclipse.core.databinding.observable.IChangeListener;
+import org.eclipse.core.databinding.observable.Realm;
 import org.eclipse.core.databinding.observable.value.IObservableValue;
 import org.eclipse.core.databinding.property.Properties;
 import org.eclipse.emf.databinding.EMFProperties;
@@ -50,9 +62,11 @@
 import org.eclipse.emfforms.spi.core.services.label.NoLabelFoundException;
 import org.eclipse.emfforms.spi.swt.core.layout.SWTGridCell;
 import org.eclipse.emfforms.swt.common.test.AbstractControl_PTest;
+import org.eclipse.jface.databinding.swt.DisplayRealm;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.widgets.Combo;
 import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Event;
 import org.junit.After;
 import org.junit.Before;
@@ -237,4 +251,62 @@
 		assertSame(TestEnum.B, iterator.next());
 		assertSame(TestEnum.C, iterator.next());
 	}
+
+	/**
+	 * Verify that the renderer correctly updates the UI when the model is updated directly
+	 * as by updating with change sets from an EMFStore repository.
+	 */
+	@SuppressWarnings("nls")
+	@Test
+	public void updateUIFromModelChange() {
+		// Have to run this test in a real SWT Display realm because the DefaultRealm for testing
+		// doesn't enforce the thread on which things happen in the observables
+		final Realm realRealm = DisplayRealm.getRealm(Display.getCurrent());
+		Realm.runWithDefault(realRealm, () -> {
+			try {
+				when(getDatabindingService().getObservableValue(any(VDomainModelReference.class), any(EObject.class)))
+					.thenReturn(observableValue);
+
+				// A is filtered by annotation and D is filtered by the property descriptor,
+				// so we can only use B and C for testing
+				domainObject.setMyEnum(TestEnum.B);
+				renderControl(new SWTGridCell(0, 2, getRenderer()));
+
+				final EnumComboViewerSWTRenderer enumRenderer = (EnumComboViewerSWTRenderer) getRenderer();
+				final IObservableValue<?> availableChoices = enumRenderer.getAvailableChoicesValue();
+
+				final IChangeListener listener = mock(IChangeListener.class);
+				availableChoices.addChangeListener(listener);
+
+				// Make changes to the model on background threads and verify that
+				// the observable machinery works correctly
+				final TestEnum[] valuesToSet = { TestEnum.C, TestEnum.B };
+				final List<Throwable> thrown = new ArrayList<>(valuesToSet.length);
+				for (final TestEnum valueToSet : valuesToSet) {
+					final CompletableFuture<?> asyncUpdate = CompletableFuture.runAsync(
+						() -> domainObject.setMyEnum(valueToSet))
+						.exceptionally(x -> {
+							thrown.add(x);
+							return null;
+						});
+
+					do {
+						SWTTestUtil.waitForUIThread();
+					} while (!asyncUpdate.isDone());
+				}
+
+				assertThat("Async update failed", thrown, not(hasItem(anything())));
+
+				// Got notified once for each async update
+				verify(listener, times(valuesToSet.length)).handleChange(any());
+			} catch (DatabindingFailedException | NoRendererFoundException | NoPropertyDescriptorFoundExeption e) {
+				sneakyThrow(e);
+			}
+		});
+	}
+
+	private static <X extends Exception> void sneakyThrow(Exception x) throws X {
+		throw (X) x;
+	}
+
 }