Added operation to delete trace links

This patch adds an operation to delete trace links which can be undone
and redone as necessary.

Change-Id: Ie9a37b6c72e0c34cf0b4507342635c6503b58b25
diff --git a/bundles/org.eclipse.capra.ui/src/org/eclipse/capra/ui/operations/DeleteTraceOperation.java b/bundles/org.eclipse.capra.ui/src/org/eclipse/capra/ui/operations/DeleteTraceOperation.java
new file mode 100644
index 0000000..a2bee3b
--- /dev/null
+++ b/bundles/org.eclipse.capra.ui/src/org/eclipse/capra/ui/operations/DeleteTraceOperation.java
@@ -0,0 +1,96 @@
+package org.eclipse.capra.ui.operations;
+
+import java.util.List;
+
+import org.eclipse.capra.core.adapters.Connection;
+import org.eclipse.capra.core.adapters.TraceMetaModelAdapter;
+import org.eclipse.capra.core.adapters.TracePersistenceAdapter;
+import org.eclipse.capra.core.helpers.EditingDomainHelper;
+import org.eclipse.capra.core.helpers.ExtensionPointHelper;
+import org.eclipse.capra.core.helpers.TraceHelper;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.commands.operations.AbstractOperation;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Operation to delete trace links. Also allows to undo and redo the operation.
+ * 
+ * @author Jan-Philipp Steghöfer
+ *
+ */
+public class DeleteTraceOperation extends AbstractOperation {
+
+	private static final String ERROR_DIALOG_TITLE = "Error deleting trace link";
+	private static final String EXCEPTION_MESSAGE_RUNTIME_EXCEPTION = "An exception occured during the deletion of the trace link.";
+
+	private Connection connection;
+
+	/**
+	 * Creates a new operation to delete trace links.
+	 * 
+	 * @param label      the label used for the operation. Should never be
+	 *                   <code>null</code>.
+	 * @param connection the connection representing the trace link that should be
+	 *                   deleted.
+	 */
+	public DeleteTraceOperation(String label, Connection connection) {
+		super(label);
+		this.connection = connection;
+	}
+
+	@Override
+	public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
+		Shell shell = info.getAdapter(Shell.class);
+		IStatus executionStatus = null;
+
+		try {
+			deleteTrace();
+			executionStatus = Status.OK_STATUS;
+		} catch (IllegalStateException e) {
+			executionStatus = new Status(Status.ERROR, OperationsHelper.PLUGIN_ID, e.getMessage(), e);
+			OperationsHelper.createErrorMessage(shell, executionStatus, ERROR_DIALOG_TITLE, e.getMessage());
+		} catch (RuntimeException e) {
+			executionStatus = new Status(Status.ERROR, OperationsHelper.PLUGIN_ID, e.getMessage(), e);
+			OperationsHelper.createErrorMessage(shell, executionStatus, ERROR_DIALOG_TITLE,
+					EXCEPTION_MESSAGE_RUNTIME_EXCEPTION);
+		}
+		return executionStatus;
+
+	}
+
+	@Override
+	public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
+		return execute(monitor, info);
+	}
+
+	@Override
+	public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
+		TracePersistenceAdapter persistenceAdapter = ExtensionPointHelper.getTracePersistenceAdapter().orElseThrow();
+
+		ResourceSet resourceSet = EditingDomainHelper.getResourceSet();
+		EObject traceModel = persistenceAdapter.getTraceModel(resourceSet);
+		TraceHelper traceHelper = new TraceHelper(traceModel);
+		traceHelper.createTrace(connection.getOrigins(), connection.getTargets(), connection.getTlink().eClass());
+		return Status.OK_STATUS;
+	}
+
+	/**
+	 * Deletes the trace stored in the {@code connection} field.
+	 */
+	private void deleteTrace() {
+		TraceMetaModelAdapter traceAdapter = ExtensionPointHelper.getTraceMetamodelAdapter().get();
+		TracePersistenceAdapter persistenceAdapter = ExtensionPointHelper.getTracePersistenceAdapter().get();
+		ResourceSet resourceSet = EditingDomainHelper.getResourceSet();
+		EObject traceModel = persistenceAdapter.getTraceModel(resourceSet);
+		EObject artifactModel = persistenceAdapter.getArtifactWrappers(resourceSet);
+		traceAdapter.deleteTrace(List.of(connection), traceModel);
+		persistenceAdapter.saveTracesAndArtifacts(traceModel, artifactModel);
+	}
+
+}
diff --git a/tests/org.eclipse.capra.testsuite/src/org/eclipse/capra/testsuite/TestDeleteTraceOperation.java b/tests/org.eclipse.capra.testsuite/src/org/eclipse/capra/testsuite/TestDeleteTraceOperation.java
new file mode 100644
index 0000000..8b742af
--- /dev/null
+++ b/tests/org.eclipse.capra.testsuite/src/org/eclipse/capra/testsuite/TestDeleteTraceOperation.java
@@ -0,0 +1,161 @@
+package org.eclipse.capra.testsuite;
+
+import static org.eclipse.capra.testsupport.TestHelper.clearWorkspace;
+import static org.eclipse.capra.testsupport.TestHelper.createEClassInEPackage;
+import static org.eclipse.capra.testsupport.TestHelper.createEcoreModel;
+import static org.eclipse.capra.testsupport.TestHelper.createSimpleProject;
+import static org.eclipse.capra.testsupport.TestHelper.getProject;
+import static org.eclipse.capra.testsupport.TestHelper.load;
+import static org.eclipse.capra.testsupport.TestHelper.projectExists;
+import static org.eclipse.capra.testsupport.TestHelper.purgeModels;
+import static org.eclipse.capra.testsupport.TestHelper.resetSelectionView;
+import static org.eclipse.capra.testsupport.TestHelper.save;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.capra.core.adapters.Connection;
+import org.eclipse.capra.core.adapters.TracePersistenceAdapter;
+import org.eclipse.capra.core.helpers.ArtifactHelper;
+import org.eclipse.capra.core.helpers.EditingDomainHelper;
+import org.eclipse.capra.core.helpers.ExtensionPointHelper;
+import org.eclipse.capra.core.helpers.TraceHelper;
+import org.eclipse.capra.generic.tracemodel.TracemodelPackage;
+import org.eclipse.capra.testsupport.TestHelper;
+import org.eclipse.capra.ui.operations.CreateTraceOperation;
+import org.eclipse.capra.ui.operations.DeleteTraceOperation;
+import org.eclipse.capra.ui.views.SelectionView;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.commands.operations.IOperationHistory;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.PlatformUI;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestDeleteTraceOperation {
+
+	private static final String TEST_NAME_UPDATED = "test-name-updated";
+	private static final String CLASS_A_NAME = "A";
+	private static final String CLASS_B_NAME = "B";
+	private static final String CLASS_C_NAME = "C";
+
+	private static final String MODEL_A_FILENAME = "modelA.ecore";
+	private static final String MODEL_B_FILENAME = "modelB.ecore";
+	private static final String MODEL_A_NAME = "modelA";
+	private static final String MODEL_B_NAME = "modelB";
+
+	private static final String TEST_PROJECT_NAME = "TestProject";
+
+	@Before
+	public void init() throws CoreException {
+		clearWorkspace();
+		resetSelectionView();
+		purgeModels();
+	}
+
+	/**
+	 * Tests the creation of a trace link and its subsequent deletion using the
+	 * operations defined in {@code org.eclipse.capra.ui.operations}.
+	 * 
+	 * @throws CoreException
+	 * @throws IOException
+	 */
+	@Test
+	public void testDeleteTrace() throws CoreException, IOException {
+		// Create a project
+		createSimpleProject(TEST_PROJECT_NAME);
+		assertTrue(projectExists(TEST_PROJECT_NAME));
+
+		// Create two models and persist them
+		IProject testProject = getProject(TEST_PROJECT_NAME);
+		EPackage a = TestHelper.createEcoreModel(MODEL_A_NAME);
+		createEClassInEPackage(a, CLASS_A_NAME);
+		save(testProject, a);
+
+		EPackage b = createEcoreModel(MODEL_B_NAME);
+		createEClassInEPackage(b, CLASS_B_NAME);
+		createEClassInEPackage(b, CLASS_C_NAME);
+		save(testProject, b);
+
+		// Load them, choose two elements
+		ResourceSet rs = new ResourceSetImpl();
+
+		EPackage _a = load(testProject, MODEL_A_FILENAME, rs);
+		assertEquals(_a.getName(), MODEL_A_NAME);
+		EClass _A = (EClass) _a.getEClassifier(CLASS_A_NAME);
+
+		EPackage _b = load(testProject, MODEL_B_FILENAME, rs);
+		assertEquals(_b.getName(), MODEL_B_NAME);
+		EClass _B = (EClass) _b.getEClassifier(CLASS_B_NAME);
+		EClass _C = (EClass) _b.getEClassifier(CLASS_C_NAME);
+
+		// Add them to the selection view
+		SelectionView.getOpenedView().dropToSelection(_A);
+		SelectionView.getOpenedView().dropToSelection(_B);
+
+		EClass traceType = TracemodelPackage.eINSTANCE.getRelatedTo();
+
+		IAdaptable adapter = SelectionView.getOpenedView().getSite();
+
+		IWorkbench workbench = PlatformUI.getWorkbench();
+		IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
+
+		CreateTraceOperation createTraceOperation = TestHelper
+				.prepareCreateTraceOperationForCurrentSelectionOfType(traceType);
+		try {
+			assertEquals(operationHistory.execute(createTraceOperation, null, adapter), Status.OK_STATUS);
+		} catch (ExecutionException e) {
+			fail("Could not create trace: ExecutionException in operation");
+		}
+
+		TracePersistenceAdapter persistenceAdapter = ExtensionPointHelper.getTracePersistenceAdapter().get();
+		TraceHelper traceHelper = new TraceHelper(
+				persistenceAdapter.getTraceModel(EditingDomainHelper.getResourceSet()));
+		EObject artifactModel = persistenceAdapter.getArtifactWrappers(_A.eResource().getResourceSet());
+		ArtifactHelper artifactHelper = new ArtifactHelper(artifactModel);
+		List<EObject> selection = artifactHelper.createWrappers(SelectionView.getOpenedView().getSelection());
+
+		// Check that the trace between A and B exists
+		traceHelper = new TraceHelper(persistenceAdapter.getTraceModel(EditingDomainHelper.getResourceSet()));
+		selection = artifactHelper.createWrappers(SelectionView.getOpenedView().getSelection());
+		assertTrue(traceHelper.traceExists(selection, traceType));
+
+		List<Connection> traces = traceHelper.getTraces(Arrays.asList(new EClass[] { _A, _B }));
+		assertFalse(traces.isEmpty());
+
+		DeleteTraceOperation deleteOp = new DeleteTraceOperation("Delete trace", traces.get(0));
+		try {
+			assertEquals(operationHistory.execute(deleteOp, null, adapter), Status.OK_STATUS);
+		} catch (ExecutionException e) {
+			fail("Could not delete trace: ExecutionException in operation");
+		}
+
+		List<Connection> tracesAfterDelete = traceHelper.getTraces(Arrays.asList(new EClass[] { _A, _B }));
+		assertTrue(tracesAfterDelete.isEmpty());
+
+		try {
+			assertEquals(operationHistory.undoOperation(deleteOp, null, adapter), Status.OK_STATUS);
+		} catch (ExecutionException e) {
+			fail("Could not undo trace deletion: ExecutionException in operation");
+		}
+
+		List<Connection> tracesAfterUndo = traceHelper.getTraces(Arrays.asList(new EClass[] { _A, _B }));
+		assertFalse(tracesAfterUndo.isEmpty());
+
+	}
+
+}