[bugzilla 411217] Make the WorkspaceSession able to identify external modifications (e.g. git reset), so that external changes can be taken into account inside editor editors & clients
Also add the corresponding test
diff --git a/plugins/org.eclipse.mylyn.docs.intent.collab.ide/src/org/eclipse/mylyn/docs/intent/collab/ide/adapters/WorkspaceAdapter.java b/plugins/org.eclipse.mylyn.docs.intent.collab.ide/src/org/eclipse/mylyn/docs/intent/collab/ide/adapters/WorkspaceAdapter.java
index 694bf5b..f0afe48 100644
--- a/plugins/org.eclipse.mylyn.docs.intent.collab.ide/src/org/eclipse/mylyn/docs/intent/collab/ide/adapters/WorkspaceAdapter.java
+++ b/plugins/org.eclipse.mylyn.docs.intent.collab.ide/src/org/eclipse/mylyn/docs/intent/collab/ide/adapters/WorkspaceAdapter.java
@@ -29,6 +29,7 @@
import java.util.Set;
import org.eclipse.emf.common.command.CommandStack;
+import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
@@ -232,8 +233,11 @@
// Step 2: we send a warning to the WorkspaceSession if necessary
treatSessionWarning(resource);
- // Step 3: save the resource
+ // Step 3: add an adapter used by the WorkspaceSession to distinguish internal
+ // from external modifications
+ resource.eAdapters().add(new InternalModificationAdapter());
+ // Step 4: save the resource
if (resource.getContents().isEmpty()) {
// if the resource is empty, we delete it
resource.delete(getSaveOptions());
@@ -559,8 +563,11 @@
public Object getIDFromElement(EObject element) {
URI uri = null;
if (element != null) {
- uri = EcoreUtil.getURI(element);
-
+ if (element.eResource() != null && element.eResource().getContents().contains(element)) {
+ uri = URI.createURI(element.eResource().getURI().toString() + "#/");
+ } else {
+ uri = EcoreUtil.getURI(element);
+ }
// if the URI starts with "#", it means that the resource is no longer part of the resource set.
// we return null to indicate the the given element is now invalid
if (uri.toString().startsWith("#")) {
@@ -757,4 +764,19 @@
public ResourceSet getResourceSet() {
return new ResourceSetImpl();
}
+
+ /**
+ * An adapter used to identify resources that are saved by a WorkspaceAdapter (and differentiate internal
+ * from external modifications).
+ *
+ * @author <a href="mailto:alex.lagarde@obeo.fr">Alex Lagarde</a>
+ */
+ public static final class InternalModificationAdapter extends AdapterImpl {
+
+ /**
+ * Default constructor.
+ */
+ private InternalModificationAdapter() {
+ }
+ }
}
diff --git a/plugins/org.eclipse.mylyn.docs.intent.collab.ide/src/org/eclipse/mylyn/docs/intent/collab/ide/repository/WorkspaceRepositoryLoader.java b/plugins/org.eclipse.mylyn.docs.intent.collab.ide/src/org/eclipse/mylyn/docs/intent/collab/ide/repository/WorkspaceRepositoryLoader.java
index 55a61b6..e9cc93d 100644
--- a/plugins/org.eclipse.mylyn.docs.intent.collab.ide/src/org/eclipse/mylyn/docs/intent/collab/ide/repository/WorkspaceRepositoryLoader.java
+++ b/plugins/org.eclipse.mylyn.docs.intent.collab.ide/src/org/eclipse/mylyn/docs/intent/collab/ide/repository/WorkspaceRepositoryLoader.java
@@ -10,7 +10,6 @@
*******************************************************************************/
package org.eclipse.mylyn.docs.intent.collab.ide.repository;
-import java.io.IOException;
import java.util.List;
import org.eclipse.emf.common.util.URI;
@@ -70,11 +69,7 @@
workspaceRepository.getResourceSet().getResource(fileURI, true);
}
} catch (WrappedException e) {
- try {
- workspaceRepository.getResourceSet().createResource(fileURI).save(null);
- } catch (IOException e1) {
- // Silently fail
- }
+ // Silent catch: resource will not be added to the resource set
}
}
diff --git a/plugins/org.eclipse.mylyn.docs.intent.collab.ide/src/org/eclipse/mylyn/docs/intent/collab/ide/repository/WorkspaceSession.java b/plugins/org.eclipse.mylyn.docs.intent.collab.ide/src/org/eclipse/mylyn/docs/intent/collab/ide/repository/WorkspaceSession.java
index 99cd161..b2f0ba1 100644
--- a/plugins/org.eclipse.mylyn.docs.intent.collab.ide/src/org/eclipse/mylyn/docs/intent/collab/ide/repository/WorkspaceSession.java
+++ b/plugins/org.eclipse.mylyn.docs.intent.collab.ide/src/org/eclipse/mylyn/docs/intent/collab/ide/repository/WorkspaceSession.java
@@ -17,6 +17,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IResource;
@@ -29,12 +30,12 @@
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.notify.Adapter;
-import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.mylyn.docs.intent.collab.handlers.adapters.IntentCommand;
import org.eclipse.mylyn.docs.intent.collab.handlers.impl.notification.elementList.ElementListAdapter;
import org.eclipse.mylyn.docs.intent.collab.ide.adapters.WorkspaceAdapter;
+import org.eclipse.mylyn.docs.intent.collab.ide.adapters.WorkspaceAdapter.InternalModificationAdapter;
import org.eclipse.mylyn.docs.intent.collab.ide.notification.WorkspaceTypeListener;
/**
@@ -140,26 +141,39 @@
// We get the changed and removed Resources
Collection<Resource> removedResources = new ArrayList<Resource>();
Collection<Resource> changedResources = new ArrayList<Resource>();
+ Collection<Resource> changedResourcesToReload = new ArrayList<Resource>();
if (!visitor.getRemovedResources().isEmpty()) {
removedResources.addAll(visitor.getRemovedResources());
}
for (Resource changedResource : visitor.getChangedResources()) {
+ Iterator<InternalModificationAdapter> internalModificationAdapters = Iterables.filter(
+ changedResource.eAdapters(), InternalModificationAdapter.class).iterator();
// If the resource is contained in the savedResources list, it means
// that we should ignore this notification ; however we remove this resource
// from this list so that we'll treat the next notifications
if (!savedResources.contains(changedResource)) {
changedResources.add(changedResource);
+
+ // If the resource is not associated to any InternalModificationAdapter, it means that
+ // it has been saved without using the Workspace, and hence that we should reload the
+ // resource to handle this external modification
+ if (!internalModificationAdapters.hasNext()) {
+ changedResourcesToReload.add(changedResource);
+ }
} else {
savedResources.remove(changedResource);
}
+ if (internalModificationAdapters.hasNext()) {
+ changedResource.eAdapters().remove(internalModificationAdapters.next());
+ }
}
// Finally, we treat each removed or changed resource.
treatRemovedResources(removedResources);
- treatChangedResources(changedResources);
+ treatChangedResources(changedResources, changedResourcesToReload);
} catch (CoreException e) {
// TODO define a standard reaction to this exception :
@@ -176,28 +190,22 @@
*
* @param changedResources
* the list of the recently changed resources
+ * @param changedResourcesToReload
+ * a subset of changedResources indicating resources that have been externally modified (and
+ * hence should be reloaded)
*/
- private void treatChangedResources(Collection<Resource> changedResources) {
+ private void treatChangedResources(Collection<Resource> changedResources,
+ final Collection<Resource> changedResourcesToReload) {
// For each changed resources
for (final Resource changedResource : changedResources) {
if (repositoryAdapter != null) {
repositoryAdapter.execute(new IntentCommand() {
public void execute() {
- // TODO [DISABLED]
- // this make the resource unstable, some commands launched within the bad timing will
- // have side
- // effects
- // we want to reload the resource if it has been modified by another tool, but not if
- // it has been
- // modified by a client
- // temporary workaround: disabling
- // System.out.println("Reloading " + changedResource + "...");
- // // We reload this resource (if it's loaded, otherwise we simply do nothing)
- //
- // reloadResource(changedResource);
- //
- // System.out.println(changedResource + " reloaded.");
+ // Reload resource only in case of an external modification
+ if (changedResourcesToReload.contains(changedResource)) {
+ reloadResource(changedResource);
+ }
// Finally, we notify the listeners of this session
notifyListeners(changedResource);
@@ -214,7 +222,7 @@
* @param changedResource
* the changed resource
*/
- private void reloadResource(final Resource changedResource) {
+ private void reloadResource(Resource changedResource) {
// We get the adapters defined on the roots (in order to re-attach them after this
// resource will be reloaded)
final Collection<Adapter> oldAdaptersList = new ArrayList<Adapter>();
@@ -223,13 +231,9 @@
}
changedResource.unload();
try {
- repository.getResourceSet().getResource(changedResource.getURI(), true);
- } catch (WrappedException we) {
- try {
- changedResource.load(WorkspaceAdapter.getLoadOptions());
- } catch (IOException e) {
- // TODO Handle this I/O Exception
- }
+ changedResource.load(WorkspaceAdapter.getLoadOptions());
+ } catch (IOException e) {
+ // TODO Handle this I/O Exception
}
// We re-attach the eAdapters to the roots
for (EObject root : changedResource.getContents()) {
diff --git a/tests/org.eclipse.mylyn.docs.intent.client.ui.test/src/org/eclipse/mylyn/docs/intent/client/ui/test/suite/IntentPluginTestSuite.java b/tests/org.eclipse.mylyn.docs.intent.client.ui.test/src/org/eclipse/mylyn/docs/intent/client/ui/test/suite/IntentPluginTestSuite.java
index d0cc6b9..bf7d9fd 100644
--- a/tests/org.eclipse.mylyn.docs.intent.client.ui.test/src/org/eclipse/mylyn/docs/intent/client/ui/test/suite/IntentPluginTestSuite.java
+++ b/tests/org.eclipse.mylyn.docs.intent.client.ui.test/src/org/eclipse/mylyn/docs/intent/client/ui/test/suite/IntentPluginTestSuite.java
@@ -25,6 +25,7 @@
import org.eclipse.mylyn.docs.intent.client.ui.test.unit.refresher.RefresherTest;
import org.eclipse.mylyn.docs.intent.client.ui.test.unit.repository.IntentURITest;
import org.eclipse.mylyn.docs.intent.client.ui.test.unit.scenario.CompilerNotificationsTest;
+import org.eclipse.mylyn.docs.intent.client.ui.test.unit.scenario.ExternalChangesTest;
import org.eclipse.mylyn.docs.intent.client.ui.test.unit.scenario.ExternalContentReferencesTest;
import org.eclipse.mylyn.docs.intent.client.ui.test.unit.scenario.IntentAbstractResourceTest;
import org.eclipse.mylyn.docs.intent.client.ui.test.unit.scenario.IntentDocumentationUpdateDoesNotCauseResolvingIssuesTest;
@@ -126,6 +127,7 @@
scenarioSuite.addTestSuite(ExternalContentReferencesTest.class);
scenarioSuite.addTestSuite(IntentHyperLinkDetetectorTest.class);
scenarioSuite.addTestSuite(ExternalParsersTest.class);
+ scenarioSuite.addTestSuite(ExternalChangesTest.class);
return scenarioSuite;
}
diff --git a/tests/org.eclipse.mylyn.docs.intent.client.ui.test/src/org/eclipse/mylyn/docs/intent/client/ui/test/unit/scenario/ExternalChangesTest.java b/tests/org.eclipse.mylyn.docs.intent.client.ui.test/src/org/eclipse/mylyn/docs/intent/client/ui/test/unit/scenario/ExternalChangesTest.java
new file mode 100644
index 0000000..d5e1771
--- /dev/null
+++ b/tests/org.eclipse.mylyn.docs.intent.client.ui.test/src/org/eclipse/mylyn/docs/intent/client/ui/test/unit/scenario/ExternalChangesTest.java
@@ -0,0 +1,232 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.mylyn.docs.intent.client.ui.test.unit.scenario;
+
+import java.io.IOException;
+
+import org.eclipse.core.runtime.AssertionFailedException;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.mylyn.docs.intent.client.ui.editor.IntentEditor;
+import org.eclipse.mylyn.docs.intent.client.ui.editor.IntentEditorDocument;
+import org.eclipse.mylyn.docs.intent.client.ui.test.util.AbstractIntentUITest;
+import org.eclipse.mylyn.docs.intent.collab.common.location.IntentLocations;
+import org.eclipse.mylyn.docs.intent.collab.ide.repository.WorkspaceRepository;
+import org.eclipse.mylyn.docs.intent.core.document.IntentDocument;
+import org.eclipse.mylyn.docs.intent.core.document.IntentDocumentFactory;
+import org.eclipse.mylyn.docs.intent.core.document.IntentSection;
+import org.eclipse.mylyn.docs.intent.core.modelingunit.ContributionInstruction;
+import org.eclipse.mylyn.docs.intent.core.modelingunit.ModelingUnit;
+import org.eclipse.mylyn.docs.intent.core.modelingunit.ModelingUnitFactory;
+import org.eclipse.mylyn.docs.intent.core.modelingunit.ModelingUnitInstructionReference;
+import org.eclipse.mylyn.docs.intent.core.modelingunit.NativeValue;
+import org.eclipse.mylyn.docs.intent.core.modelingunit.StructuralFeatureAffectation;
+
+/**
+ * An test ensuring that external changes (i.e. changes on the document made without using the
+ * RepositoryAdapter, for example git updates) are correctly handled.
+ *
+ * @author <a href="mailto:alex.lagarde@obeo.fr">Alex Lagarde</a>
+ */
+public class ExternalChangesTest extends AbstractIntentUITest {
+ /**
+ * Path to test file.
+ */
+ private static final String INTENT_DOCUMENT_EXAMPLE_PATH = "data/unit/documents/scenario/abstract_resources.intent";
+
+ /**
+ * The current Intent editor.
+ */
+ private IntentEditor editor;
+
+ /**
+ * The document associated to the current Intent editor.
+ */
+ private IntentEditorDocument document;
+
+ /**
+ * URI of the {@link IntentDocument} to modify externally.
+ */
+ private URI documentURI;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ setUpIntentProject("intentProject", INTENT_DOCUMENT_EXAMPLE_PATH, true);
+ documentURI = URI.createURI("platform:/resource/" + intentProject.getName() + "/.repository/"
+ + IntentLocations.INTENT_INDEX + '.' + WorkspaceRepository.getWorkspaceResourceExtension());
+ }
+
+ /**
+ * Ensures that external changes (i.e. changes on the document made without using the RepositoryAdapter,
+ * for example git updates) are correctly handled.
+ */
+ public void testExternalChangesOnCompiler() {
+ // Step 1: modify the IntentDocument model without using the repository adapter
+ // Adding a new modeling unit that contribute to the abstract root by setting its nsURI
+ IntentDocument intentDocument = (IntentDocument)new ResourceSetImpl().getResource(documentURI, true)
+ .getContents().iterator().next();
+ IntentSection intentSection = intentDocument.getSubSections().get(0).getSubSections().get(0);
+ assertEquals("Wrong initial state:expecting only 2 modeling units", 2, intentSection
+ .getModelingUnits().size());
+ ModelingUnit newModelingUnit = ModelingUnitFactory.eINSTANCE.createModelingUnit();
+ ContributionInstruction contributionInstruction = ModelingUnitFactory.eINSTANCE
+ .createContributionInstruction();
+ ModelingUnitInstructionReference contributionReference = ModelingUnitFactory.eINSTANCE
+ .createModelingUnitInstructionReference();
+ contributionReference.setIntentHref("myAbstractRoot");
+ contributionInstruction.setContributionReference(contributionReference);
+ StructuralFeatureAffectation setNsURIInstruction = ModelingUnitFactory.eINSTANCE
+ .createStructuralFeatureAffectation();
+ setNsURIInstruction.setName("nsURI");
+ NativeValue nsURIValue = ModelingUnitFactory.eINSTANCE.createNativeValue();
+ nsURIValue.setValue("myNsURI");
+ setNsURIInstruction.getValues().add(nsURIValue);
+ contributionInstruction.getContributions().add(setNsURIInstruction);
+ newModelingUnit.getInstructions().add(contributionInstruction);
+ intentSection.getIntentContent().add(newModelingUnit);
+
+ repositoryListener.clearPreviousEntries();
+ try {
+ intentDocument.eResource().save(null);
+ Thread.sleep(1000);
+ } catch (IOException e) {
+ AssertionFailedException assertionFailed = new AssertionFailedException(e.getMessage());
+ assertionFailed.setStackTrace(e.getStackTrace());
+ throw assertionFailed;
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+
+ // Step 2: check that intent model was updated
+ EObject intentDocumentAsLoadedByRepositoryAdapter = repositoryAdapter
+ .getResource(IntentLocations.INTENT_INDEX).getContents().iterator().next();
+ IntentSection intentSectionAsLoadedByRepositoryAdapter = ((IntentDocument)intentDocumentAsLoadedByRepositoryAdapter)
+ .getSubSections().get(0).getSubSections().get(0);
+ assertEquals("Intent section should have been reloaded due to external changes", 3,
+ intentSectionAsLoadedByRepositoryAdapter.getModelingUnits().size());
+
+ // Step 3: check that compiler was called
+ waitForCompiler();
+ Resource generatedResource = repositoryAdapter
+ .getResource(IntentLocations.GENERATED_RESOURCES_FOLDER_PATH + "/abstractResource");
+ assertNotNull("Compiler did not re-generate the resource due to external changes", generatedResource);
+ assertTrue("Compiler generated an empty resource",
+ generatedResource.getContents().iterator().next() instanceof EPackage);
+ assertEquals("Compiler did not take in acount external changes", "myNsURI",
+ ((EPackage)generatedResource.getContents().iterator().next()).getNsURI());
+
+ }
+
+ /**
+ * Ensures that external changes (i.e. changes on the document made without using the RepositoryAdapter,
+ * for example git updates) are correctly handled.
+ */
+ public void testExternalChangesOnDocumentWithDocumentOpened() {
+ // Step 1 : open an editor on the root document
+ editor = openIntentEditor();
+ document = (IntentEditorDocument)editor.getDocumentProvider().getDocument(editor.getEditorInput());
+
+ // Step 2: modify the IntentDocument model without using the repository adapter
+ IntentDocument intentDocument = (IntentDocument)new ResourceSetImpl().getResource(documentURI, true)
+ .getContents().iterator().next();
+ assertEquals("Wrong initial state: Intent document should have only one chapter", 1, intentDocument
+ .eContents().size());
+ assertEquals("Wrong initial state: Intent document should have only one chapter", 2, document.get()
+ .split("Chapter").length);
+
+ intentDocument.getIntentContent().add(IntentDocumentFactory.eINSTANCE.createIntentSection());
+ try {
+ intentDocument.eResource().save(null);
+ Thread.sleep(1000);
+ } catch (IOException e) {
+ AssertionFailedException assertionFailed = new AssertionFailedException(e.getMessage());
+ assertionFailed.setStackTrace(e.getStackTrace());
+ throw assertionFailed;
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+
+ // Step 3: check that intent model was updated
+ EObject intentDocumentAsLoadedByRepositoryAdapter = repositoryAdapter
+ .getResource(IntentLocations.INTENT_INDEX).getContents().iterator().next();
+ assertEquals("Intent document should have been reloaded due to external changes", 2,
+ intentDocumentAsLoadedByRepositoryAdapter.eContents().size());
+
+ // Step 4: check that editor was updated
+ waitForAllOperationsInUIThread();
+ assertEquals("Editor should have been reloaded", 3, document.get().split("Chapter").length);
+ }
+
+ /**
+ * Ensures that external changes (i.e. changes on the document made without using the RepositoryAdapter,
+ * for example git updates) are correctly handled.
+ */
+ public void testExternalChangesOnChapterWithDocumentOpened() {
+ // Step 1 : open an editor on the root document
+ editor = openIntentEditor();
+ document = (IntentEditorDocument)editor.getDocumentProvider().getDocument(editor.getEditorInput());
+
+ // Step 2: modify the IntentChapter without using the repository adapter
+ IntentDocument intentDocument = (IntentDocument)new ResourceSetImpl().getResource(documentURI, true)
+ .getContents().iterator().next();
+ IntentSection intentSection = intentDocument.getSubSections().get(0).getSubSections().get(0);
+ assertEquals("Wrong initial state: Intent section should have only 2 modeling units", 2,
+ intentSection.getModelingUnits().size());
+ assertEquals("Wrong initial state: Intent section should have only 2 modeling units", 3, document
+ .get().split("@M").length);
+
+ intentSection.getIntentContent().add(ModelingUnitFactory.eINSTANCE.createModelingUnit());
+ try {
+ intentDocument.eResource().save(null);
+ Thread.sleep(1000);
+ } catch (IOException e) {
+ AssertionFailedException assertionFailed = new AssertionFailedException(e.getMessage());
+ assertionFailed.setStackTrace(e.getStackTrace());
+ throw assertionFailed;
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+
+ // Step 3: check that intent model was updated
+ EObject intentDocumentAsLoadedByRepositoryAdapter = repositoryAdapter
+ .getResource(IntentLocations.INTENT_INDEX).getContents().iterator().next();
+ IntentSection intentSectionAsLoadedByRepositoryAdapter = ((IntentDocument)intentDocumentAsLoadedByRepositoryAdapter)
+ .getSubSections().get(0).getSubSections().get(0);
+ assertEquals("Intent document should have been reloaded due to external changes", 3,
+ intentSectionAsLoadedByRepositoryAdapter.getModelingUnits().size());
+
+ // Step 4: check that editor was updated
+ waitForAllOperationsInUIThread();
+ assertEquals("Editor should have been reloaded", 4, document.get().split("@M").length);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see junit.framework.TestCase#tearDown()
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ if (editor != null) {
+ editor.close(false);
+ }
+ super.tearDown();
+ }
+}