Bug 546777 - Workbench opens closed/moved fragment on restart

Change-Id: I6f36b451727c31984b8c2aaf09145f71db4c6c61
Signed-off-by: Stefan Nöbauer <stefan.noebauer@kgu-consulting.com>
diff --git a/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/ModelAssembler.java b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/ModelAssembler.java
index 97c958e..6d8db66 100644
--- a/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/ModelAssembler.java
+++ b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/ModelAssembler.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2010, 2018 BestSolution.at and others.
+ * Copyright (c) 2010, 2019 BestSolution.at and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -14,6 +14,7 @@
  *     René Brandstetter - Bug 419749
  *     Brian de Alwis (MTI) - Bug 433053
  *     Alexandra Buzila - Refactoring, Bug 475934
+ *     Stefan Noebauer - Bug 546777
  ******************************************************************************/
 
 package org.eclipse.e4.ui.internal.workbench;
@@ -121,7 +122,7 @@
 	 * @param initial    <code>true</code> if running from a non-persisted state
 	 *
 	 */
-	private void processFragments(IExtension[] extensions, boolean initial) {
+	public void processFragments(IExtension[] extensions, boolean initial) {
 		List<ModelFragmentWrapper> wrappers = new ArrayList<>();
 		for (IExtension extension : extensions) {
 			IConfigurationElement[] ces = extension.getConfigurationElements();
@@ -334,12 +335,15 @@
 			return new ArrayList<>();
 		}
 
+		List<MApplicationElement> existingElements = new ArrayList<>();
+
 		for (MApplicationElement el : elements) {
 			EObject o = (EObject) el;
 
 			E4XMIResource r = (E4XMIResource) o.eResource();
 
 			if (checkExist && applicationResource.getIDToEObjectMap().containsKey(r.getID(o))) {
+				existingElements.add(el);
 				continue;
 			}
 
@@ -360,6 +364,8 @@
 				applicationResource.setID(eObj, r.getInternalId(eObj));
 			}
 		}
+		// remove all existing Elements including all children.
+		fragment.getElements().removeAll(existingElements);
 
 		return fragment.merge(application);
 	}
diff --git a/tests/org.eclipse.e4.ui.tests/data/ModelAssembler/NotExistsNonInitalFragmet.e4xmi b/tests/org.eclipse.e4.ui.tests/data/ModelAssembler/NotExistsNonInitalFragmet.e4xmi
new file mode 100644
index 0000000..99b1f95
--- /dev/null
+++ b/tests/org.eclipse.e4.ui.tests/data/ModelAssembler/NotExistsNonInitalFragmet.e4xmi
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="ASCII"?>

+<fragment:ModelFragments xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:basic="http://www.eclipse.org/ui/2010/UIModel/application/ui/basic" xmlns:fragment="http://www.eclipse.org/ui/2010/UIModel/fragment" xmi:id="_hpfZ0NsLEeWEUpR9iKVK6Q">

+  <imports xsi:type="basic:PartStack" xmi:id="_Y0PC0H1YEemug7TLPAdneg" elementId="partStackBottom"/>

+  <fragments xsi:type="fragment:StringModelFragment" xmi:id="_OETVYH1YEemug7TLPAdneg" featurename="children" parentElementId="partStackBottom">

+    <elements xsi:type="basic:Part" xmi:id="_gXKowH1YEemug7TLPAdneg" elementId="part2Id"/>

+    <elements xsi:type="basic:Part" xmi:id="_KjQaEH1kEemug7TLPAdneg" elementId="part3Id"/>

+  </fragments>

+</fragment:ModelFragments>

diff --git a/tests/org.eclipse.e4.ui.tests/data/ModelAssembler/fragment_initial.xml b/tests/org.eclipse.e4.ui.tests/data/ModelAssembler/fragment_initial.xml
new file mode 100644
index 0000000..26e8bad
--- /dev/null
+++ b/tests/org.eclipse.e4.ui.tests/data/ModelAssembler/fragment_initial.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<plugin>
+	<extension id="id1" point="org.eclipse.e4.workbench.model">
+		<fragment apply="initial"
+			uri="data/ModelAssembler/NotExistsInitalFragmet.e4xmi">
+		</fragment>
+	</extension>
+</plugin>
\ No newline at end of file
diff --git a/tests/org.eclipse.e4.ui.tests/data/ModelAssembler/fragment_notexists.xml b/tests/org.eclipse.e4.ui.tests/data/ModelAssembler/fragment_notexists.xml
new file mode 100644
index 0000000..1548ec5
--- /dev/null
+++ b/tests/org.eclipse.e4.ui.tests/data/ModelAssembler/fragment_notexists.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<plugin>
+	<extension id="id1" point="org.eclipse.e4.workbench.model">
+		<fragment apply="notexists"
+			uri="data/ModelAssembler/NotExistsNonInitalFragmet.e4xmi">
+		</fragment>
+	</extension>
+</plugin>
\ No newline at end of file
diff --git a/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/ModelAssemblerTests.java b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/ModelAssemblerTests.java
index 7095eda..2d3909d 100644
--- a/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/ModelAssemblerTests.java
+++ b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/ModelAssemblerTests.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2016, 2019 EclipseSource Muenchen GmbH and others.
  *
  *
  * This program and the accompanying materials
