From 528fb9781f6fabf27381939223dc708bfc1e5899 Mon Sep 17 00:00:00 2001
From: Simon Barner <barner@fortiss.org>
Date: Tue, 4 Dec 2018 11:29:19 +0100
Subject: [PATCH] Enable editing of multi-valued EAttribute annotations

* Add new classes
  * MultiValueAnnotationTextEditingSupport, an editing support that
    allows for textual editing of multi-value annotation.
  * MultiValueAnnotationTextEditingDialog, a dialog to that allows to
    edit multiple values
* Adapt
  * EditingSupportFactory to instantiate
    MultiValueAnnotationTextEditingSupport instead of throwning an
    exception about an unsupported case
  * EStructuralFeatureValueProviderBase to set values using the new
    method setAnnotationValueFromString(Collection<String>, T)

Issue-Ref: 3578
Issue-Url: https://af3-developer.fortiss.org/issues/3578

Signed-off-by: Simon Barner <barner@fortiss.org>
---
 .../ui/annotation/editingsupport/.ratings     |   4 +-
 .../editingsupport/EditingSupportFactory.java |   4 +-
 ...MultiValueAnnotationTextEditingDialog.java | 244 ++++++++++++++++++
 ...ultiValueAnnotationTextEditingSupport.java |  54 ++++
 .../base/annotation/valueprovider/.ratings    |   2 +-
 .../EStructuralFeatureValueProviderBase.java  |  55 +++-
 6 files changed, 356 insertions(+), 7 deletions(-)
 create mode 100644 org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/MultiValueAnnotationTextEditingDialog.java
 create mode 100644 org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/MultiValueAnnotationTextEditingSupport.java

diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/.ratings b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/.ratings
index c98809460..5101c3fd2 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/.ratings
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/.ratings
@@ -1,7 +1,7 @@
 AnnotationEditingSupportBase.java a5ecf54616b50f947d251f45cbb5789df5234170 GREEN
 CheckBoxEditingSupport.java 1d8d9dd444f0e52767c65fd2711321438d3a9b29 GREEN
 ComboBoxEditingSupport.java 6b6a23be327ebdea9bdaf007304bd3d7c14b2cef GREEN
-EditingSupportFactory.java db05c9f4c1d48f9858827051b0e9e58214645c0a YELLOW
+EditingSupportFactory.java e42347692ef23a8cb71532edadcf176ae49e992d YELLOW
 ElementCommentEditingSupport.java 4be366924a040caf3f80e35b383e796e84aedcac GREEN
 ElementEditingSupportBase.java a6360f99ee149276f0fbd299820ebd1c9731ea97 GREEN
 ElementNameEditingSupport.java 0dcaecf4ba5f8ddefa3ccb7d6f4e4506f7f09b26 GREEN
@@ -12,4 +12,6 @@ MultiValueAnnotationDialogBase.java 910a7e1203f84e462d25d4063264fbd19b5ae22e YEL
 MultiValueAnnotationEditingSupportBase.java ac228c1a4dec5d7035729585c2dcb9799da6aba9 YELLOW
 MultiValueAnnotationSelectionDialog.java d5639aee0d5b4220d5dae92df0d87062dfe17bd6 YELLOW
 MultiValueAnnotationSelectionEditingSupport.java a4c3c3f9c3cc4f43f27c95f9ba676b4bef01e157 YELLOW
+MultiValueAnnotationTextEditingDialog.java 8d8ee46724f52175233e74ec7f0a5bed4f7d3636 YELLOW
+MultiValueAnnotationTextEditingSupport.java 38c780819396fc75d10fbb660832652d89d59378 YELLOW
 TextEditingSupport.java e761ea393816f23ca41157f2a9a9a8d8ef30b858 GREEN
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/EditingSupportFactory.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/EditingSupportFactory.java
index db05c9f4c..e42347692 100644
--- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/EditingSupportFactory.java
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/EditingSupportFactory.java
@@ -117,8 +117,8 @@ public class EditingSupportFactory {
 					specification, eStructuralFeatureDescriptor);
 		}
 
