Bug 148327 - Add factory methods for enum converters

And use it in snippet.

Change-Id: I99ec7510c035cc07c68067570e0ead5bd7a7449d
Signed-off-by: Jens Lidestrom <jens@lidestrom.se>
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.ui/+/155307
Tested-by: Platform Bot <platform-bot@eclipse.org>
Tested-by: Lars Vogel <Lars.Vogel@vogella.com>
Reviewed-by: Lars Vogel <Lars.Vogel@vogella.com>
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/conversion/EnumConverters.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/conversion/EnumConverters.java
new file mode 100644
index 0000000..e837b4d
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/conversion/EnumConverters.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Jens Lidestrom and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Jens Lidestrom - initial API and implementation (Bug 148327)
+ ******************************************************************************/
+
+package org.eclipse.core.databinding.conversion;
+
+import java.util.Objects;
+
+/**
+ * Contains static methods the create converters for working with {@link Enum}s.
+ *
+ * @since 1.11
+ */
+public class EnumConverters {
+	/**
+	 * Creates a converter which converts from {@link Enum#ordinal}s to enum values
+	 * of the given type. Invalid ordinal values are converted to {@code null}.
+	 *
+	 * @param enumToType to type; not null
+	 * @return the created converter
+	 */
+	public static <T extends Enum<T>> IConverter<Integer, T> fromOrdinal(Class<T> enumToType) {
+		Objects.requireNonNull(enumToType);
+		T[] ordinals = enumToType.getEnumConstants();
+		return IConverter.create(Integer.class, enumToType,
+				i -> i == null || i < 0 || i >= ordinals.length ? null : ordinals[i]);
+	}
+
+	/**
+	 * Creates a converter which converts from the {@link #toString} values of enums
+	 * values to enum values themselves. Invalid string values are converted to
+	 * {@code null}.
+	 *
+	 * @param enumToType to type; not null
+	 * @return the created converter
+	 */
+	public static <T extends Enum<T>> IConverter<String, T> fromString(Class<T> enumToType) {
+		Objects.requireNonNull(enumToType);
+		return IConverter.create(String.class, enumToType, text -> {
+			if (text == null) {
+				return null;
+			}
+
+			try {
+				return Enum.valueOf(enumToType, text);
+			} catch (IllegalArgumentException e) {
+				return null;
+			}
+		});
+	}
+
+	/**
+	 * Creates a converter which converts from {@link Enum#ordinal}s to enum values
+	 * of the given type. {@code null} in the converter input is converted to
+	 * {@code null}.
+	 *
+	 * @param enumFromType from type; not null
+	 * @return the created converter
+	 */
+	public static <T extends Enum<T>> IConverter<T, Integer> toOrdinal(Class<T> enumFromType) {
+		Objects.requireNonNull(enumFromType);
+		return IConverter.create(enumFromType, Integer.class, e -> e == null ? null : e.ordinal());
+	}
+
+	/**
+	 * Creates a converter which converts to the {@link #toString} values of the
+	 * enum constants. {@code null} in the converter input is converted to
+	 * {@code null}.
+	 *
+	 * @param enumFromType from type; not null
+	 * @return the created converter
+	 */
+	public static <T extends Enum<T>> IConverter<T, String> toString(Class<T> enumFromType) {
+		Objects.requireNonNull(enumFromType);
+		return IConverter.create(enumFromType, String.class, e -> e == null ? null : e.toString());
+	}
+}
diff --git a/examples/org.eclipse.jface.examples.databinding/META-INF/MANIFEST.MF b/examples/org.eclipse.jface.examples.databinding/META-INF/MANIFEST.MF
index d5a1436..aff2fe3 100644
--- a/examples/org.eclipse.jface.examples.databinding/META-INF/MANIFEST.MF
+++ b/examples/org.eclipse.jface.examples.databinding/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.jface.examples.databinding
-Bundle-Version: 1.2.100.qualifier
+Bundle-Version: 1.3.0.qualifier
 Eclipse-BundleShape: dir
 Bundle-ClassPath: .
 Bundle-Vendor: %providerName
diff --git a/examples/org.eclipse.jface.examples.databinding/src/org/eclipse/jface/examples/databinding/snippets/Snippet034ComboViewerAndEnum.java b/examples/org.eclipse.jface.examples.databinding/src/org/eclipse/jface/examples/databinding/snippets/Snippet034ComboViewerAndEnum.java
index 0248f30..816aff8 100644
--- a/examples/org.eclipse.jface.examples.databinding/src/org/eclipse/jface/examples/databinding/snippets/Snippet034ComboViewerAndEnum.java
+++ b/examples/org.eclipse.jface.examples.databinding/src/org/eclipse/jface/examples/databinding/snippets/Snippet034ComboViewerAndEnum.java
@@ -15,8 +15,14 @@
 
 package org.eclipse.jface.examples.databinding.snippets;
 
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
 import org.eclipse.core.databinding.DataBindingContext;
+import org.eclipse.core.databinding.UpdateValueStrategy;
+import org.eclipse.core.databinding.beans.typed.BeanProperties;
 import org.eclipse.core.databinding.beans.typed.PojoProperties;