@@ -11,10 +11,14 @@
  *
  * Contributors:
  * Alexandra Buzila - initial API and implementation
+ * Stefan Noebauer - Bug 546777
  ******************************************************************************/
 
 package org.eclipse.e4.ui.tests.workbench;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.hasSize;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
@@ -47,6 +51,8 @@
 import org.eclipse.e4.ui.model.application.ui.advanced.MArea;
 import org.eclipse.e4.ui.model.application.ui.advanced.MPlaceholder;
 import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.model.application.ui.basic.MPartSashContainer;
+import org.eclipse.e4.ui.model.application.ui.basic.MPartStack;
 import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow;
 import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
 import org.eclipse.e4.ui.model.fragment.MFragmentFactory;
@@ -61,7 +67,6 @@
 import org.eclipse.emf.ecore.resource.URIConverter;
 import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 
 @SuppressWarnings("nls")
@@ -141,8 +146,7 @@
 		// create fragment
 		MStringModelFragment fragment = MFragmentFactory.INSTANCE.createStringModelFragment();
 		fragment.setFeaturename("children");
-		final String fragmentParentId = "org.eclipse.e4.ui.tests.modelassembler.app";
-		fragment.setParentElementId(fragmentParentId);
+		fragment.setParentElementId(APPLICATION_ID);
 		fragment.getElements().add(window);
 		// add fragment to resource
 		Resource fragmentResource = factory.createResource(URI.createURI("fragmentvirtualuri"));
@@ -160,7 +164,7 @@
 		assertTrue(elements.contains(window));
 		MUIElement found = modelService.find(contributedElementId, application);
 		assertEquals(window, found);
-		assertEquals(fragmentParentId, found.getParent().getElementId());
+		assertEquals(APPLICATION_ID, found.getParent().getElementId());
 
 		verifyZeroInteractions(logger);
 
@@ -168,12 +172,12 @@
 	}
 
 	@Test
-	@Ignore // currently ignored due to bug 487748
+//	@Ignore // currently ignored due to bug 487748
 	public void testFragments_existingXMIID_checkExists() throws Exception {
 		// create fragment
 		MStringModelFragment fragment = MFragmentFactory.INSTANCE.createStringModelFragment();
 		fragment.setFeaturename("children");
-		fragment.setParentElementId("org.eclipse.e4.ui.tests.modelassembler.app");
+		fragment.setParentElementId(APPLICATION_ID);
 		// create fragment resource
 		E4XMIResource fragmentResource = (E4XMIResource) factory.createResource(URI.createURI("fragmentvirtualuri"));
 		resourceSet.getResources().add(fragmentResource);
@@ -221,7 +225,7 @@
 		// create fragment
 		MStringModelFragment fragment = MFragmentFactory.INSTANCE.createStringModelFragment();
 		fragment.setFeaturename("children");
-		fragment.setParentElementId("org.eclipse.e4.ui.tests.modelassembler.app");
+		fragment.setParentElementId(APPLICATION_ID);
 		// create fragment resource
 		E4XMIResource fragmentResource = (E4XMIResource) factory.createResource(URI.createURI("fragmentvirtualuri"));
 		resourceSet.getResources().add(fragmentResource);
@@ -507,7 +511,121 @@
 		assertEquals("simpleprocessor.post", application.getDescriptors().get(0).getElementId());
 	}
 