-		throw new Exception(
-				"For feature multiplicity > 1, EStructuralValueProvider has been implemented for EReferences and EEnumns, only .");
+		return new MultiValueAnnotationTextEditingSupport<String>(viewer, clazz, specification,
+				eStructuralFeatureDescriptor);
 	}
 
 	/**
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/MultiValueAnnotationTextEditingDialog.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/MultiValueAnnotationTextEditingDialog.java
new file mode 100644
index 000000000..8d8ee4672
--- /dev/null
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/MultiValueAnnotationTextEditingDialog.java
@@ -0,0 +1,244 @@
+/*-------------------------------------------------------------------------+
+| Copyright 2018 fortiss GmbH                                              |
+|                                                                          |
+| Licensed under the Apache License, Version 2.0 (the "License");          |
+| you may not use this file except in compliance with the License.         |
+| You may obtain a copy of the License at                                  |
+|                                                                          |
+|    http://www.apache.org/licenses/LICENSE-2.0                            |
+|                                                                          |
+| Unless required by applicable law or agreed to in writing, software      |
+| distributed under the License is distributed on an "AS IS" BASIS,        |
+| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+| See the License for the specific language governing permissions and      |
+| limitations under the License.                                           |
++--------------------------------------------------------------------------*/
+package org.fortiss.tooling.base.ui.annotation.editingsupport;
+
+import static java.lang.Integer.compare;
+import static java.lang.Math.min;
+import static java.util.Arrays.asList;
+import static java.util.Arrays.stream;
+
+import java.util.Collection;
+import java.util.OptionalInt;
+import java.util.stream.IntStream;
+
+import org.eclipse.emf.ecore.EAttribute;
+import org.eclipse.emf.ecore.EFactory;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SegmentEvent;
+import org.eclipse.swt.events.SegmentListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.List;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Dialog to that allows to edit multiple values
+ * 
+ * @author barner
+ */
+public class MultiValueAnnotationTextEditingDialog<T> extends MultiValueAnnotationDialogBase<T> {
+
+	/** The {@link Text} into which input is entered. */
+	private Text textInput;
+
+	/** {@link List} containing the current input. */
+	private List list;
+
+	/** {@link Button} to add the current value of {@link #textInput} to {@link #list}. */
+	private Button addButton;
+
+	/** {@link Button} to remove the currently selected item from {@link #list}. */
+	private Button removeButton;
+
+	/** {@link Button} to move up the currently selected item of {@link #list}. */
+	private Button upButton;
+
+	/** {@link Button} to move down the currently selected item of {@link #list}. */
+	private Button downButton;
+
+	/** Flag if dialog is currently set up in {@link #createDialogArea(Composite)}. */
+	private boolean isCreateDialogArea;
+
+	/** Underlying {@link EAttribute}. */
+	private EAttribute eAttribute;
+
+	/** The {@link Collection} of initial values. */
+	private Collection<T> initialValues;
+
+	/** Constructs a new {@link MultiValueAnnotationTextEditingDialog}. */
+	public MultiValueAnnotationTextEditingDialog(EAttribute eAttribute,
+			Collection<T> initialValues) {
+		super("Add", eAttribute.getLowerBound(), eAttribute.getUpperBound());
+		this.eAttribute = eAttribute;
+		this.initialValues = initialValues;
+	}
+
+	/** Returns the {@link EFactory} for the underlying {@link #eAttribute}. */
+	protected EFactory getEFactory() {
+		return eAttribute.getEType().getEPackage().getEFactoryInstance();
+	}
+
+	/** Refreshes the status of the dialog's UI elements. */
+	protected void refresh() {
+		if(isCreateDialogArea) {
+			return;
+		}
+
+		boolean isValidText = true;
+		try {
+			EFactory eFactory = getEFactory();
+			eFactory.createFromString(eAttribute.getEAttributeType(), textInput.getText());
+		} catch(Exception e) {
+			isValidText = false;
+		}
+
+		addButton.setEnabled(isValidText);
+
+		OptionalInt min = stream(list.getSelectionIndices()).reduce(Integer::min);
+		OptionalInt max = stream(list.getSelectionIndices()).reduce(Integer::max);
+		removeButton.setEnabled(min.isPresent());
+
+		upButton.setEnabled(min.isPresent() && min.getAsInt() > 0);
+		downButton.setEnabled(max.isPresent() && max.getAsInt() < list.getItemCount() - 1);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected Composite createDialogArea(Composite parent) {
+		isCreateDialogArea = true;
+		Composite area = (Composite)super.createDialogArea(parent);
+		area.setLayout(new GridLayout(2, false));
+
+		textInput = new Text(area, SWT.BORDER | SWT.LEFT);
+		GridData gd_Text = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1);
+		gd_Text.widthHint = 300;
+		gd_Text.minimumWidth = 300;
+		textInput.setLayoutData(gd_Text);
+		textInput.addSegmentListener(new SegmentListener() {
+			/** {@inheritDoc} */
+			@Override
+			public void getSegments(SegmentEvent event) {
+				refresh();
+			}
+		});
+
+		addButton = new Button(area, SWT.PUSH);
+		GridData gd_Buttons = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
+		gd_Buttons.widthHint = 100;
+		gd_Buttons.minimumWidth = 100;
+		addButton.setLayoutData(gd_Buttons);
+		addButton.setText("Add");
+
+		addButton.addSelectionListener(new SelectionAdapter() {
+			/** {@inheritDoc} */
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				list.add(textInput.getText());
+				textInput.setText("");
+				refresh();
+			}
+		});
+
+		list = new List(area, SWT.BORDER | SWT.LEFT | SWT.MULTI);
+		GridData gd_List = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 4);
+		gd_List.minimumWidth = 310;
+		gd_List.heightHint = 310;
+		list.setLayoutData(gd_List);
+		list.addSelectionListener(new SelectionAdapter() {
+			/** {@inheritDoc} */
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				refresh();
+			}
+		});
+		for(Object value : initialValues) {
+			EFactory eFactory = getEFactory();
+			String valueLabel = eFactory.convertToString(eAttribute.getEAttributeType(), value);
+			list.add(valueLabel);
+		}
+
+		removeButton = new Button(area, SWT.PUSH);
+		removeButton.setLayoutData(gd_Buttons);
+		removeButton.setText("Remove");
+		removeButton.addSelectionListener(new SelectionAdapter() {
+			/** {@inheritDoc} */
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				int[] selectionIndices = list.getSelectionIndices();
+				int index = -1;
+				if(selectionIndices.length == 1) {
+					index = selectionIndices[0];
+				}
+				for(int i : selectionIndices) {
+					list.remove(i);
+				}
+				list.setSelection(min(index, list.getItemCount()));
+				refresh();
+			}
+		});
+
+		upButton = new Button(area, SWT.PUSH);
+		upButton.setLayoutData(gd_Buttons);
+		upButton.setText("Up");
+		upButton.addSelectionListener(new SelectionAdapter() {
+			/** {@inheritDoc} */
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				stream(list.getSelectionIndices()).sorted().forEach(i -> {
+					String swap = list.getItem(i - 1);
+					list.setItem(i - 1, list.getItem(i));
+					list.setItem(i, swap);
+				});
+				IntStream oldIndices = stream(list.getSelectionIndices());
+				list.deselectAll();
+				list.select(oldIndices.map(i -> i - 1).toArray());
+				refresh();
+			}
+		});
+
+		downButton = new Button(area, SWT.PUSH);
+		downButton.setLayoutData(gd_Buttons);
+		downButton.setText("Down");
+		downButton.addSelectionListener(new SelectionAdapter() {
+			/** {@inheritDoc} */
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				stream(list.getSelectionIndices()).boxed().sorted((i1, i2) -> compare(i2, i1))
+						.forEach(i -> {
+							String swap = list.getItem(i + 1);
+							list.setItem(i + 1, list.getItem(i));
+							list.setItem(i, swap);
+						});
+				IntStream oldIndices = stream(list.getSelectionIndices());
+				list.deselectAll();
+				list.select(oldIndices.map(i -> i + 1).toArray());
+				refresh();
+			}
+		});
+
+		isCreateDialogArea = false;
+		refresh();
+		return area;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void create() {
+		super.create();
+		getShell().setDefaultButton(addButton);
+	}
+
+	/** {@inheritDoc} */
+	@SuppressWarnings("unchecked")
+	@Override
+	protected Collection<T> getElementsFromDialog() {
+		return (Collection<T>)asList(list.getItems());
+	}
+}
diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/MultiValueAnnotationTextEditingSupport.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/MultiValueAnnotationTextEditingSupport.java
new file mode 100644
index 000000000..38c780819
--- /dev/null
+++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/annotation/editingsupport/MultiValueAnnotationTextEditingSupport.java
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------+
+| Copyright 2018 fortiss GmbH                                              |
+|                                                                          |
+| Licensed under the Apache License, Version 2.0 (the "License");          |
+| you may not use this file except in compliance with the License.         |
+| You may obtain a copy of the License at                                  |
+|                                                                          |
+|    http://www.apache.org/licenses/LICENSE-2.0                            |
+|                                                                          |
+| Unless required by applicable law or agreed to in writing, software      |
+| distributed under the License is distributed on an "AS IS" BASIS,        |
+| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+| See the License for the specific language governing permissions and      |
+| limitations under the License.                                           |
++--------------------------------------------------------------------------*/
+package org.fortiss.tooling.base.ui.annotation.editingsupport;
+
+import java.util.Collection;
+
+import org.eclipse.emf.ecore.EAttribute;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.EditingSupport;
+import org.eclipse.swt.widgets.Composite;
+import org.fortiss.tooling.base.annotation.valueprovider.EStructuralFeatureDescriptor;
+import org.fortiss.tooling.base.model.element.IAnnotatedSpecification;
+import org.fortiss.tooling.base.model.element.IModelElement;
+
+/**
+ * {@link EditingSupport} for textual editing of multi-value annotations.
+ * 
+ * @author barner
+ */
+public class MultiValueAnnotationTextEditingSupport<T>
+		extends MultiValueAnnotationEditingSupportBase<T> {
+
+	/** Constructs a new {@link MultiValueAnnotationTextEditingSupport}. */
+	public MultiValueAnnotationTextEditingSupport(ColumnViewer viewer,
+			Class<? extends IAnnotatedSpecification> clazz, IAnnotatedSpecification specification,
+			EStructuralFeatureDescriptor eStructuralFeatureDescriptor) {
+		super(viewer, clazz, specification, eStructuralFeatureDescriptor);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IMultiValueAnnotationDialog<T> createMultiEditingDialog(Composite parent,
+			IModelElement modelElement, Collection<T> values) {
+
+		EStructuralFeature eStructuralFeature =
+				eStructuralFeatureDescriptor.getEStructuralFeature(specification);
+
+		return new MultiValueAnnotationTextEditingDialog<T>((EAttribute)eStructuralFeature, values);
+	}
+}
diff --git a/org.fortiss.tooling.base/src/org/fortiss/tooling/base/annotation/valueprovider/.ratings b/org.fortiss.tooling.base/src/org/fortiss/tooling/base/annotation/valueprovider/.ratings
index 8a68adfa5..f5eb48ac6 100644
--- a/org.fortiss.tooling.base/src/org/fortiss/tooling/base/annotation/valueprovider/.ratings
+++ b/org.fortiss.tooling.base/src/org/fortiss/tooling/base/annotation/valueprovider/.ratings
@@ -1,6 +1,6 @@
 AnnotationInstSpec.java b4f2ed47a8984e751e04049de5bdb3cad2c0a933 GREEN
 DerivedAnnotationValueProviderBase.java 15da44b7b92b7fd351aa48422ff5957a2ce34e35 GREEN
 EStructuralFeatureDescriptor.java 2e14df3830d854bc1693382727b2033b23d0051c GREEN
-EStructuralFeatureValueProviderBase.java 1077fa87bfb21dc3041a1695a4247b50b0409fd1 YELLOW
+EStructuralFeatureValueProviderBase.java 7e3f41a7e5c22fda63058fb5cd1c8036df0e8a3f YELLOW
 IAnnotationValueProvider.java d093cb522e7c484420331f0c77690bebe7e131b4 GREEN
 ValueProviderBase.java e4e866840845346ec99a4304048f5327c4890996 GREEN
diff --git a/org.fortiss.tooling.base/src/org/fortiss/tooling/base/annotation/valueprovider/EStructuralFeatureValueProviderBase.java b/org.fortiss.tooling.base/src/org/fortiss/tooling/base/annotation/valueprovider/EStructuralFeatureValueProviderBase.java
index 1077fa87b..7e3f41a7e 100644
--- a/org.fortiss.tooling.base/src/org/fortiss/tooling/base/annotation/valueprovider/EStructuralFeatureValueProviderBase.java
+++ b/org.fortiss.tooling.base/src/org/fortiss/tooling/base/annotation/valueprovider/EStructuralFeatureValueProviderBase.java
@@ -15,9 +15,14 @@
 +--------------------------------------------------------------------------*/
 package org.fortiss.tooling.base.annotation.valueprovider;
 
+import static java.util.Collections.emptyList;
+import static org.fortiss.tooling.kernel.utils.LoggingUtils.error;
+
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
+import org.eclipse.emf.common.util.BasicEList;
 import org.eclipse.emf.common.util.EList;
 import org.eclipse.emf.ecore.EAttribute;
 import org.eclipse.emf.ecore.EClass;
@@ -25,6 +30,7 @@ import org.eclipse.emf.ecore.EDataType;
 import org.eclipse.emf.ecore.EFactory;
 import org.eclipse.emf.ecore.EReference;
 import org.eclipse.emf.ecore.EStructuralFeature;
+import org.fortiss.tooling.base.ToolingBaseActivator;
 import org.fortiss.tooling.base.annotation.valueprovider.EStructuralFeatureDescriptor.EReferenceScope;
 import org.fortiss.tooling.base.model.element.IAnnotatedSpecification;
 import org.fortiss.tooling.base.model.element.IModelElement;
@@ -161,7 +167,39 @@ public abstract class EStructuralFeatureValueProviderBase<T extends IAnnotatedSp
 			}
 		} else {
 			throw new Exception(
-					"setAnnotationValueFromString() is only availabe for EAttributes. Use setAnnotationValue(U value, T specification) instead.");
+					"setAnnotationValueFromString(String, T) is only availabe for EAttributes. Use setAnnotationValue(U value, T specification) instead.");
+		}
+	}
+
+	/**
+	 * Sets a value for a {@link IAnnotatedSpecification} from a {@code Collection<String>}
+	 * representation of the input.
+	 */
+	private void setAnnotationValueFromString(Collection<String> collection, T specification)
+			throws Exception {
+		EStructuralFeature structuralFeature =
+				getEStructuralFeatureDescriptor().getEStructuralFeature(specification);
+
+		if(structuralFeature instanceof EAttribute) {
+			EFactory eFactory = getEFactory(structuralFeature);
+			EDataType eAttributeType = ((EAttribute)structuralFeature).getEAttributeType();
+			EList<Object> values = new BasicEList<>();
+			for(String s : collection) {
+				Object o = null;
+				try {
+					o = eFactory.createFromString(eAttributeType, s);
+				} catch(Exception e) {
+					error(ToolingBaseActivator.getDefault(), "Could not convert " +
+							(s != null ? s : "<null>") + " to datatype " + eAttributeType + ".", e);
+				}
+				if(o != null) {
+					values.add(o);
+				}
+			}
+			specification.eSet(structuralFeature, values);
+		} else {
+			throw new Exception(
+					"setAnnotationValueFromString(Collection<String>, T) is only availabe for EAttributes.");
 		}
 	}
 