+import org.eclipse.core.databinding.conversion.EnumConverters;
 import org.eclipse.core.databinding.observable.Realm;
 import org.eclipse.jface.databinding.swt.DisplayRealm;
 import org.eclipse.jface.databinding.swt.typed.WidgetProperties;
@@ -26,6 +32,7 @@
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.RowLayout;
 import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Text;
 
@@ -73,8 +80,9 @@
 	 * need to implement the JavaBeans property change listener methods.
 	 */
 	static class Person {
-		String name;
-		Gender gender;
+		private String name;
+		private Gender gender;
+		private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
 
 		public Person(String name, Gender gender) {
 			this.name = name;
@@ -85,8 +93,10 @@
 			return name;
 		}
 
-		public void setName(String name) {
-			this.name = name;
+		public void setName(String newName) {
+			String old = this.name;
+			this.name = newName;
+			propertyChangeSupport.firePropertyChange("name", old, name);
 		}
 
 		public Gender getGender() {
@@ -94,8 +104,19 @@
 		}
 
 		public void setGender(Gender newGender) {
+			Gender old = this.gender;
 			this.gender = newGender;
+			propertyChangeSupport.firePropertyChange("gender", old, gender);
 		}
+
+		public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+			propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
+		}
+
+		public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+			propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
+		}
+
 	}
 
 	/** The GUI view. */
@@ -103,6 +124,7 @@
 		private Person viewModel;
 		private Text name;
 		private ComboViewer gender;
+		private Label genderText;
 
 		public View(Person viewModel) {
 			this.viewModel = viewModel;
@@ -120,6 +142,7 @@
 
 			name = new Text(shell, SWT.BORDER);
 			gender = new ComboViewer(shell, SWT.READ_ONLY);
+			genderText = new Label(shell, SWT.NONE);
 
 			// Here's the first key to binding a combo to an Enum:
 			// First give it an ArrayContentProvider,
@@ -138,10 +161,15 @@
 			bindingContext.bindValue(ViewerProperties.singleSelection(Gender.class).observe(gender),
 					PojoProperties.value(Person.class, "gender").observe(viewModel));
 
+			// The EnumConverters class is convenient when binding an enum in a situation
+			// where a Viewer can not be used
+			bindingContext.bindValue(WidgetProperties.text().observe(genderText),
+					BeanProperties.value(Person.class, "gender", Gender.class).observe(viewModel), null,
+					UpdateValueStrategy.create(EnumConverters.toString(Gender.class)));
+
 			shell.pack();
 			shell.open();
 			return shell;
 		}
 	}
-
 }
diff --git a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/EnumConvertersTest.java b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/EnumConvertersTest.java
new file mode 100644
index 0000000..8d242f7
--- /dev/null
+++ b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/EnumConvertersTest.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Jens Lidestrom and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Jens Lidestrom - initial API and implementation (Bug 558842)
+ ******************************************************************************/
+
+package org.eclipse.core.tests.internal.databinding;
+
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.core.databinding.conversion.EnumConverters;
+import org.eclipse.core.databinding.conversion.IConverter;
+import org.junit.Test;
+
+/**
+ * Test for {@link EnumConverters}.
+ */
+public class EnumConvertersTest {
+
+	enum TestEnum {
+		A, B;
+
+		@Override
+		public String toString() {
+			// To be able to tell apart from the enum name
+			return name().toLowerCase();
+		}
+	}
+
+	@Test
+	public void testFromOrdinal() {
+		IConverter<Integer, TestEnum> fromOrdinal = EnumConverters.fromOrdinal(TestEnum.class);
+		assertEquals(TestEnum.A, fromOrdinal.convert(0));
+		assertEquals(null, fromOrdinal.convert(null));
+		assertEquals(null, fromOrdinal.convert(100));
+		assertEquals(Integer.class, fromOrdinal.getFromType());
+		assertEquals(TestEnum.class, fromOrdinal.getToType());
+	}
+
+	@Test
+	public void testFromString() {
+		IConverter<String, TestEnum> fromOrdinal = EnumConverters.fromString(TestEnum.class);
+		assertEquals(TestEnum.A, fromOrdinal.convert("A"));
+		assertEquals(null, fromOrdinal.convert("a"));
+		assertEquals(null, fromOrdinal.convert(null));
+		assertEquals(String.class, fromOrdinal.getFromType());
+		assertEquals(TestEnum.class, fromOrdinal.getToType());
+	}
+
+	@Test
+	public void testToString() {
+		IConverter<TestEnum, String> fromOrdinal = EnumConverters.toString(TestEnum.class);
+		assertEquals("a", fromOrdinal.convert(TestEnum.A));
+		assertEquals(null, fromOrdinal.convert(null));
+		assertEquals(TestEnum.class, fromOrdinal.getFromType());
+		assertEquals(String.class, fromOrdinal.getToType());
+	}
+
+	@Test
+	public void testToOrdinal() {
+		IConverter<TestEnum, Integer> converter = EnumConverters.toOrdinal(TestEnum.class);
+		assertEquals(0, (int) converter.convert(TestEnum.A));
+		assertEquals(null, converter.convert(null));
+		assertEquals(TestEnum.class, converter.getFromType());
+		assertEquals(Integer.class, converter.getToType());
+	}
+}