+	@Test
+	public void testNotExistsNonInitialFragmentApply() throws IOException {
+		// ### arrange
+
+		// In this test case we assume that the given application comes from a persisted
+		// model (workbench.xmi file) where part 2 has been moved to the partStackTop.
+		// But the fragment contributes part 2 to the partStackBottom.
+		// By using apply=notexists part 2 from the fragment will not be applied since
+		// it is already present in the model
+		IExtension[] createExtensions = createExtensions(
+				"org.eclipse.e4.ui.tests/data/ModelAssembler/fragment_notexists.xml");
+		MWindow mainWindow = modelService.createModelElement(MWindow.class);
+		mainWindow.setElementId("mainWindow");
+
+		MPartSashContainer partSashContainer = modelService.createModelElement(MPartSashContainer.class);
+		MPartStack partStackTop = modelService.createModelElement(MPartStack.class);
+		partStackTop.setElementId("partStackTop");
+		MPartStack partStackBottom = modelService.createModelElement(MPartStack.class);
+		partStackBottom.setElementId("partStackBottom");
+
+		MPart part1 = modelService.createModelElement(MPart.class);
+		part1.setElementId("part1Id");
+		MPart part2 = modelService.createModelElement(MPart.class);
+		part2.setElementId("part2Id");
+		part2.getPersistedState().put("id", "kgu");
+
+		// initially add both parts to top stack, while contributing fragment
+		// contributes part 2 to the partStackBottom
+		partStackTop.getChildren().add(part1);
+		partStackTop.getChildren().add(part2);
+
+		partSashContainer.getChildren().add(partStackTop);
+		partSashContainer.getChildren().add(partStackBottom);
+
+		mainWindow.getChildren().add(partSashContainer);
+		application.getChildren().add(mainWindow);
+
+		// set same id from fragment file
+		EObject ePart2 = (EObject) part2;
+		E4XMIResource r = (E4XMIResource) ePart2.eResource();
+		r.setID(ePart2, "_gXKowH1YEemug7TLPAdneg");
+
+		// ### act
+		assembler.processFragments(createExtensions, false);
+
+		// ### assert
+		List<MPart> findElements = modelService.findElements(application, "part2Id", MPart.class);
+		// due to apply=notexists the part from the fragment should not have been merged
+		// into the model, because the part from the fragment is already part of
+		// the model
+		assertThat(findElements, hasSize(1));
+		MPart mPart = findElements.get(0);
+		assertThat(mPart.getPersistedState(), hasEntry("id", "kgu"));
+
+		// part 3 is not part of the persisted application yet and is therefore added by
+		// the fragment merge
+		List<MPart> findPart3 = modelService.findElements(application, "part3Id", MPart.class);
+		assertThat(findPart3, hasSize(1));
+
+		// --> part 2 will not be merged due to apply=notexists, but part 3 is added,
+		// because it does not exist yet.
+	}
+
+	@Test
+	public void testInitialApplyButNonInitialStartupFragment() throws IOException {
+		// arrange
+		IExtension[] createExtensions = createExtensions(
+				"org.eclipse.e4.ui.tests/data/ModelAssembler/fragment_initial.xml");
+		MWindow mainWindow = modelService.createModelElement(MWindow.class);
+		mainWindow.setElementId("mainWindow");
+
+		MPartSashContainer partSashContainer = modelService.createModelElement(MPartSashContainer.class);
+		MPartStack partStackTop = modelService.createModelElement(MPartStack.class);
+		partStackTop.setElementId("partStackTop");
+		MPartStack partStackBottom = modelService.createModelElement(MPartStack.class);
+		partStackBottom.setElementId("partStackBottom");
+
+		MPart part1 = modelService.createModelElement(MPart.class);
+		part1.setElementId("part1Id");
+		MPart part2 = modelService.createModelElement(MPart.class);
+		part2.setElementId("part2Id");
+
+		// initially add both parts to top stack, while contributing fragment
+		// contributes part 2 to the partStackBottom
+		partStackTop.getChildren().add(part1);
+		partStackTop.getChildren().add(part2);
+
+		partSashContainer.getChildren().add(partStackTop);
+		partSashContainer.getChildren().add(partStackBottom);
+
+		mainWindow.getChildren().add(partSashContainer);
+		application.getChildren().add(mainWindow);
+
+		// act
+		assembler.processFragments(createExtensions, false);
+
+		// assert
+		List<MPart> findElements = modelService.findElements(application, "part2Id", MPart.class);
+
+		// due to apply=initial the part from the fragment should not have been merged
+		// into the application, because the fragment is not being processed
+		assertThat(findElements, hasSize(1));
+	}
+
 	private void testProcessor(String filePath, boolean initial, boolean afterFragments) throws Exception {
+		IExtension[] extensions = createExtensions(filePath);
+		assembler.runProcessors(extensions, initial, afterFragments);
+	}
+
+	/**
+	 * @param filePath
+	 * @return
+	 * @throws IOException
+	 */
+	private IExtension[] createExtensions(String filePath) throws IOException {
 		IContributor contributor = ContributorFactorySimple.createContributor(BUNDLE_SYMBOLIC_NAME);
 		IExtensionRegistry registry = createTestExtensionRegistry();
 		assertEquals(0, registry.getConfigurationElementsFor(EXTENSION_POINT_ID).length);
@@ -515,7 +633,7 @@
 		IExtensionPoint extPoint = registry.getExtensionPoint(EXTENSION_POINT_ID);
 		IExtension[] extensions = new ExtensionsSort().sort(extPoint.getExtensions());
 		assertEquals(0, application.getDescriptors().size());
-		assembler.runProcessors(extensions, initial, afterFragments);
+		return extensions;
 	}
 
 	private IExtensionRegistry createTestExtensionRegistry() {