@@ -175,6 +213,8 @@ public abstract class EStructuralFeatureValueProviderBase<T extends IAnnotatedSp
 	@SuppressWarnings("unchecked")
 	@Override
 	public <U> void setAnnotationValue(U value, T specification) throws Exception {
+		final EStructuralFeature eStructuralFeature =
+				getEStructuralFeatureDescriptor().getEStructuralFeature(specification);
 		if(value instanceof String) {
 			// In case the input choice is presented in dynamically managed combo box (i.e., where
 			// new value choices can be entered by the user), the empty String indicates that the
@@ -190,9 +230,18 @@ public abstract class EStructuralFeatureValueProviderBase<T extends IAnnotatedSp
 			}
 
 			setAnnotationValueFromString((String)value, specification);
+		} else if(value instanceof Collection<?>) {
+			Collection<?> collection = (Collection<?>)value;
+			if(collection.isEmpty()) {
+				specification.eSet(eStructuralFeature, emptyList());
+			} else {
+				if(collection.iterator().next() instanceof String) {
+					setAnnotationValueFromString((Collection<String>)value, specification);
+				} else {
+					specification.eSet(eStructuralFeature, value);
+				}
+			}
 		} else {
-			final EStructuralFeature eStructuralFeature =
-					getEStructuralFeatureDescriptor().getEStructuralFeature(specification);
 			if(value != null) {
 				specification.eSet(eStructuralFeature, value);
 			} else {
-- 
GitLab