Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--extraplugins/migration/org.eclipse.papyrus.migration.rsa/META-INF/MANIFEST.MF2
-rw-r--r--extraplugins/migration/org.eclipse.papyrus.migration.rsa/src/org/eclipse/papyrus/migration/rsa/transformation/ImportTransformation.java100
-rw-r--r--plugins/editor/org.eclipse.papyrus.editor/.classpath2
-rw-r--r--plugins/editor/org.eclipse.papyrus.editor/.settings/org.eclipse.jdt.core.prefs6
-rw-r--r--plugins/editor/org.eclipse.papyrus.editor/META-INF/MANIFEST.MF8
-rw-r--r--plugins/editor/org.eclipse.papyrus.editor/pom.xml2
-rw-r--r--plugins/editor/org.eclipse.papyrus.editor/src/org/eclipse/papyrus/editor/PapyrusMatchingStrategy.java48
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/META-INF/MANIFEST.MF4
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/plugin.xml4
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/pom.xml2
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/ILanguageModel.java32
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AbstractBaseModel.java28
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AbstractModelWithSharedResource.java5
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AdjunctResourceModelSnippet.java104
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/ModelsReader.java58
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/sasheditor/DiModel.java48
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/JobBasedFuture.java6
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.tools/META-INF/MANIFEST.MF2
-rw-r--r--plugins/infra/core/org.eclipse.papyrus.infra.tools/pom.xml2
-rw-r--r--plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/META-INF/MANIFEST.MF2
-rw-r--r--plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/pom.xml2
-rw-r--r--plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/src/org/eclipse/papyrus/infra/editor/welcome/internal/WelcomeModelManager.java58
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/META-INF/MANIFEST.MF6
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/plugin.xml9
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/pom.xml4
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/schema/index.exsd119
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/Activator.java42
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/WorkspaceSaveHelper.java262
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/AbstractCrossReferenceIndex.java404
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/CrossReferenceIndex.java226
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/CrossReferenceIndexHandler.java270
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/InternalIndexUtil.java73
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/OnDemandCrossReferenceIndex.java182
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/StopParsing.java30
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/IIndexSaveParticipant.java44
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/IndexManager.java1075
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/IndexPersistenceManager.java256
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/InternalModelIndex.java118
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/ICrossReferenceIndex.java274
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/ShardResourceHelper.java418
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/ShardResourceLocator.java178
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/index/IWorkspaceModelIndexProvider.java27
-rw-r--r--plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/index/WorkspaceModelIndex.java1107
-rw-r--r--plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/META-INF/MANIFEST.MF2
-rw-r--r--plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/plugin.xml4
-rw-r--r--plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/pom.xml2
-rw-r--r--plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/src/org/eclipse/papyrus/infra/gmfdiag/common/model/NotationModel.java29
-rw-r--r--plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/.classpath14
-rw-r--r--plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/.settings/org.eclipse.jdt.core.prefs6
-rw-r--r--plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/META-INF/MANIFEST.MF4
-rw-r--r--plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/pom.xml2
-rw-r--r--plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/src/org/eclipse/papyrus/infra/services/controlmode/commands/BasicUncontrolCommand.java13
-rw-r--r--plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/src/org/eclipse/papyrus/infra/services/controlmode/commands/LoadDiagramCommand.java3
-rw-r--r--plugins/infra/ui/org.eclipse.papyrus.infra.ui/META-INF/MANIFEST.MF7
-rw-r--r--plugins/infra/ui/org.eclipse.papyrus.infra.ui/pom.xml2
-rw-r--r--plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/editor/CoreMultiDiagramEditor.java42
-rw-r--r--plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/EditorUtils.java67
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/META-INF/MANIFEST.MF2
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/pom.xml2
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/src/org/eclipse/papyrus/uml/decoratormodel/internal/ui/Activator.java5
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/.classpath2
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/.settings/org.eclipse.jdt.core.prefs6
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/META-INF/MANIFEST.MF5
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/plugin.xml7
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/pom.xml2
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/Activator.java13
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/DecoratorModelIndex.java168
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/AbstractUMLIndexHandler.java6
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ModelIndexHandler.java14
-rw-r--r--plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ProfileIndexHandler.java19
-rw-r--r--plugins/uml/tools/org.eclipse.papyrus.uml.tools/src/org/eclipse/papyrus/uml/tools/providers/SemanticUMLContentProvider.java34
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/.classpath8
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/.settings/org.eclipse.jdt.core.prefs6
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/META-INF/MANIFEST.MF9
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/pom.xml2
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package1.uml10
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package1/packageA.uml10
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package1/packageA/foo.uml9
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package2.uml10
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package2/packageB.uml10
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package2/packageB/bar.uml9
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/referencing.uml21
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/root.di2
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/root.notation2
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/root.uml11
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/AbstractCrossReferenceIndexTest.java182
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/CrossReferenceIndexTest.java150
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/ShardResourceHelperTest.java241
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/ShardResourceLocatorTest.java93
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/index/WorkspaceModelIndexTest.java225
-rw-r--r--tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/tests/AllTests.java9
-rw-r--r--tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/.classpath14
-rw-r--r--tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/.settings/org.eclipse.jdt.core.prefs6
-rw-r--r--tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/META-INF/MANIFEST.MF8
-rw-r--r--tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/pom.xml2
-rw-r--r--tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/src/org/eclipse/papyrus/infra/services/controlmode/tests/uncontrol/UncontrolModelTest.java45
-rw-r--r--tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/META-INF/MANIFEST.MF2
-rw-r--r--tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/pom.xml2
-rw-r--r--tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/rules/AbstractModelFixture.java20
99 files changed, 6043 insertions, 1206 deletions
diff --git a/extraplugins/migration/org.eclipse.papyrus.migration.rsa/META-INF/MANIFEST.MF b/extraplugins/migration/org.eclipse.papyrus.migration.rsa/META-INF/MANIFEST.MF
index a04cabef9d3..1cfdb1e2fcb 100644
--- a/extraplugins/migration/org.eclipse.papyrus.migration.rsa/META-INF/MANIFEST.MF
+++ b/extraplugins/migration/org.eclipse.papyrus.migration.rsa/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Require-Bundle: org.eclipse.ui,
org.eclipse.core.resources;bundle-version="3.9.0",
org.eclipse.papyrus.infra.core.log;bundle-version="1.2.0",
- org.eclipse.papyrus.infra.emf;bundle-version="1.2.0",
+ org.eclipse.papyrus.infra.emf;bundle-version="[2.0.1,3.0.0)",
org.eclipse.m2m.qvt.oml;bundle-version="3.5.0",
org.eclipse.m2m.qvt.oml.common;bundle-version="3.5.0",
org.eclipse.m2m.qvt.oml.project;bundle-version="3.3.0",
diff --git a/extraplugins/migration/org.eclipse.papyrus.migration.rsa/src/org/eclipse/papyrus/migration/rsa/transformation/ImportTransformation.java b/extraplugins/migration/org.eclipse.papyrus.migration.rsa/src/org/eclipse/papyrus/migration/rsa/transformation/ImportTransformation.java
index 89d3ac30a00..5150068adb1 100644
--- a/extraplugins/migration/org.eclipse.papyrus.migration.rsa/src/org/eclipse/papyrus/migration/rsa/transformation/ImportTransformation.java
+++ b/extraplugins/migration/org.eclipse.papyrus.migration.rsa/src/org/eclipse/papyrus/migration/rsa/transformation/ImportTransformation.java
@@ -8,7 +8,7 @@
*
* Contributors:
* Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
- * Christian W. Damus - bug 496439
+ * Christian W. Damus - bugs 496439, 496299
*****************************************************************************/
package org.eclipse.papyrus.migration.rsa.transformation;
@@ -47,6 +47,7 @@ import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EAnnotation;
+import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
@@ -72,6 +73,7 @@ import org.eclipse.m2m.qvt.oml.util.ISessionData;
import org.eclipse.m2m.qvt.oml.util.Trace;
import org.eclipse.m2m.qvt.oml.util.WriterLog;
import org.eclipse.papyrus.dsml.validation.PapyrusDSMLValidationRule.PapyrusDSMLValidationRulePackage;
+import org.eclipse.papyrus.infra.emf.resource.ShardResourceHelper;
import org.eclipse.papyrus.infra.emf.utils.EMFHelper;
import org.eclipse.papyrus.infra.tools.util.ClassLoaderHelper;
import org.eclipse.papyrus.infra.tools.util.ListHelper;
@@ -143,17 +145,17 @@ public class ImportTransformation {
protected long importExtensionsTime = 0L;
/** Source URI to Target URI map (For Models/Libraries/Fragments) */
- protected final Map<URI, URI> uriMappings = new HashMap<URI, URI>();
+ protected final Map<URI, URI> uriMappings = new HashMap<>();
/** Source URI to Target URI map (For Profiles) */
- protected final Map<URI, URI> profileURIMappings = new HashMap<URI, URI>();
+ protected final Map<URI, URI> profileURIMappings = new HashMap<>();
- protected List<Diagram> diagramsToDelete = new LinkedList<Diagram>();
+ protected List<Diagram> diagramsToDelete = new LinkedList<>();
protected static final ExecutorsPool executorsPool = new ExecutorsPool(2);
/** EPackages corresponding to source native profiles with specific support in the transformation */
- protected static final Set<EPackage> sourceEPackages = new HashSet<EPackage>();
+ protected static final Set<EPackage> sourceEPackages = new HashSet<>();
protected final DependencyAnalysisHelper analysisHelper;
@@ -442,7 +444,7 @@ public class ImportTransformation {
return false;
}
- protected static final Set<String> supportedDiagramIds = new HashSet<String>();
+ protected static final Set<String> supportedDiagramIds = new HashSet<>();
protected static boolean isSupported(Diagram diagram) {
return supportedDiagramIds.contains(diagram.getType());
@@ -592,7 +594,7 @@ public class ImportTransformation {
notationResource.getContents().addAll(outNotationObjects);
// Cleanup empty diagrams (FIXME: They should not be generated)
- List<EObject> contentsCopy = new LinkedList<EObject>(notationResource.getContents());
+ List<EObject> contentsCopy = new LinkedList<>(notationResource.getContents());
for (EObject next : contentsCopy) {
if (next instanceof Diagram) {
Diagram diagram = (Diagram) next;
@@ -612,7 +614,7 @@ public class ImportTransformation {
configureResource((XMIResource) umlResource);
// Handle orphaned elements: remove them and log a warning (Log temporarily disabled to avoid spamming the console)
- List<EObject> notationRootElements = new LinkedList<EObject>(notationResource.getContents());
+ List<EObject> notationRootElements = new LinkedList<>(notationResource.getContents());
for (EObject rootElement : notationRootElements) {
if (rootElement instanceof View) {
View rootView = (View) rootElement;
@@ -641,7 +643,7 @@ public class ImportTransformation {
Collection<Resource> resourcesToSave = handleFragments(umlResource, notationResource, sashResource);
for (Resource resource : resourcesToSave) {
- List<EObject> rootElements = new LinkedList<EObject>(resource.getContents());
+ List<EObject> rootElements = new LinkedList<>(resource.getContents());
for (EObject rootElement : rootElements) {
EPackage ePackage = rootElement.eClass().getEPackage();
if (ePackage == ProfileBasePackage.eINSTANCE || ePackage == DefaultPackage.eINSTANCE) {
@@ -792,7 +794,7 @@ public class ImportTransformation {
protected IStatus importRSAProfiles(ExecutionContext context, IProgressMonitor monitor) {
URI transformationURI = getProfilesTransformationURI();
- List<ModelExtent> extents = new LinkedList<ModelExtent>();
+ List<ModelExtent> extents = new LinkedList<>();
extents.add(getInOutUMLModel());
extents.add(getInoutNotationModel());
Diagnostic loadedProfiles = loadInPapyrusProfiles();
@@ -886,9 +888,9 @@ public class ImportTransformation {
return Diagnostic.OK_INSTANCE;
}
- List<String> missingProfiles = new LinkedList<String>();
+ List<String> missingProfiles = new LinkedList<>();
- List<EObject> allContents = new LinkedList<EObject>();
+ List<EObject> allContents = new LinkedList<>();
try {
URI validationProfileURI = URI.createURI("pathmap://DSMLValidation_PROFILES/PapyrusValidationRuleDSML.uml");
Resource validationProfile = resourceSet.getResource(validationProfileURI, true);
@@ -947,7 +949,7 @@ public class ImportTransformation {
}
protected Collection<Resource> handleFragments(Resource umlResource, Resource notationResource, Resource sashResource) {
- Collection<Resource> result = new HashSet<Resource>();
+ Collection<Resource> result = new HashSet<>();
result.add(umlResource);
result.add(notationResource);
result.add(sashResource);
@@ -956,16 +958,23 @@ public class ImportTransformation {
Iterator<EObject> elementIterator = umlResource.getAllContents();
- Set<Resource> fragmentResources = new HashSet<Resource>();
+ Set<Resource> fragmentResources = new HashSet<>();
+ List<EAnnotation> rsaAnnotations = new ArrayList<>();
while (elementIterator.hasNext()) {
EObject element = elementIterator.next();
- if (element.eResource() != umlResource && element.eResource().getContents().contains(element)) { // Controlled/Fragment root
- fragmentResources.add(element.eResource());
+ Resource possibleFragment = element.eResource();
+ if ((possibleFragment != umlResource) && possibleFragment.getContents().contains(element)) { // Controlled/Fragment root
+ fragmentResources.add(possibleFragment);
}
+
+ collectRSAAnnotations(element, rsaAnnotations);
}
- List<Resource> fragmentUMLResources = new LinkedList<Resource>();
+ // Strip all RSA fragment annotations
+ rsaAnnotations.forEach(EcoreUtil::remove);
+
+ List<Resource> fragmentUMLResources = new LinkedList<>();
for (Resource fragmentResource : fragmentResources) {
URI papyrusFragmentURI = convertToPapyrus(fragmentResource.getURI(), UMLResource.FILE_EXTENSION);
@@ -989,12 +998,18 @@ public class ImportTransformation {
}
newResource.getContents().addAll(fragmentResource.getContents());
+
+ // Make it a Papyrus controlled unit of the "shard" variety
+ try (ShardResourceHelper shard = new ShardResourceHelper(newResource)) {
+ shard.setShard(true);
+ }
+
result.add(newResource);
}
deleteSourceStereotypes(fragmentResources);
- List<EObject> importedElements = new LinkedList<EObject>(notationResource.getContents());
+ List<EObject> importedElements = new LinkedList<>(notationResource.getContents());
for (EObject notationElement : importedElements) {
if (notationElement instanceof Diagram) {
EObject semanticElement = ((Diagram) notationElement).getElement();
@@ -1062,14 +1077,14 @@ public class ImportTransformation {
}
protected void deleteSourceStereotypes(Collection<Resource> fragmentResources) {
- Set<Resource> allResources = new HashSet<Resource>(fragmentResources);
+ Set<Resource> allResources = new HashSet<>(fragmentResources);
allResources.add(umlResource);
for (Resource resource : allResources) {
// For performance reasons, RSA RT Stereotypes have not been deleted during the QVTo transformation (Bug 444379)
// Delete them as a post-action. Iterate on all controlled models and delete the RealTime stereotypes at the root of each resource
- List<EObject> resourceContents = new LinkedList<EObject>(resource.getContents());
+ List<EObject> resourceContents = new LinkedList<>(resource.getContents());
for (EObject rootElement : resourceContents) {
if (sourceEPackages.contains(rootElement.eClass().getEPackage())) {
delete(rootElement);
@@ -1078,6 +1093,39 @@ public class ImportTransformation {
}
}
+ /**
+ * Collects the RSA-style fragment linkage annotations, RSA diagrams, and other
+ * RSA-specific annotations attached to an {@code object}.
+ *
+ * @param object
+ * an object in the model
+ * @param annotations
+ * collects the RSA-specific annotations
+ */
+ protected void collectRSAAnnotations(EObject object, Collection<? super EAnnotation> annotations) {
+ if (object instanceof EModelElement) {
+ EModelElement modelElement = (EModelElement) object;
+ modelElement.getEAnnotations().stream()
+ .filter(this::isRSASpecificAnnotation)
+ .forEach(annotations::add);
+ }
+ }
+
+ protected boolean isRSASpecificAnnotation(EAnnotation annotation) {
+ boolean result = false;
+
+ String source = annotation.getSource();
+ if (source != null) {
+ // This covers both the fragments and the fragmentContainer annotation
+ result = source.startsWith("com.ibm.xtools.uml.msl.fragment") //$NON-NLS-1$
+ || source.equals("uml2.diagrams") //$NON-NLS-1$
+ // Covers the UI-reduction annotation
+ || source.startsWith("com.ibm.xtools.common.ui."); //$NON-NLS-1$
+ }
+
+ return result;
+ }
+
protected URI convertToPapyrus(URI rsaURI, String extension) {
if ("epx".equals(rsaURI.fileExtension())) { //$NON-NLS-1$
// Profiles: myProfile.profile.uml, myProfile.profile.notation, ...
@@ -1131,7 +1179,7 @@ public class ImportTransformation {
result = executor.execute(context, extents.toArray(new ModelExtent[0]));
// Append to our history
- List<EObject> history = new ArrayList<EObject>(trace.getTraceContent());
+ List<EObject> history = new ArrayList<>(trace.getTraceContent());
history.addAll(newTraces.getTraceContent());
trace.setTraceContent(history);
} finally {
@@ -1210,7 +1258,7 @@ public class ImportTransformation {
}
protected void configureResource(XMIResource resource) {
- Map<Object, Object> saveOptions = new HashMap<Object, Object>();
+ Map<Object, Object> saveOptions = new HashMap<>();
// default save options.
saveOptions.put(XMLResource.OPTION_DECLARE_XML, Boolean.TRUE);
@@ -1229,7 +1277,7 @@ public class ImportTransformation {
}
protected List<ModelExtent> getModelExtents() {
- List<ModelExtent> allExtents = new LinkedList<ModelExtent>();
+ List<ModelExtent> allExtents = new LinkedList<>();
allExtents.add(getInOutUMLModel());
allExtents.add(getInoutNotationModel());
allExtents.add(getOutSashModel());
@@ -1251,9 +1299,9 @@ public class ImportTransformation {
* We need to make all root Elements available to the QVTo ModelExtent (Including the ones
* from fragments)
*/
- List<EObject> allStereotypeApplications = new LinkedList<EObject>();
+ List<EObject> allStereotypeApplications = new LinkedList<>();
TreeIterator<EObject> allContents = resource.getAllContents();
- Set<Resource> browsedResources = new HashSet<Resource>();
+ Set<Resource> browsedResources = new HashSet<>();
browsedResources.add(resource);
while (allContents.hasNext()) {
EObject next = allContents.next();
@@ -1275,7 +1323,7 @@ public class ImportTransformation {
}
}
- List<EObject> allRootElements = new LinkedList<EObject>(resource.getContents());
+ List<EObject> allRootElements = new LinkedList<>(resource.getContents());
allRootElements.addAll(allStereotypeApplications);
// outUML = new BasicModelExtent(resource.getContents());
diff --git a/plugins/editor/org.eclipse.papyrus.editor/.classpath b/plugins/editor/org.eclipse.papyrus.editor/.classpath
index ad32c83a788..eca7bdba8f0 100644
--- a/plugins/editor/org.eclipse.papyrus.editor/.classpath
+++ b/plugins/editor/org.eclipse.papyrus.editor/.classpath
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
diff --git a/plugins/editor/org.eclipse.papyrus.editor/.settings/org.eclipse.jdt.core.prefs b/plugins/editor/org.eclipse.papyrus.editor/.settings/org.eclipse.jdt.core.prefs
index 71d2d530b86..8ddd9a88d57 100644
--- a/plugins/editor/org.eclipse.papyrus.editor/.settings/org.eclipse.jdt.core.prefs
+++ b/plugins/editor/org.eclipse.papyrus.editor/.settings/org.eclipse.jdt.core.prefs
@@ -1,15 +1,15 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.compiler.source=1.8
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
diff --git a/plugins/editor/org.eclipse.papyrus.editor/META-INF/MANIFEST.MF b/plugins/editor/org.eclipse.papyrus.editor/META-INF/MANIFEST.MF
index 661cf32e7ae..85e130fde5d 100644
--- a/plugins/editor/org.eclipse.papyrus.editor/META-INF/MANIFEST.MF
+++ b/plugins/editor/org.eclipse.papyrus.editor/META-INF/MANIFEST.MF
@@ -1,14 +1,14 @@
Manifest-Version: 1.0
Export-Package: org.eclipse.papyrus.editor
Require-Bundle: org.eclipse.gmf.runtime.diagram.ui;bundle-version="[1.8.0,2.0.0)",
- org.eclipse.papyrus.infra.emf;bundle-version="[2.0.0,3.0.0)",
- org.eclipse.papyrus.infra.ui;bundle-version="[1.2.0,2.0.0)";visibility:=reexport
+ org.eclipse.papyrus.infra.ui;bundle-version="[1.3.0,2.0.0)";visibility:=reexport,
+ org.eclipse.papyrus.infra.emf;bundle-version="[2.1.0,3.0.0)"
Bundle-Vendor: %providerName
Bundle-ActivationPolicy: lazy
-Bundle-Version: 1.2.0.qualifier
+Bundle-Version: 1.4.0.qualifier
Bundle-Localization: plugin
Bundle-Name: %pluginName
Bundle-Activator: org.eclipse.papyrus.editor.Activator
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.eclipse.papyrus.editor;singleton:=true
-Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
diff --git a/plugins/editor/org.eclipse.papyrus.editor/pom.xml b/plugins/editor/org.eclipse.papyrus.editor/pom.xml
index 8a7a84420fd..dfafd98eaa7 100644
--- a/plugins/editor/org.eclipse.papyrus.editor/pom.xml
+++ b/plugins/editor/org.eclipse.papyrus.editor/pom.xml
@@ -7,6 +7,6 @@
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.papyrus.editor</artifactId>
- <version>1.2.0-SNAPSHOT</version>
+ <version>1.4.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
diff --git a/plugins/editor/org.eclipse.papyrus.editor/src/org/eclipse/papyrus/editor/PapyrusMatchingStrategy.java b/plugins/editor/org.eclipse.papyrus.editor/src/org/eclipse/papyrus/editor/PapyrusMatchingStrategy.java
index 6b33d372b75..cab679f6c69 100644
--- a/plugins/editor/org.eclipse.papyrus.editor/src/org/eclipse/papyrus/editor/PapyrusMatchingStrategy.java
+++ b/plugins/editor/org.eclipse.papyrus.editor/src/org/eclipse/papyrus/editor/PapyrusMatchingStrategy.java
@@ -1,6 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2010 CEA LIST.
- *
+ * Copyright (c) 2010, 2016 CEA LIST, Christian W. Damus, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -9,20 +8,18 @@
*
* Contributors:
* Ansgar Radermacher ansgar.radermacher@cea.fr - Initial API and implementation
+ * Christian W. Damus - bug 496299
*
*****************************************************************************/
package org.eclipse.papyrus.editor;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.papyrus.infra.emf.resource.ICrossReferenceIndex;
+import org.eclipse.papyrus.infra.ui.util.EditorUtils;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorMatchingStrategy;
import org.eclipse.ui.IEditorReference;
-import org.eclipse.ui.IFileEditorInput;
-import org.eclipse.ui.PartInitException;
public class PapyrusMatchingStrategy implements IEditorMatchingStrategy {
@@ -43,26 +40,27 @@ public class PapyrusMatchingStrategy implements IEditorMatchingStrategy {
*/
@Override
public boolean matches(IEditorReference editorRef, IEditorInput newEInput) {
- if (newEInput instanceof IFileEditorInput) {
- IFile newFile = ((IFileEditorInput) newEInput).getFile();
- String extension = newFile.getFileExtension();
- if ("uml".equals(extension) || "di".equals(extension) || "notation".equals(extension)) {
- try {
- IEditorInput exiEInput = editorRef.getEditorInput();
- if ((exiEInput instanceof IFileEditorInput)) {
- IFile exiFile = ((IFileEditorInput) exiEInput).getFile();
- IPath exiFilenameWOE = exiFile.getFullPath().removeFileExtension();
- IPath newFilenameWOE = newFile.getFullPath().removeFileExtension();
+ boolean result = false;
+
+ URI newURI = EditorUtils.getResourceURI(newEInput);
+ if (newURI != null) {
+ URI existingURI = EditorUtils.getResourceURI(editorRef);
+ if (existingURI != null) {
+ String extension = newURI.fileExtension();
- if (exiFilenameWOE.equals(newFilenameWOE)) {
- return true;
- }
- }
- } catch (PartInitException e) {
- Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getLocalizedMessage(), e));
+ // Some processing is only appropriate to Papyrus model resources
+ if ("uml".equals(extension) || "di".equals(extension) || "notation".equals(extension)) {
+ // Resolve the root unit in case of a shard
+ ICrossReferenceIndex index = ICrossReferenceIndex.getInstance(null);
+ newURI = EditorUtils.resolveShardRoot(index, newURI);
+ existingURI = EditorUtils.resolveShardRoot(index, existingURI);
}
+
+ // Are they the same?
+ result = newURI.equals(existingURI);
}
}
- return false;
+
+ return result;
}
}
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/META-INF/MANIFEST.MF b/plugins/infra/core/org.eclipse.papyrus.infra.core/META-INF/MANIFEST.MF
index 7d28645a912..c8635206ed3 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.core/META-INF/MANIFEST.MF
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/META-INF/MANIFEST.MF
@@ -4,7 +4,7 @@ Export-Package: org.eclipse.papyrus.infra.core,
org.eclipse.papyrus.infra.core.editor,
org.eclipse.papyrus.infra.core.extension,
org.eclipse.papyrus.infra.core.internal.expressions;x-internal:=true,
- org.eclipse.papyrus.infra.core.internal.language;x-internal:=true,
+ org.eclipse.papyrus.infra.core.internal.language;x-friends:="org.eclipse.papyrus.infra.emf",
org.eclipse.papyrus.infra.core.internal.sashmodel;x-internal:=true,
org.eclipse.papyrus.infra.core.language,
org.eclipse.papyrus.infra.core.listenerservice,
@@ -29,7 +29,7 @@ Require-Bundle: org.eclipse.emf.workspace;bundle-version="[1.5.0,2.0.0)",
org.eclipse.core.resources;bundle-version="[3.11.0,4.0.0)";visibility:=reexport
Bundle-Vendor: %providerName
Bundle-ActivationPolicy: lazy
-Bundle-Version: 2.0.0.qualifier
+Bundle-Version: 2.2.0.qualifier
Bundle-Name: %pluginName
Bundle-Localization: plugin
Bundle-Activator: org.eclipse.papyrus.infra.core.Activator
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/plugin.xml b/plugins/infra/core/org.eclipse.papyrus.infra.core/plugin.xml
index cb8e66e2229..5002ac0231a 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.core/plugin.xml
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/plugin.xml
@@ -24,6 +24,10 @@
description="Main Papyrus IModel"
fileExtension="di"
required="true">
+ <modelSnippet
+ classname="org.eclipse.papyrus.infra.core.resource.AdjunctResourceModelSnippet"
+ description="Snippet for DI resource of referenced model resources">
+ </modelSnippet>
</model>
</extension>
<extension
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/pom.xml b/plugins/infra/core/org.eclipse.papyrus.infra.core/pom.xml
index 505d993b1c3..2524dc5f316 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.core/pom.xml
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/pom.xml
@@ -7,6 +7,6 @@
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.papyrus.infra.core</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.2.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project> \ No newline at end of file
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/ILanguageModel.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/ILanguageModel.java
new file mode 100644
index 00000000000..8e25eb2bc31
--- /dev/null
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/ILanguageModel.java
@@ -0,0 +1,32 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.core.internal.language;
+
+import org.eclipse.papyrus.infra.core.language.ILanguage;
+import org.eclipse.papyrus.infra.core.resource.IModel;
+
+/**
+ * An adapter type for {@link IModel}s that provide language-specific details
+ * about themselves.
+ */
+public interface ILanguageModel {
+ /**
+ * Obtains a model's file extension. This identifies resources that
+ * are expected to contain "semantic model" content for some {@link ILanguage}.
+ * Language models are expected to have file extensions associated with them.
+ *
+ * @return the model's file extension
+ */
+ String getModelFileExtension();
+}
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AbstractBaseModel.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AbstractBaseModel.java
index 6aa34544b9d..40c057e37ba 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AbstractBaseModel.java
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AbstractBaseModel.java
@@ -10,7 +10,7 @@
* CEA LIST - Initial API and implementation
* Christian W. Damus (CEA) - manage models by URI, not IFile (CDO)
* Christian W. Damus (CEA) - bug 437052
- * Christian W. Damus - bugs 399859, 481149, 485220
+ * Christian W. Damus - bugs 399859, 481149, 485220, 496299
*
*****************************************************************************/
package org.eclipse.papyrus.infra.core.resource;
@@ -32,6 +32,7 @@ import org.eclipse.emf.ecore.xmi.XMIResource;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.URIHandlerImpl.PlatformSchemeAware;
import org.eclipse.papyrus.infra.core.Activator;
+import org.eclipse.papyrus.infra.core.internal.language.ILanguageModel;
/**
* An abstract implementation of model. This class should be subclassed to fit
@@ -276,7 +277,7 @@ public abstract class AbstractBaseModel extends AbstractModel implements IVersio
}
public static Map<Object, Object> getDefaultSaveOptions() {
- Map<Object, Object> saveOptions = new HashMap<Object, Object>();
+ Map<Object, Object> saveOptions = new HashMap<>();
// default save options.
saveOptions.put(XMLResource.OPTION_DECLARE_XML, Boolean.TRUE);
@@ -298,7 +299,7 @@ public abstract class AbstractBaseModel extends AbstractModel implements IVersio
@Override
public void saveCopy(IPath targetPathWithoutExtension, Map<Object, Object> targetMap) {
// OutputStream targetStream = getOutputStream(targetPath);
- Map<Object, Object> saveOptions = new HashMap<Object, Object>();
+ Map<Object, Object> saveOptions = new HashMap<>();
URI targetURI = getTargetURI(targetPathWithoutExtension);
@@ -461,4 +462,25 @@ public abstract class AbstractBaseModel extends AbstractModel implements IVersio
return object.eContainer() == null;
}
+ /**
+ * @since 2.1
+ */
+ @Override
+ public <T> T getAdapter(Class<T> adapter) {
+ T result = null;
+
+ if (adapter == ILanguageModel.class) {
+ result = adapter.cast(new ILanguageModel() {
+
+ @Override
+ public String getModelFileExtension() {
+ return AbstractBaseModel.this.getModelFileExtension();
+ }
+ });
+ } else {
+ result = super.getAdapter(adapter);
+ }
+
+ return result;
+ }
}
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AbstractModelWithSharedResource.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AbstractModelWithSharedResource.java
index 748ab35ed26..884a997c7e4 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AbstractModelWithSharedResource.java
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AbstractModelWithSharedResource.java
@@ -8,7 +8,7 @@
*
* Contributors:
* LIFL - Initial API and implementation
- * Christian W. Damus - bug 485220
+ * Christian W. Damus - bugs 485220, 496299
*
*****************************************************************************/
package org.eclipse.papyrus.infra.core.resource;
@@ -80,6 +80,7 @@ public abstract class AbstractModelWithSharedResource<T extends EObject> extends
// Check if model is loaded.
if (resourceIsSet()) {
configureResource(resource);
+ startSnippets();
return;
}
// model is not loaded, do it.
@@ -192,7 +193,7 @@ public abstract class AbstractModelWithSharedResource<T extends EObject> extends
@SuppressWarnings("unchecked")
public List<T> getModelRoots() {
- List<T> roots = new ArrayList<T>();
+ List<T> roots = new ArrayList<>();
for (EObject object : getResource().getContents()) {
if (isModelRoot(object)) {
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AdjunctResourceModelSnippet.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AdjunctResourceModelSnippet.java
new file mode 100644
index 00000000000..46a6003edc5
--- /dev/null
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/AdjunctResourceModelSnippet.java
@@ -0,0 +1,104 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.core.resource;
+
+import java.util.Collections;
+
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.URIConverter;
+import org.eclipse.papyrus.infra.core.Activator;
+
+/**
+ * An {@link IModel} snippet that loads the model's corresponding resource
+ * whenever a "primary" semantic resource is loaded that has a resource
+ * corresponding to it that is managed by the {@code IModel}.
+ *
+ * @since 2.1
+ */
+public class AdjunctResourceModelSnippet implements IModelSnippet {
+ private EMFLogicalModel model;
+ private Adapter adapter;
+
+ /**
+ * Initializes me.
+ */
+ public AdjunctResourceModelSnippet() {
+ super();
+ }
+
+
+ @Override
+ public void start(IModel startingModel) {
+ if (startingModel instanceof EMFLogicalModel) {
+ model = (EMFLogicalModel) startingModel;
+
+ adapter = new ResourceAdapter() {
+ @Override
+ protected void handleResourceLoaded(Resource resource) {
+ maybeLoadAdjunctResource(resource);
+ }
+ };
+
+ model.getModelManager().eAdapters().add(adapter);
+ }
+ }
+
+ @Override
+ public void dispose(IModel stoppingModel) {
+ if ((stoppingModel == model) && (adapter != null)) {
+ model.getModelManager().eAdapters().remove(adapter);
+ adapter = null;
+ model = null;
+ }
+ }
+
+ void maybeLoadAdjunctResource(Resource resource) {
+ // If the parameter resource is the model's own kind of resource,
+ // then there is nothing to do
+ if ((model != null) && !model.isRelatedResource(resource)) {
+ URI adjunctURI = resource.getURI().trimFileExtension().appendFileExtension(model.getModelFileExtension());
+ ResourceSet resourceSet = resource.getResourceSet();
+
+ boolean adjunctAlreadyLoaded = false;
+ for (Resource loadedResource : resourceSet.getResources()) {
+ if (loadedResource.getURI().equals(adjunctURI)) {
+ adjunctAlreadyLoaded = true;
+ break;
+ }
+ }
+
+ if (!adjunctAlreadyLoaded && (resourceSet.getURIConverter() != null)) {
+ URIConverter converter = resourceSet.getURIConverter();
+
+ // If the di resource associated to the parameter resource exists,
+ // then load it
+ if (converter.exists(adjunctURI, Collections.emptyMap())) {
+ // Best effort load. This must not interfere with other
+ // resource set operations
+ try {
+ resourceSet.getResource(adjunctURI, true);
+ } catch (Exception e) {
+ Activator.log.error(
+ String.format("Failed to load %s resource", model.getModelFileExtension()), //$NON-NLS-1$
+ e);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/ModelsReader.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/ModelsReader.java
index d64dc7bffb0..249ce9a72ee 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/ModelsReader.java
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/ModelsReader.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2011, 2014 CEA LIST and others.
+ * Copyright (c) 2011, 2016 CEA LIST, Christian W. Damus, and others.
*
*
* All rights reserved. This program and the accompanying materials
@@ -10,6 +10,7 @@
* Contributors:
* CEA LIST - Initial API and implementation
* Christian W. Damus (CEA) - bug 429242
+ * Christian W. Damus - bug 496299
*
*****************************************************************************/
package org.eclipse.papyrus.infra.core.resource;
@@ -18,6 +19,7 @@ import static org.eclipse.papyrus.infra.core.Activator.log;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -31,6 +33,7 @@ import org.eclipse.emf.common.util.URI;
import org.eclipse.papyrus.infra.core.Activator;
import org.eclipse.papyrus.infra.core.extension.ExtensionException;
import org.eclipse.papyrus.infra.core.extension.ExtensionUtils;
+import org.eclipse.papyrus.infra.tools.util.TypeUtils;
import com.google.common.collect.Sets;
@@ -191,7 +194,18 @@ public class ModelsReader extends ExtensionUtils {
try {
if (MODEL_ELEMENT_NAME.equals(ele.getName())) {
IModel model = instanciateModel(ele);
+
+ // Register the model
+ AbstractModel previous = TypeUtils.as(modelSet.getModel(model.getIdentifier()), AbstractModel.class);
modelSet.registerModel(model);
+
+ // We may be contributing to another model already registered
+ // under the same ID
+ model = modelSet.getModel(model.getIdentifier());
+ if ((previous != null) && (previous != model)) {
+ inherit(model, previous);
+ }
+
addDeclaredModelSnippet(ele, model);
addDeclaredDependencies(ele, model);
log.debug("model loaded: '" + model.getClass().getName() + "'");
@@ -203,6 +217,21 @@ public class ModelsReader extends ExtensionUtils {
}
/**
+ * Let a {@code special} model inherit the snippets and dependencies from a
+ * more {@code general} model that it replaces in the model-set.
+ *
+ * @param special
+ * a specializing model
+ * @param general
+ * a more generalized model that it replaces
+ */
+ private void inherit(IModel special, AbstractModel general) {
+ general.snippets.forEach(special::addModelSnippet);
+ special.setAfterLoadModelDependencies(general.getAfterLoadModelIdentifiers());
+ special.setBeforeUnloadDependencies(general.getUnloadBeforeModelIdentifiers());
+ }
+
+ /**
* Add ModelSet snippet
*
* @param modelSet
@@ -316,11 +345,12 @@ public class ModelsReader extends ExtensionUtils {
protected void addDeclaredDependencies(IConfigurationElement modelConfigurationElement, IModel model) {
// Get children
IConfigurationElement[] dependencyElements = modelConfigurationElement.getChildren(DEPENDENCY_ELEMENT_NAME);
- List<String> afterLoadModelIdentifiers = null;
- List<String> unloadBeforeModelIdentifiers = null;
- for (IConfigurationElement dependencyElement : dependencyElements) {
+ // Ordering is important, obviously, but we mustn't have duplicates
+ LinkedHashSet<String> afterLoadModelIdentifiers = null;
+ LinkedHashSet<String> unloadBeforeModelIdentifiers = null;
+ for (IConfigurationElement dependencyElement : dependencyElements) {
// init load after and unloadBefore
IConfigurationElement[] loadAfterElements = dependencyElement.getChildren(LOAD_AFTER_ELEMENT_NAME);
IConfigurationElement[] unloadBeforeElements = dependencyElement.getChildren(UNLOAD_BEFORE_ELEMENT_NAME);
@@ -329,7 +359,11 @@ public class ModelsReader extends ExtensionUtils {
String identifier = loadAfterElement.getAttribute(IDENTIFIER_ATTRIBUTE_NAME);
if (identifier != null && identifier.length() > 0) {
if (afterLoadModelIdentifiers == null) {
- afterLoadModelIdentifiers = new ArrayList<String>();
+ afterLoadModelIdentifiers = new LinkedHashSet<>();
+ List<String> existing = model.getAfterLoadModelIdentifiers();
+ if (existing != null) {
+ afterLoadModelIdentifiers.addAll(existing);
+ }
}
afterLoadModelIdentifiers.add(identifier);
}
@@ -339,7 +373,11 @@ public class ModelsReader extends ExtensionUtils {
String identifier = unloadBeforeElement.getAttribute(IDENTIFIER_ATTRIBUTE_NAME);
if (identifier != null && identifier.length() > 0) {
if (unloadBeforeModelIdentifiers == null) {
- unloadBeforeModelIdentifiers = new ArrayList<String>();
+ unloadBeforeModelIdentifiers = new LinkedHashSet<>();
+ List<String> existing = model.getUnloadBeforeModelIdentifiers();
+ if (existing != null) {
+ unloadBeforeModelIdentifiers.addAll(existing);
+ }
}
unloadBeforeModelIdentifiers.add(identifier);
}
@@ -347,8 +385,12 @@ public class ModelsReader extends ExtensionUtils {
}
// all config elements have been parsed. sets the dependencies in the model
- model.setAfterLoadModelDependencies(afterLoadModelIdentifiers);
- model.setBeforeUnloadDependencies(unloadBeforeModelIdentifiers);
+ if (afterLoadModelIdentifiers != null) {
+ model.setAfterLoadModelDependencies(new ArrayList<>(afterLoadModelIdentifiers));
+ }
+ if (unloadBeforeModelIdentifiers != null) {
+ model.setBeforeUnloadDependencies(new ArrayList<>(unloadBeforeModelIdentifiers));
+ }
}
/**
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/sasheditor/DiModel.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/sasheditor/DiModel.java
index de2bb7ae32e..2f6a1cd2758 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/sasheditor/DiModel.java
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/sasheditor/DiModel.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2011, 2014 LIFL, CEA LIST, and others.
+ * Copyright (c) 2011, 2016 LIFL, CEA LIST, Christian W. Damus, and others.
*
*
* All rights reserved. This program and the accompanying materials
@@ -10,21 +10,18 @@
* Contributors:
* LIFL - Initial API and implementation
* Christian W. Damus (CEA) - bug 429242
+ * Christian W. Damus - bug 496299
*
*****************************************************************************/
package org.eclipse.papyrus.infra.core.resource.sasheditor;
-import java.util.Collections;
import java.util.Map;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
-import org.eclipse.emf.ecore.resource.ResourceSet;
-import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.xmi.XMIResource;
import org.eclipse.emf.ecore.xmi.XMLResource;
-import org.eclipse.papyrus.infra.core.Activator;
import org.eclipse.papyrus.infra.core.resource.AbstractModelWithSharedResource;
import org.eclipse.papyrus.infra.core.resource.IEMFModel;
import org.eclipse.papyrus.infra.core.resource.IModel;
@@ -131,47 +128,6 @@ public class DiModel extends AbstractModelWithSharedResource<EObject> implements
}
- @Override
- public void handle(Resource resource) {
- super.handle(resource);
- if (resource == null) {
- return;
- }
-
- // If the parameter resource is already a di resource, nothing to do
- if (!isRelatedResource(resource)) {
- URI diUri = resource.getURI().trimFileExtension().appendFileExtension(DI_FILE_EXTENSION);
- ResourceSet resourceSet = getResourceSet();
-
- boolean diAlreadyLoaded = false;
- for (Resource loadedResource : resourceSet.getResources()) {
- if (loadedResource.getURI().equals(diUri)) {
- diAlreadyLoaded = true;
- break;
- }
- }
-
- if (!diAlreadyLoaded && resourceSet.getURIConverter() != null) {
- URIConverter converter = resourceSet.getURIConverter();
-
- // If the di resource associated to the parameter resource exists, load it
- if (converter.exists(diUri, Collections.emptyMap())) {
-
- // loadModel writes this.resource and this.resourceUri. In this case, when only want to load
- // the resource, but it should not become the main resource
-
- // Load with try/catch to remain consistent with loadModel()
- try {
- resourceSet.getResource(diUri, true);
- } catch (Exception ex) {
- Activator.log.error(ex);
- }
- }
-
- }
- }
- }
-
// Prevent infinite loop from 2 models delegating to each other.
private boolean checkingControlState = false;
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/JobBasedFuture.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/JobBasedFuture.java
index f5fb41b133b..a7af91a37f2 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/JobBasedFuture.java
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/JobBasedFuture.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2014 Christian W. Damus and others.
+ * Copyright (c) 2014, 2016 Christian W. Damus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -179,8 +179,8 @@ public abstract class JobBasedFuture<V> extends Job implements ListenableFuture<
boolean result = isDone();
if (!result) {
- Job current = Job.getJobManager().currentJob();
- if ((current == null) || (current.getRule() == null)) {
+ ISchedulingRule current = Job.getJobManager().currentRule();
+ if (current == null) {
result = uiSafeAwaitDone(timeoutMillis);
} else {
result = lockBasedAwaitDone(timeoutMillis);
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/META-INF/MANIFEST.MF b/plugins/infra/core/org.eclipse.papyrus.infra.tools/META-INF/MANIFEST.MF
index 6db575046db..8fca886535b 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.tools/META-INF/MANIFEST.MF
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/META-INF/MANIFEST.MF
@@ -13,7 +13,7 @@ Require-Bundle: org.eclipse.papyrus.infra.core.log;bundle-version="[1.2.0,2.0.0)
org.eclipse.core.resources;bundle-version="3.11.0"
Bundle-Vendor: %Bundle-Vendor
Bundle-ActivationPolicy: lazy
-Bundle-Version: 2.0.0.qualifier
+Bundle-Version: 2.0.100.qualifier
Eclipse-BuddyPolicy: dependent
Bundle-Name: %Bundle-Name
Bundle-Activator: org.eclipse.papyrus.infra.tools.Activator
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/pom.xml b/plugins/infra/core/org.eclipse.papyrus.infra.tools/pom.xml
index 28f1cab8cac..252c49ec53f 100644
--- a/plugins/infra/core/org.eclipse.papyrus.infra.tools/pom.xml
+++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/pom.xml
@@ -7,6 +7,6 @@
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.papyrus.infra.tools</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.100-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project> \ No newline at end of file
diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/META-INF/MANIFEST.MF b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/META-INF/MANIFEST.MF
index b31bb6ac1d6..d66d15f0e09 100644
--- a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/META-INF/MANIFEST.MF
+++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/META-INF/MANIFEST.MF
@@ -16,7 +16,7 @@ Require-Bundle: org.eclipse.uml2.types;bundle-version="[2.0.0,3.0.0)";visibility
org.eclipse.papyrus.infra.properties.ui;bundle-version="[1.2.0,2.0.0)"
Bundle-Vendor: %providerName
Bundle-ActivationPolicy: lazy;exclude:="org.eclipse.papyrus.infra.editor.welcome.internal.constraints"
-Bundle-Version: 1.2.0.qualifier
+Bundle-Version: 1.2.100.qualifier
Bundle-ClassPath: .
Bundle-Localization: plugin
Bundle-Name: %pluginName
diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/pom.xml b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/pom.xml
index ec1b679a968..71d390c9897 100644
--- a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/pom.xml
+++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/pom.xml
@@ -7,6 +7,6 @@
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.papyrus.infra.editor.welcome</artifactId>
- <version>1.2.0-SNAPSHOT</version>
+ <version>1.2.100-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
diff --git a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/src/org/eclipse/papyrus/infra/editor/welcome/internal/WelcomeModelManager.java b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/src/org/eclipse/papyrus/infra/editor/welcome/internal/WelcomeModelManager.java
index 4cf7fafb50a..4e6910dffe8 100644
--- a/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/src/org/eclipse/papyrus/infra/editor/welcome/internal/WelcomeModelManager.java
+++ b/plugins/infra/editor/org.eclipse.papyrus.infra.editor.welcome/src/org/eclipse/papyrus/infra/editor/welcome/internal/WelcomeModelManager.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2015 Christian W. Damus and others.
+ * Copyright (c) 2015, 2016 Christian W. Damus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -38,7 +38,6 @@ import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
-import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl.ResourceLocator;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl;
@@ -261,61 +260,6 @@ public class WelcomeModelManager {
}
}
- // More or less copied from EMF
- @Override
- protected Resource basicGetResource(URI uri, boolean loadOnDemand) {
- Map<URI, Resource> map = resourceSet.getURIResourceMap();
- if (map != null) {
- Resource resource = map.get(uri);
- if (resource != null) {
- if (loadOnDemand && !resource.isLoaded()) {
- demandLoadHelper(resource);
- }
- return resource;
- }
- }
-
- URIConverter theURIConverter = resourceSet.getURIConverter();
- URI normalizedURI = theURIConverter.normalize(uri);
- for (Resource resource : resourceSet.getResources()) {
- if (theURIConverter.normalize(resource.getURI()).equals(normalizedURI)) {
- if (loadOnDemand && !resource.isLoaded()) {
- demandLoadHelper(resource);
- }
-
- if (map != null) {
- map.put(uri, resource);
- }
- return resource;
- }
- }
-
- Resource delegatedResource = delegatedGetResource(uri, loadOnDemand);
- if (delegatedResource != null) {
- if (map != null) {
- map.put(uri, delegatedResource);
- }
- return delegatedResource;
- }
-
- if (loadOnDemand) {
- Resource resource = demandCreateResource(uri);
- if (resource == null) {
- throw new IllegalArgumentException(String.format("Cannot create a resource for '%s'; a registered resource factory is needed", uri));
- }
-
- demandLoadHelper(resource);
-
- if (map != null) {
- map.put(uri, resource);
- }
- return resource;
- }
-
- return null;
-
- }
-
@Override
public void dispose() {
super.dispose();
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/META-INF/MANIFEST.MF b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/META-INF/MANIFEST.MF
index eb599b54ca7..63e6c7970ef 100644
--- a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/META-INF/MANIFEST.MF
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/META-INF/MANIFEST.MF
@@ -4,18 +4,20 @@ Export-Package: org.eclipse.papyrus.infra.emf,
org.eclipse.papyrus.infra.emf.advice,
org.eclipse.papyrus.infra.emf.commands,
org.eclipse.papyrus.infra.emf.edit.domain,
+ org.eclipse.papyrus.infra.emf.internal.resource;x-internal:=true,
+ org.eclipse.papyrus.infra.emf.internal.resource.index;x-internal:=true,
org.eclipse.papyrus.infra.emf.requests,
org.eclipse.papyrus.infra.emf.resource,
org.eclipse.papyrus.infra.emf.resource.index,
org.eclipse.papyrus.infra.emf.spi.resolver,
org.eclipse.papyrus.infra.emf.utils
-Require-Bundle: org.eclipse.papyrus.infra.core;bundle-version="[2.0.0,3.0.0)";visibility:=reexport,
+Require-Bundle: org.eclipse.papyrus.infra.core;bundle-version="[2.1.0,3.0.0)";visibility:=reexport,
org.eclipse.core.expressions;bundle-version="[3.5.0,4.0.0)";visibility:=reexport,
org.eclipse.gmf.runtime.emf.type.core;bundle-version="[1.9.0,2.0.0)";visibility:=reexport,
org.eclipse.papyrus.emf.facet.custom.core;bundle-version="[2.0.0,3.0.0)";visibility:=reexport
Bundle-Vendor: Eclipse Modeling Project
Bundle-ActivationPolicy: lazy
-Bundle-Version: 2.0.100.qualifier
+Bundle-Version: 2.2.0.qualifier
Bundle-Name: EMF Tools
Bundle-Activator: org.eclipse.papyrus.infra.emf.Activator
Bundle-ManifestVersion: 2
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/plugin.xml b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/plugin.xml
index de27f193cdc..3fa4aef1e73 100644
--- a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/plugin.xml
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/plugin.xml
@@ -18,6 +18,8 @@
-->
<plugin>
<extension-point id="dependencyUpdateParticipant" name="Dependency Update Participants" schema="schema/dependencyUpdateParticipant.exsd"/>
+ <extension-point id="index" name="Workspace Model Index" schema="schema/index.exsd"/>
+
<extension
point="org.eclipse.papyrus.infra.types.core.elementTypeSetConfiguration">
<elementTypeSet
@@ -26,4 +28,11 @@
</elementTypeSet>
</extension>
+ <extension
+ point="org.eclipse.papyrus.infra.emf.index">
+ <indexProvider
+ class="org.eclipse.papyrus.infra.emf.internal.resource.CrossReferenceIndex$IndexProvider">
+ </indexProvider>
+ </extension>
+
</plugin>
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/pom.xml b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/pom.xml
index 4f142a8b4fe..157d5cc080a 100644
--- a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/pom.xml
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/pom.xml
@@ -7,6 +7,6 @@
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.papyrus.infra.emf</artifactId>
- <version>2.0.100-SNAPSHOT</version>
+ <version>2.2.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
-</project> \ No newline at end of file
+</project>
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/schema/index.exsd b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/schema/index.exsd
new file mode 100644
index 00000000000..f70c104a3a8
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/schema/index.exsd
@@ -0,0 +1,119 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.papyrus.infra.emf" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+ <appinfo>
+ <meta.schema plugin="org.eclipse.papyrus.infra.emf" id="index" name="Workspace Model Index"/>
+ </appinfo>
+ <documentation>
+ Registration of workspace model indices.
+ </documentation>
+ </annotation>
+
+ <element name="extension">
+ <annotation>
+ <appinfo>
+ <meta.element />
+ </appinfo>
+ </annotation>
+ <complexType>
+ <sequence>
+ <element ref="indexProvider" minOccurs="1" maxOccurs="unbounded"/>
+ </sequence>
+ <attribute name="point" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="id" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="name" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appinfo>
+ <meta.attribute translatable="true"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="indexProvider">
+ <annotation>
+ <documentation>
+ A supplier of a &lt;tt&gt;WorkspaceModelIndex&lt;/tt&gt; to add to the indexing subsystem.
+ </documentation>
+ </annotation>
+ <complexType>
+ <attribute name="class" type="string" use="required">
+ <annotation>
+ <documentation>
+ The class implementing the index provider.
+ </documentation>
+ <appinfo>
+ <meta.attribute kind="java" basedOn=":org.eclipse.papyrus.infra.emf.resource.index.IWorkspaceModelIndexProvider"/>
+ </appinfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="since"/>
+ </appinfo>
+ <documentation>
+ 2.1
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="examples"/>
+ </appinfo>
+ <documentation>
+ [Enter extension point usage example here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="apiinfo"/>
+ </appinfo>
+ <documentation>
+ [Enter API information here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="implementation"/>
+ </appinfo>
+ <documentation>
+ [Enter information about supplied implementation of this extension point.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appinfo>
+ <meta.section type="copyright"/>
+ </appinfo>
+ <documentation>
+ Copyright (c) 2016 Christian W. Damus and others.
+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
+ </documentation>
+ </annotation>
+
+</schema>
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/Activator.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/Activator.java
index 0698bdea266..a28b0c13ec4 100644
--- a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/Activator.java
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/Activator.java
@@ -8,14 +8,22 @@
*
* Contributors:
* Camille Letavernier (camille.letavernier@cea.fr) - Initial API and implementation
- * Christian W. Damus - bug 485220
+ * Christian W. Damus - bugs 485220, 496299
*
*****************************************************************************/
package org.eclipse.papyrus.infra.emf;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.eclipse.core.resources.ISavedState;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Plugin;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
@@ -24,6 +32,8 @@ import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.papyrus.emf.facet.custom.core.ICustomizationManager;
import org.eclipse.papyrus.emf.facet.custom.core.ICustomizationManagerFactory;
import org.eclipse.papyrus.infra.core.log.LogHelper;
+import org.eclipse.papyrus.infra.emf.internal.resource.index.IndexManager;
+import org.eclipse.papyrus.infra.emf.internal.resource.index.IndexPersistenceManager;
import org.eclipse.papyrus.infra.emf.spi.resolver.EObjectResolverService;
import org.eclipse.papyrus.infra.emf.spi.resolver.IEObjectResolver;
import org.osgi.framework.BundleContext;
@@ -66,6 +76,30 @@ public class Activator extends Plugin {
log = new LogHelper(this);
resolverService = new EObjectResolverService(context);
+
+ // Set up for workspace save and loading from saved state
+ WorkspaceSaveHelper saveHelper = new WorkspaceSaveHelper();
+ List<WorkspaceSaveHelper.SaveDelegate> saveDelegates = getSaveDelegates();
+ ISavedState state = ResourcesPlugin.getWorkspace().addSaveParticipant(
+ PLUGIN_ID,
+ saveHelper.createSaveParticipant(saveDelegates));
+ if ((state != null) && (state.getSaveNumber() != 0)) {
+ saveHelper.initializeSaveDelegates(state, saveDelegates);
+ }
+
+ // Kick off the workspace model indexing system
+ new Job("Initialize workspace model index") {
+ {
+ setSystem(true);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ IndexManager.getInstance();
+
+ return Status.OK_STATUS;
+ }
+ }.schedule();
}
@Override
@@ -127,4 +161,10 @@ public class Activator extends Plugin {
return resolverService;
}
+ private List<WorkspaceSaveHelper.SaveDelegate> getSaveDelegates() {
+ return Arrays.asList(
+ new WorkspaceSaveHelper.SaveDelegate("index", //$NON-NLS-1$
+ IndexPersistenceManager.INSTANCE.getSaveParticipant(),
+ IndexPersistenceManager.INSTANCE::initialize));
+ }
}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/WorkspaceSaveHelper.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/WorkspaceSaveHelper.java
new file mode 100644
index 00000000000..f01728c8206
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/WorkspaceSaveHelper.java
@@ -0,0 +1,262 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import org.eclipse.core.resources.ISaveContext;
+import org.eclipse.core.resources.ISaveParticipant;
+import org.eclipse.core.resources.ISavedState;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Helper class for delegating workspace save participation.
+ */
+class WorkspaceSaveHelper {
+
+ /**
+ * Initializes me.
+ */
+ WorkspaceSaveHelper() {
+ super();
+ }
+
+ void initializeSaveDelegates(ISavedState state, List<SaveDelegate> saveDelegates) throws CoreException {
+ SaveDelegate[] currentDelegate = new SaveDelegate[] { null };
+ state = delegatingSavedState(state, () -> currentDelegate[0]);
+
+ for (SaveDelegate next : saveDelegates) {
+ currentDelegate[0] = next;
+ next.initializer.accept(state);
+ }
+ }
+
+ ISaveParticipant createSaveParticipant(List<SaveDelegate> saveDelegates) {
+ return new DelegatingSaveParticipant(saveDelegates);
+ }
+
+ /**
+ * Creates a save context that provides a view of path mappings specific to the current
+ * save delegate in the sequence.
+ *
+ * @param context
+ * the real save context
+ * @param currentDelegate
+ * a supplier of the current save delegate
+ *
+ * @return the delegating save context
+ */
+ private ISaveContext delegatingSaveContext(ISaveContext context, Supplier<? extends SaveDelegate> currentDelegate) {
+ InvocationHandler handler = new InvocationHandler() {
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (method.getDeclaringClass() == ISaveContext.class) {
+ switch (method.getName()) {
+ case "getFiles":
+ if (method.getParameterCount() == 0) {
+ // This is our getFiles
+ return getFiles();
+ }
+ break;
+ case "map":
+ if (method.getParameterCount() == 2) {
+ // This is our map(IPath, IPath)
+ return map((IPath) args[0], (IPath) args[1]);
+ }
+ break;
+ }
+ }
+
+ return method.invoke(context, args);
+ }
+
+ private IPath[] getFiles() {
+ // Get only those with our particular prefix and strip that prefix
+ IPath prefix = currentDelegate.get().pathPrefix;
+ return Stream.of(context.getFiles())
+ .filter(prefix::isPrefixOf)
+ .map(p -> p.makeRelativeTo(prefix))
+ .toArray(IPath[]::new);
+ }
+
+ private Void map(IPath path, IPath location) {
+ // Prepend the supplied path key with our unique prefix
+ context.map(currentDelegate.get().pathPrefix.append(path), location);
+ return null;
+ }
+ };
+
+ return (ISaveContext) Proxy.newProxyInstance(getClass().getClassLoader(),
+ new Class<?>[] { ISaveContext.class },
+ handler);
+ }
+
+ /**
+ * Creates a saved state that provides a view of path mappings specific to the current
+ * save delegate in the sequence.
+ *
+ * @param state
+ * the real saved state
+ * @param currentDelegate
+ * a supplier of the current save delegate
+ *
+ * @return the delegating saved state
+ */
+ private ISavedState delegatingSavedState(ISavedState state, Supplier<? extends SaveDelegate> currentDelegate) {
+ InvocationHandler handler = new InvocationHandler() {
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (method.getDeclaringClass() == ISavedState.class) {
+ switch (method.getName()) {
+ case "getFiles":
+ if (method.getParameterCount() == 0) {
+ // This is our getFiles
+ return getFiles();
+ }
+ break;
+ case "lookup":
+ if (method.getParameterCount() == 1) {
+ // This is our lookup(IPath)
+ return lookup((IPath) args[0]);
+ }
+ break;
+ }
+ }
+
+ return method.invoke(state, args);
+ }
+
+ private IPath[] getFiles() {
+ // Get only those with our particular prefix and strip that prefix
+ IPath prefix = currentDelegate.get().pathPrefix;
+ return Stream.of(state.getFiles())
+ .filter(prefix::isPrefixOf)
+ .map(p -> p.makeRelativeTo(prefix))
+ .toArray(IPath[]::new);
+ }
+
+ private IPath lookup(IPath path) {
+ // Prepend the supplied path key with our unique prefix
+ return state.lookup(currentDelegate.get().pathPrefix.append(path));
+ }
+ };
+
+ return (ISavedState) Proxy.newProxyInstance(getClass().getClassLoader(),
+ new Class<?>[] { ISavedState.class },
+ handler);
+ }
+
+ //
+ // Nested types
+ //
+
+ final static class SaveDelegate {
+ final IPath pathPrefix;
+ final ISaveParticipant participant;
+ final InitAction initializer;
+
+ SaveDelegate(String pathPrefix, ISaveParticipant participant, InitAction initializer) {
+ super();
+
+ this.pathPrefix = new Path(pathPrefix);
+ this.participant = participant;
+ this.initializer = initializer;
+ }
+ }
+
+ // This delegating participant only handles full saves
+ private class DelegatingSaveParticipant implements ISaveParticipant {
+ private final List<SaveDelegate> delegates;
+
+ DelegatingSaveParticipant(Collection<? extends SaveDelegate> delegates) {
+ super();
+
+ this.delegates = ImmutableList.copyOf(delegates);
+ }
+
+ @Override
+ public void prepareToSave(ISaveContext context) throws CoreException {
+ if (context.getKind() == ISaveContext.FULL_SAVE) {
+ iterate(context, ISaveParticipant::prepareToSave);
+ }
+ }
+
+ @Override
+ public void saving(ISaveContext context) throws CoreException {
+ if (context.getKind() == ISaveContext.FULL_SAVE) {
+ iterate(context, ISaveParticipant::saving);
+
+ // Declare full participation to increment the save number
+ context.needSaveNumber();
+ }
+ }
+
+ @Override
+ public void doneSaving(ISaveContext context) {
+ if (context.getKind() == ISaveContext.FULL_SAVE) {
+ safeIterate(context, ISaveParticipant::doneSaving);
+ }
+ }
+
+ @Override
+ public void rollback(ISaveContext context) {
+ if (context.getKind() == ISaveContext.FULL_SAVE) {
+ safeIterate(context, ISaveParticipant::rollback);
+ }
+ }
+
+ void iterate(ISaveContext context, SaveAction saveAction) throws CoreException {
+ SaveDelegate[] current = { null };
+ ISaveContext privateContext = delegatingSaveContext(context, () -> current[0]);
+
+ for (SaveDelegate next : delegates) {
+ current[0] = next;
+ saveAction.accept(next.participant, privateContext);
+ }
+ }
+
+ void safeIterate(ISaveContext context, BiConsumer<? super ISaveParticipant, ? super ISaveContext> saveAction) {
+ SaveDelegate[] current = { null };
+ ISaveContext privateContext = delegatingSaveContext(context, () -> current[0]);
+
+ for (SaveDelegate next : delegates) {
+ current[0] = next;
+ saveAction.accept(next.participant, privateContext);
+ }
+ }
+ }
+
+ @FunctionalInterface
+ interface InitAction {
+ void accept(ISavedState state) throws CoreException;
+ }
+
+ @FunctionalInterface
+ interface SaveAction {
+ void accept(ISaveParticipant participant, ISaveContext context) throws CoreException;
+ }
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/AbstractCrossReferenceIndex.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/AbstractCrossReferenceIndex.java
new file mode 100644
index 00000000000..82153f1a84b
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/AbstractCrossReferenceIndex.java
@@ -0,0 +1,404 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.internal.resource;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.papyrus.infra.emf.Activator;
+import org.eclipse.papyrus.infra.emf.resource.ICrossReferenceIndex;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Common implementation of a cross-reference index in the workspace.
+ */
+public abstract class AbstractCrossReferenceIndex implements ICrossReferenceIndex {
+
+ public static final String SHARD_ANNOTATION_SOURCE = "http://www.eclipse.org/papyrus/2016/resource/shard"; //$NON-NLS-1$
+
+ static final int MAX_INDEX_JOBS = 5;
+
+ final Object sync = new Object();
+
+ final SetMultimap<URI, URI> outgoingReferences = HashMultimap.create();
+ final SetMultimap<URI, URI> incomingReferences = HashMultimap.create();
+
+ final SetMultimap<URI, URI> resourceToShards = HashMultimap.create();
+ final SetMultimap<URI, URI> shardToParents = HashMultimap.create();
+
+ // These are abstracted as URIs without extension
+ SetMultimap<URI, URI> aggregateOutgoingReferences;
+ SetMultimap<URI, URI> aggregateIncomingReferences;
+ SetMultimap<URI, URI> aggregateResourceToShards;
+ SetMultimap<URI, URI> aggregateShardToParents;
+ final SetMultimap<URI, String> shards = HashMultimap.create();
+
+ /**
+ * Initializes me.
+ */
+ AbstractCrossReferenceIndex() {
+ super();
+ }
+
+ //
+ // Queries
+ //
+
+ @Override
+ public ListenableFuture<SetMultimap<URI, URI>> getOutgoingCrossReferencesAsync() {
+ return afterIndex(getOutgoingCrossReferencesCallable());
+ }
+
+ @Override
+ public SetMultimap<URI, URI> getOutgoingCrossReferences() throws CoreException {
+ return sync(afterIndex(getOutgoingCrossReferencesCallable()));
+ }
+
+ Callable<SetMultimap<URI, URI>> getOutgoingCrossReferencesCallable() {
+ return sync(() -> ImmutableSetMultimap.copyOf(outgoingReferences));
+ }
+
+ @Override
+ public ListenableFuture<Set<URI>> getOutgoingCrossReferencesAsync(URI resourceURI) {
+ return afterIndex(getOutgoingCrossReferencesCallable(resourceURI));
+ }
+
+ @Override
+ public Set<URI> getOutgoingCrossReferences(URI resourceURI) throws CoreException {
+ return sync(afterIndex(getOutgoingCrossReferencesCallable(resourceURI)));
+ }
+
+ Callable<Set<URI>> getOutgoingCrossReferencesCallable(URI resourceURI) {
+ return sync(() -> {
+ String ext = resourceURI.fileExtension();
+ URI withoutExt = resourceURI.trimFileExtension();
+ Set<URI> result = getAggregateOutgoingCrossReferences().get(withoutExt).stream()
+ .map(uri -> uri.appendFileExtension(ext))
+ .collect(Collectors.toSet());
+
+ return Collections.unmodifiableSet(result);
+ });
+ }
+
+ SetMultimap<URI, URI> getAggregateOutgoingCrossReferences() {
+ SetMultimap<URI, URI> result;
+
+ synchronized (sync) {
+ if (aggregateOutgoingReferences == null) {
+ // Compute the aggregate now
+ aggregateOutgoingReferences = HashMultimap.create();
+ for (Map.Entry<URI, URI> next : outgoingReferences.entries()) {
+ aggregateOutgoingReferences.put(next.getKey().trimFileExtension(),
+ next.getValue().trimFileExtension());
+ }
+ }
+
+ result = aggregateOutgoingReferences;
+ }
+
+ return result;
+ }
+
+ @Override
+ public ListenableFuture<SetMultimap<URI, URI>> getIncomingCrossReferencesAsync() {
+ return afterIndex(getIncomingCrossReferencesCallable());
+ }
+
+ @Override
+ public SetMultimap<URI, URI> getIncomingCrossReferences() throws CoreException {
+ return sync(afterIndex(getIncomingCrossReferencesCallable()));
+ }
+
+ Callable<SetMultimap<URI, URI>> getIncomingCrossReferencesCallable() {
+ return sync(() -> ImmutableSetMultimap.copyOf(incomingReferences));
+ }
+
+ @Override
+ public ListenableFuture<Set<URI>> getIncomingCrossReferencesAsync(URI resourceURI) {
+ return afterIndex(getIncomingCrossReferencesCallable(resourceURI));
+ }
+
+ @Override
+ public Set<URI> getIncomingCrossReferences(URI resourceURI) throws CoreException {
+ return sync(afterIndex(getIncomingCrossReferencesCallable(resourceURI)));
+ }
+
+ Callable<Set<URI>> getIncomingCrossReferencesCallable(URI resourceURI) {
+ return sync(() -> {
+ String ext = resourceURI.fileExtension();
+ URI withoutExt = resourceURI.trimFileExtension();
+ Set<URI> result = getAggregateIncomingCrossReferences().get(withoutExt).stream()
+ .map(uri -> uri.appendFileExtension(ext))
+ .collect(Collectors.toSet());
+
+ return Collections.unmodifiableSet(result);
+ });
+ }
+
+ SetMultimap<URI, URI> getAggregateIncomingCrossReferences() {
+ SetMultimap<URI, URI> result;
+
+ synchronized (sync) {
+ if (aggregateIncomingReferences == null) {
+ // Compute the aggregate now
+ aggregateIncomingReferences = HashMultimap.create();
+ for (Map.Entry<URI, URI> next : incomingReferences.entries()) {
+ aggregateIncomingReferences.put(next.getKey().trimFileExtension(),
+ next.getValue().trimFileExtension());
+ }
+ }
+
+ result = aggregateIncomingReferences;
+ }
+
+ return result;
+ }
+
+ @Override
+ public ListenableFuture<Boolean> isShardAsync(URI resourceURI) {
+ return afterIndex(getIsShardCallable(resourceURI));
+ }
+
+ @Override
+ public boolean isShard(URI resourceURI) throws CoreException {
+ return sync(afterIndex(getIsShardCallable(resourceURI)));
+ }
+
+ final <V> V sync(Future<V> future) throws CoreException {
+ try {
+ return future.get();
+ } catch (InterruptedException e) {
+ throw new CoreException(Status.CANCEL_STATUS);
+ } catch (ExecutionException e) {
+ throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to access the resource shard index", e));
+ }
+ }
+
+ Callable<Boolean> getIsShardCallable(URI shardURI) {
+ return sync(() -> isShard0(shardURI.trimFileExtension()));
+ }
+
+ boolean isShard0(URI uriWithoutExtension) {
+ return !shards.get(uriWithoutExtension).isEmpty();
+ }
+
+ void setShard(URI resourceURI, boolean isShard) {
+ if (isShard) {
+ shards.put(resourceURI.trimFileExtension(), resourceURI.fileExtension());
+ } else {
+ shards.remove(resourceURI.trimFileExtension(), resourceURI.fileExtension());
+ }
+ }
+
+ @Override
+ public ListenableFuture<SetMultimap<URI, URI>> getShardsAsync() {
+ return afterIndex(getShardsCallable());
+ }
+
+ @Override
+ public SetMultimap<URI, URI> getShards() throws CoreException {
+ return sync(afterIndex(getShardsCallable()));
+ }
+
+ Callable<SetMultimap<URI, URI>> getShardsCallable() {
+ return sync(() -> ImmutableSetMultimap.copyOf(resourceToShards));
+ }
+
+ @Override
+ public ListenableFuture<Set<URI>> getShardsAsync(URI resourceURI) {
+ return afterIndex(getShardsCallable(resourceURI));
+ }
+
+ @Override
+ public Set<URI> getShards(URI resourceURI) throws CoreException {
+ return sync(afterIndex(getShardsCallable(resourceURI)));
+ }
+
+ Callable<Set<URI>> getShardsCallable(URI shardURI) {
+ return sync(() -> {
+ String ext = shardURI.fileExtension();
+ URI withoutExt = shardURI.trimFileExtension();
+ Set<URI> result = getAggregateShards().get(withoutExt).stream()
+ // Only those that actually are shards
+ .filter(AbstractCrossReferenceIndex.this::isShard0)
+ .map(uri -> uri.appendFileExtension(ext))
+ .collect(Collectors.toSet());
+
+ return Collections.unmodifiableSet(result);
+ });
+ }
+
+ SetMultimap<URI, URI> getAggregateShards() {
+ SetMultimap<URI, URI> result;
+
+ synchronized (sync) {
+ if (aggregateResourceToShards == null) {
+ // Compute the aggregate now
+ aggregateResourceToShards = HashMultimap.create();
+ for (Map.Entry<URI, URI> next : resourceToShards.entries()) {
+ aggregateResourceToShards.put(next.getKey().trimFileExtension(),
+ next.getValue().trimFileExtension());
+ }
+ }
+
+ result = aggregateResourceToShards;
+ }
+
+ return result;
+ }
+
+ @Override
+ public ListenableFuture<Set<URI>> getParentsAsync(URI shardURI) {
+ return afterIndex(getParentsCallable(shardURI));
+ }
+
+ @Override
+ public Set<URI> getParents(URI shardURI) throws CoreException {
+ return sync(afterIndex(getParentsCallable(shardURI)));
+ }
+
+ Callable<Set<URI>> getParentsCallable(URI shardURI) {
+ return sync(() -> {
+ Set<URI> result;
+ URI withoutExt = shardURI.trimFileExtension();
+
+ // If it's not a shard, it has no parents, by definition
+ if (!isShard0(withoutExt)) {
+ result = Collections.emptySet();
+ } else {
+ String ext = shardURI.fileExtension();
+ result = getAggregateShardToParents().get(withoutExt).stream()
+ .map(uri -> uri.appendFileExtension(ext))
+ .collect(Collectors.toSet());
+ result = Collections.unmodifiableSet(result);
+ }
+
+ return result;
+ });
+ }
+
+ SetMultimap<URI, URI> getAggregateShardToParents() {
+ SetMultimap<URI, URI> result;
+
+ synchronized (sync) {
+ if (aggregateShardToParents == null) {
+ // Compute the aggregate now
+ aggregateShardToParents = HashMultimap.create();
+ for (Map.Entry<URI, URI> next : shardToParents.entries()) {
+ aggregateShardToParents.put(next.getKey().trimFileExtension(),
+ next.getValue().trimFileExtension());
+ }
+ }
+
+ result = aggregateShardToParents;
+ }
+
+ return result;
+ }
+
+ @Override
+ public ListenableFuture<Set<URI>> getRootsAsync(URI shardURI) {
+ return afterIndex(getRootsCallable(shardURI));
+ }
+
+ @Override
+ public Set<URI> getRoots(URI shardURI) throws CoreException {
+ return sync(afterIndex(getRootsCallable(shardURI)));
+ }
+
+ Callable<Set<URI>> getRootsCallable(URI shardURI) {
+ return sync(() -> {
+ Set<URI> result;
+ URI withoutExt = shardURI.trimFileExtension();
+
+ // If it's not a shard, it has no roots, by definition
+ if (!isShard0(withoutExt)) {
+ result = Collections.emptySet();
+ } else {
+ // TODO: Cache this?
+ ImmutableSet.Builder<URI> resultBuilder = ImmutableSet.builder();
+
+ SetMultimap<URI, URI> shardToParents = getAggregateShardToParents();
+
+ // Breadth-first search of the parent graph
+ Queue<URI> queue = Lists.newLinkedList();
+ Set<URI> cycleDetect = Sets.newHashSet();
+ String ext = shardURI.fileExtension();
+ queue.add(withoutExt);
+
+ for (URI next = queue.poll(); next != null; next = queue.poll()) {
+ if (cycleDetect.add(next)) {
+ if (shardToParents.containsKey(next)) {
+ queue.addAll(shardToParents.get(next));
+ } else {
+ // It's a root
+ resultBuilder.add(next.appendFileExtension(ext));
+ }
+ }
+ }
+
+ result = resultBuilder.build();
+ }
+
+ return result;
+ });
+ }
+
+ final <V> Callable<V> sync(Callable<V> callable) {
+ return new SyncCallable<V>() {
+ @Override
+ protected V doCall() throws Exception {
+ return callable.call();
+ }
+ };
+ }
+
+ //
+ // Indexing
+ //
+
+ abstract <V> ListenableFuture<V> afterIndex(Callable<V> callable);
+
+ //
+ // Nested types
+ //
+
+ private abstract class SyncCallable<V> implements Callable<V> {
+ @Override
+ public final V call() throws Exception {
+ synchronized (sync) {
+ return doCall();
+ }
+ }
+
+ protected abstract V doCall() throws Exception;
+ }
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/CrossReferenceIndex.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/CrossReferenceIndex.java
new file mode 100644
index 00000000000..c51c3ce56fd
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/CrossReferenceIndex.java
@@ -0,0 +1,226 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.internal.resource;
+
+import java.io.InputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.stream.Collectors;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.papyrus.infra.emf.Activator;
+import org.eclipse.papyrus.infra.emf.resource.index.IWorkspaceModelIndexProvider;
+import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndex;
+import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndex.PersistentIndexHandler;
+import org.xml.sax.helpers.DefaultHandler;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * An index of cross-resource references in the workspace.
+ */
+public class CrossReferenceIndex extends AbstractCrossReferenceIndex {
+
+ private static final CrossReferenceIndex INSTANCE = new CrossReferenceIndex();
+
+ private final WorkspaceModelIndex<CrossReferencedFile> index;
+
+ /**
+ * Not instantiable by clients.
+ */
+ private CrossReferenceIndex() {
+ super();
+
+ // TODO: Is there a constant somewhere for the XMI content-type?
+ index = new WorkspaceModelIndex<CrossReferencedFile>(
+ "papyrusCrossRefs", //$NON-NLS-1$
+ "org.eclipse.emf.ecore.xmi", //$NON-NLS-1$
+ null, indexer(), MAX_INDEX_JOBS);
+ }
+
+ public void dispose() {
+ index.dispose();
+ }
+
+ public static CrossReferenceIndex getInstance() {
+ return INSTANCE;
+ }
+
+ //
+ // Indexing
+ //
+
+ <V> ListenableFuture<V> afterIndex(Callable<V> callable) {
+ return index.afterIndex(callable);
+ }
+
+ private void runIndexHandler(IFile file, URI resourceURI, DefaultHandler handler) {
+ try (InputStream input = file.getContents()) {
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setValidating(false);
+ factory.setNamespaceAware(true);
+ SAXParser parser = factory.newSAXParser();
+
+ parser.parse(input, handler, resourceURI.toString());
+ } catch (Exception e) {
+ Activator.log.error("Exception in indexing resource", e); //$NON-NLS-1$
+ }
+ }
+
+ private boolean indexResource(IFile file, CrossReferencedFile index) {
+ boolean result = true;
+
+ final URI resourceURI = URI.createPlatformResourceURI(file.getFullPath().toString(), true);
+
+ synchronized (sync) {
+ // unindex the resource
+ unindexResource(file);
+
+ // update the forward mapping
+ resourceToShards.putAll(resourceURI, index.getShards());
+ outgoingReferences.putAll(resourceURI, index.getCrossReferences());
+
+ // and the reverse mapping
+ for (URI next : index.getShards()) {
+ shardToParents.put(next, resourceURI);
+ }
+ for (URI next : index.getCrossReferences()) {
+ incomingReferences.put(next, resourceURI);
+ }
+
+ // Is it actually a shard style? (we index all cross-resource containment)
+ setShard(resourceURI, index.isShard());
+ }
+
+ return result;
+ }
+
+ private CrossReferencedFile indexResource(IFile file) {
+ final URI resourceURI = URI.createPlatformResourceURI(file.getFullPath().toString(), true);
+
+ CrossReferenceIndexHandler handler = new CrossReferenceIndexHandler(resourceURI);
+ runIndexHandler(file, resourceURI, handler);
+
+ CrossReferencedFile result = new CrossReferencedFile(handler);
+ indexResource(file, result);
+
+ return result;
+ }
+
+ private void unindexResource(IFile file) {
+ final URI resourceURI = URI.createPlatformResourceURI(file.getFullPath().toString(), true);
+
+ synchronized (sync) {
+ // purge the aggregates (for model-set "resource without URI")
+ aggregateResourceToShards = null;
+ aggregateShardToParents = null;
+ aggregateOutgoingReferences = null;
+ aggregateIncomingReferences = null;
+ setShard(resourceURI, false);
+
+ // And remove all traces of this resource
+ resourceToShards.removeAll(resourceURI);
+ outgoingReferences.removeAll(resourceURI);
+
+ // the multimap's entry collection that underlies the key-set
+ // is modified as we go, so take a safe copy of the keys
+ for (URI next : new ArrayList<>(shardToParents.keySet())) {
+ shardToParents.remove(next, resourceURI);
+ }
+ for (URI next : new ArrayList<>(incomingReferences.keySet())) {
+ incomingReferences.remove(next, resourceURI);
+ }
+ }
+ }
+
+ private PersistentIndexHandler<CrossReferencedFile> indexer() {
+ return new PersistentIndexHandler<CrossReferencedFile>() {
+ @Override
+ public CrossReferencedFile index(IFile file) {
+ return indexResource(file);
+ }
+
+ @Override
+ public void unindex(IFile file) {
+ CrossReferenceIndex.this.unindexResource(file);
+ }
+
+ @Override
+ public boolean load(IFile file, CrossReferencedFile index) {
+ return CrossReferenceIndex.this.indexResource(file, index);
+ }
+ };
+ }
+
+ //
+ // Nested types
+ //
+
+ static final class CrossReferencedFile implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private boolean isShard;
+ private Set<String> crossReferences;
+ private Set<String> shards;
+
+ private transient Set<URI> crossReferenceURIs;
+ private transient Set<URI> shardURIs;
+
+ CrossReferencedFile(CrossReferenceIndexHandler handler) {
+ super();
+
+ this.isShard = handler.isShard();
+ this.crossReferences = handler.getCrossReferences();
+ this.shards = handler.getShards();
+ }
+
+ boolean isShard() {
+ return isShard;
+ }
+
+ Set<URI> getCrossReferences() {
+ if (crossReferenceURIs == null) {
+ crossReferenceURIs = crossReferences.stream()
+ .map(URI::createURI)
+ .collect(Collectors.toSet());
+ }
+ return crossReferenceURIs;
+ }
+
+ Set<URI> getShards() {
+ if (shardURIs == null) {
+ shardURIs = shards.stream()
+ .map(URI::createURI)
+ .collect(Collectors.toSet());
+ }
+ return shardURIs;
+ }
+ }
+
+ /**
+ * Index provider on the extension point.
+ */
+ public static final class IndexProvider implements IWorkspaceModelIndexProvider {
+ @Override
+ public WorkspaceModelIndex<?> get() {
+ return CrossReferenceIndex.INSTANCE.index;
+ }
+ }
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/CrossReferenceIndexHandler.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/CrossReferenceIndexHandler.java
new file mode 100644
index 00000000000..4b6dbe96778
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/CrossReferenceIndexHandler.java
@@ -0,0 +1,270 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.internal.resource;
+
+import static org.eclipse.papyrus.infra.tools.util.TypeUtils.as;
+
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.ecore.EcorePackage;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.Sets;
+
+/**
+ * XML parsing handler for extraction of resource cross-reference topology.
+ */
+public class CrossReferenceIndexHandler extends DefaultHandler {
+ private final URI fileURI;
+
+ private final boolean annotationOnly;
+
+ private Set<String> crossReferences = Sets.newHashSet();
+ private XMIElement shard;
+ private Set<String> shards = Sets.newHashSet();
+
+ // The (optional) parent references in the annotation
+ private Set<String> parents = Sets.newHashSet();
+
+ private BiMap<String, String> namespacePrefixes = HashBiMap.create();
+
+ private String xmiContainerQName;
+ private String xmiTypeQName;
+ private String eAnnotationSourceName;
+ private String eAnnotationReferencesName;
+
+ private XMIElement top;
+
+ /**
+ * Initializes me.
+ *
+ * @param fileURI
+ * the URI of the XMI file that I am parsing
+ */
+ public CrossReferenceIndexHandler(final URI fileURI) {
+ this(fileURI, false);
+ }
+
+ /**
+ * Initializes me.
+ *
+ * @param fileURI
+ * the URI of the XMI file that I am parsing
+ * @param annotationOnly
+ * whether we stop parsing as soon as the shard annotation has been processed
+ */
+ public CrossReferenceIndexHandler(URI fileURI, boolean annotationOnly) {
+ this.fileURI = fileURI;
+ this.annotationOnly = annotationOnly;
+ }
+
+ public URI getFileURI() {
+ return fileURI;
+ }
+
+ public Set<String> getCrossReferences() {
+ return crossReferences;
+ }
+
+ public boolean isShard() {
+ return shard != null;
+ }
+
+ public Set<String> getShards() {
+ return shards;
+ }
+
+ public Set<String> getParents() {
+ return parents;
+ }
+
+ @Override
+ public void startPrefixMapping(String prefix, String uri) throws SAXException {
+ namespacePrefixes.put(prefix, uri);
+
+ if ("xmi".equals(prefix)) { //$NON-NLS-1$
+ xmiTypeQName = qname(prefix, "type"); //$NON-NLS-1$
+ xmiContainerQName = qname(prefix, "XMI"); //$NON-NLS-1$
+ eAnnotationSourceName = "source"; //$NON-NLS-1$
+ eAnnotationReferencesName = "references"; //$NON-NLS-1$
+ }
+ }
+
+ protected final String qname(String prefix, String name) {
+ StringBuilder buf = new StringBuilder(prefix.length() + name.length() + 1);
+ return buf.append(prefix).append(':').append(name).toString();
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+ push(qName, attributes);
+
+ handleXMIElement(top, attributes);
+ }
+
+ protected final void push(String qName, Attributes attributes) {
+ top = new XMIElement(qName, attributes);
+ }
+
+ protected final XMIElement pop() {
+ XMIElement result = top;
+ if (top != null) {
+ top = top.parent;
+ }
+
+ return result;
+ }
+
+ protected void handleXMIElement(XMIElement element, Attributes attributes) throws SAXException {
+ if (element.getHREF() != null) {
+ URI xref = element.getHREF().trimFragment();
+
+ // Don't index internal references
+ if (!xref.equals(fileURI)) {
+ if (element.isContainment()) {
+ // Cross-resource containment is a shard relationship
+ shards.add(xref.toString());
+ } else if (isShard() && (element.parent == shard) && element.isRole(eAnnotationReferencesName)) {
+ // Handle shard parent resource reference. This is
+ // *not* a regular cross-resource reference
+ parents.add(xref.toString());
+ } else {
+ // Regular cross-resource reference
+ crossReferences.add(xref.toString());
+ }
+ }
+ } else if (element.isAnnotation()) {
+ String source = attributes.getValue(eAnnotationSourceName);
+ if (AbstractCrossReferenceIndex.SHARD_ANNOTATION_SOURCE.equals(source)) {
+ // This is a shard
+ shard = element;
+ }
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ XMIElement ended = pop();
+
+ if (annotationOnly && isShard() && (ended == shard)) {
+ // We have finished with shard linkage
+ throw new StopParsing();
+ }
+ }
+
+ //
+ // Nested types
+ //
+
+ protected final class XMIElement {
+ final XMIElement parent;
+
+ final String type;
+ final String role;
+ final String href;
+
+ private EClass eclass;
+
+ XMIElement(String qName, Attributes attributes) {
+ parent = top;
+
+ if ((parent == null) || parent.isXMIContainer()) {
+ // It's actually a type name
+ this.role = null;
+ this.type = qName;
+ } else {
+ this.role = qName;
+ this.type = attributes.getValue(xmiTypeQName);
+ }
+
+ this.href = attributes.getValue("href"); //$NON-NLS-1$
+ }
+
+ /** Am I the {@code xmi:XMI} container? */
+ boolean isXMIContainer() {
+ return (role == null) && ((type == null) || type.equals(xmiContainerQName));
+ }
+
+ boolean isRoot() {
+ return (parent == null) || parent.isXMIContainer();
+ }
+
+ boolean isRole(String roleName) {
+ return roleName.equals(role);
+ }
+
+ URI getHREF() {
+ return Strings.isNullOrEmpty(href) ? null : URI.createURI(href).resolve(fileURI);
+ }
+
+ boolean isAnnotation() {
+ return getEClass() == EcorePackage.Literals.EANNOTATION;
+ }
+
+ boolean isContainment() {
+ boolean result = false;
+
+ if (!isRoot()) {
+ EStructuralFeature feature = parent.getFeature(this.role);
+ result = (feature instanceof EReference)
+ && ((EReference) feature).isContainment();
+ }
+
+ return result;
+ }
+
+ EStructuralFeature getFeature(String role) {
+ EClass eclass = getEClass();
+
+ return (eclass == null) ? null : eclass.getEStructuralFeature(role);
+ }
+
+ EClass getEClass() {
+ if (eclass == null) {
+ if (type != null) {
+ Iterator<String> parts = Splitter.on(':').split(type).iterator();
+ String ns = namespacePrefixes.get(parts.next());
+ if (ns != null) {
+ EPackage epackage = EPackage.Registry.INSTANCE.getEPackage(ns);
+ if (epackage != null) {
+ eclass = as(epackage.getEClassifier(parts.next()), EClass.class);
+ }
+ }
+ } else if (parent != null) {
+ EClass parentEClass = parent.getEClass();
+ if (parentEClass != null) {
+ EReference ref = as(parentEClass.getEStructuralFeature(role), EReference.class);
+ if (ref != null) {
+ eclass = ref.getEReferenceType();
+ }
+ }
+ }
+ }
+
+ return eclass;
+ }
+ }
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/InternalIndexUtil.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/InternalIndexUtil.java
new file mode 100644
index 00000000000..7a36b289094
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/InternalIndexUtil.java
@@ -0,0 +1,73 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.internal.resource;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.papyrus.infra.core.internal.language.ILanguageModel;
+import org.eclipse.papyrus.infra.core.language.ILanguageService;
+import org.eclipse.papyrus.infra.core.resource.ModelSet;
+
+/**
+ * Miscellaneous internal utilities supporting or using the model indexing facilities.
+ */
+public class InternalIndexUtil {
+
+ /**
+ * Not instantiable by clients.
+ */
+ private InternalIndexUtil() {
+ super();
+ }
+
+ /**
+ * Determine the resource file extensions that contain "semantic model" content,
+ * using heuristics if necessary to make a best guess.
+ *
+ * @param resourceSet
+ * a resource set
+ * @return the set of file extensions for resources that are expected to contain
+ * semantic model content that is interesting to index
+ */
+ // in which the shard loading is important
+ public static Set<String> getSemanticModelFileExtensions(ResourceSet resourceSet) {
+ Set<String> result = null;
+
+ try {
+ if (resourceSet instanceof ModelSet) {
+ ILanguageService.getLanguageModels((ModelSet) resourceSet).stream()
+ .map(m -> m.getAdapter(ILanguageModel.class))
+ .filter(Objects::nonNull) // Not all models provide the adapter
+ .map(ILanguageModel::getModelFileExtension)
+ .filter(Objects::nonNull) // They really should provide this, though
+ .collect(Collectors.toSet());
+ }
+ } catch (Exception e) {
+ // We seem not to have the Language Service? That's fine
+ } catch (LinkageError e) {
+ // We seem to be operating without the Eclipse/OSGi run-time? That's fine
+ }
+
+ if (result == null) {
+ // Best guess for common Papyrus applications
+ result = Collections.singleton("uml"); //$NON-NLS-1$
+ }
+
+ return result;
+ }
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/OnDemandCrossReferenceIndex.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/OnDemandCrossReferenceIndex.java
new file mode 100644
index 00000000000..6b139df2a0b
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/OnDemandCrossReferenceIndex.java
@@ -0,0 +1,182 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.internal.resource;
+
+import java.io.InputStream;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.URIConverter;
+import org.eclipse.papyrus.infra.emf.Activator;
+import org.eclipse.papyrus.infra.emf.resource.ICrossReferenceIndex;
+import org.xml.sax.InputSource;
+
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.SetMultimap;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+/**
+ * An implementation of the {@link ICrossReferenceIndex Cross-Reference Index} API
+ * that determines shard relationships on-the-fly from pre-parsing of shard
+ * annotations references, where they are available. It does no other cross-reference
+ * indexing than this.
+ */
+public class OnDemandCrossReferenceIndex extends AbstractCrossReferenceIndex {
+
+ private static final ThreadGroup threadGroup = new ThreadGroup("XRefIndex"); //$NON-NLS-1$
+ private static final AtomicInteger threadCounter = new AtomicInteger();
+
+ private static final ListeningExecutorService executor = MoreExecutors.listeningDecorator(
+ new ThreadPoolExecutor(0, MAX_INDEX_JOBS, 60L, TimeUnit.SECONDS,
+ new SynchronousQueue<>(),
+ OnDemandCrossReferenceIndex::createThread));
+
+ private final Set<String> modelResourceFileExtensions;
+
+ /**
+ * Initializes me with the resource set in which I will index resources.
+ *
+ * @param resourceSet
+ * the contextual resource set, or {@code null} if none and
+ * the default heuristic- or otherwise-determined resources
+ * should be indexed on demand
+ */
+ public OnDemandCrossReferenceIndex(ResourceSet resourceSet) {
+ this(InternalIndexUtil.getSemanticModelFileExtensions(resourceSet));
+ }
+
+ /**
+ * Initializes me with the file extensions of resources that I will index.
+ *
+ * @param resourceFileExtensions
+ * the file extensions of resources to index on demand
+ */
+ public OnDemandCrossReferenceIndex(Set<String> resourceFileExtensions) {
+ super();
+
+ this.modelResourceFileExtensions = resourceFileExtensions;
+ }
+
+ private static Thread createThread(Runnable run) {
+ Thread result = new Thread(threadGroup, run, "XRefIndex-" + threadCounter.incrementAndGet());
+ result.setDaemon(true);
+ return result;
+ }
+
+ @Override
+ boolean isShard0(URI uriWithoutExtension) {
+ // Hook for on-demand indexing
+
+ // If the key isn't even there, we know that no interesting extension is
+ if (!shards.containsKey(uriWithoutExtension) ||
+ !intersects(shards.get(uriWithoutExtension), modelResourceFileExtensions)) {
+ index(uriWithoutExtension.appendFileExtension("uml"));
+ }
+
+ return super.isShard0(uriWithoutExtension);
+ }
+
+ private static <T> boolean intersects(Set<? extends T> a, Set<? extends T> b) {
+ return !a.isEmpty() && !b.isEmpty() && a.stream().anyMatch(b::contains);
+ }
+
+ @Override
+ Callable<SetMultimap<URI, URI>> getShardsCallable() {
+ // We don't parse on-the-fly for child shards; it requires scanning
+ // the whole resource
+ return () -> ImmutableSetMultimap.of();
+ }
+
+ @Override
+ Callable<SetMultimap<URI, URI>> getOutgoingCrossReferencesCallable() {
+ // We don't parse on-the-fly for cross-references; it requires scanning
+ // the whole resource
+ return () -> ImmutableSetMultimap.of();
+ }
+
+ @Override
+ Callable<SetMultimap<URI, URI>> getIncomingCrossReferencesCallable() {
+ // We don't parse on-the-fly for cross-references; it requires scanning
+ // the whole resource
+ return () -> ImmutableSetMultimap.of();
+ }
+
+ //
+ // Indexing
+ //
+
+ @Override
+ <V> ListenableFuture<V> afterIndex(Callable<V> callable) {
+ return executor.submit(callable);
+ }
+
+ void index(URI resourceURI) {
+ // Index this resource
+ Queue<URI> toIndex = Lists.newLinkedList();
+ toIndex.offer(resourceURI);
+
+ for (URI next = toIndex.poll(); next != null; next = toIndex.poll()) {
+ doIndex(next);
+
+ // And then, breadth-first, its parents that aren't already indexed
+ shardToParents.get(next).stream()
+ .filter(((Predicate<URI>) shards::containsKey).negate())
+ .forEach(toIndex::offer);
+ }
+ }
+
+ private void doIndex(URI resourceURI) {
+ // Only parse as far as the shard annotation, which occurs near the top
+ CrossReferenceIndexHandler handler = new CrossReferenceIndexHandler(resourceURI, true);
+
+ try (InputStream input = URIConverter.INSTANCE.createInputStream(resourceURI)) {
+ InputSource source = new InputSource(input);
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setValidating(false);
+ factory.setNamespaceAware(true);
+ SAXParser parser = factory.newSAXParser();
+
+ parser.parse(source, handler);
+ } catch (StopParsing stop) {
+ // Normal
+ } catch (Exception e) {
+ Activator.log.error("Failed to scan model resource for parent reference.", e); //$NON-NLS-1$
+ }
+
+ // Clear the aggregate map because we now have updates to include
+ aggregateShardToParents = null;
+
+ setShard(resourceURI, handler.isShard());
+ Set<URI> parents = handler.getParents().stream()
+ .map(URI::createURI)
+ .collect(Collectors.toSet());
+ shardToParents.putAll(resourceURI, parents);
+ }
+
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/StopParsing.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/StopParsing.java
new file mode 100644
index 00000000000..4ef170d6ad4
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/StopParsing.java
@@ -0,0 +1,30 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.internal.resource;
+
+/**
+ * A simple, recognizable throwable to bail out of XML parsing early.
+ */
+class StopParsing extends Error {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Initializes me.
+ */
+ public StopParsing() {
+ super();
+ }
+
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/IIndexSaveParticipant.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/IIndexSaveParticipant.java
new file mode 100644
index 00000000000..ab570a6bb22
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/IIndexSaveParticipant.java
@@ -0,0 +1,44 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.internal.resource.index;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.core.resources.ISaveParticipant;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndex;
+
+/**
+ * Protocol for an extension of the plug-in's {@link ISaveParticipant}
+ * that saves the current state of a {@link WorkspaceModelIndex}.
+ */
+public interface IIndexSaveParticipant {
+ /**
+ * Saves an {@code index} to a file.
+ *
+ * @param index
+ * the index to save
+ * @param store
+ * the output stream on which to save it. The caller may choose to
+ * {@link OutputStream#close() close} this stream but is not
+ * required to
+ *
+ * @throws IOException
+ * on failure to write to the {@code store}
+ * @throws CoreException
+ * on failure to save the {@code index}
+ */
+ void save(WorkspaceModelIndex<?> index, OutputStream output) throws IOException, CoreException;
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/IndexManager.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/IndexManager.java
new file mode 100644
index 00000000000..f13ff6e6830
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/IndexManager.java
@@ -0,0 +1,1075 @@
+/*****************************************************************************
+ * Copyright (c) 2014, 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.internal.resource.index;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.IResourceVisitor;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.core.runtime.content.IContentTypeManager;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.IJobChangeListener;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.jobs.JobChangeAdapter;
+import org.eclipse.papyrus.infra.core.utils.JobBasedFuture;
+import org.eclipse.papyrus.infra.core.utils.JobExecutorService;
+import org.eclipse.papyrus.infra.emf.Activator;
+import org.eclipse.papyrus.infra.emf.resource.index.IWorkspaceModelIndexListener;
+import org.eclipse.papyrus.infra.emf.resource.index.IWorkspaceModelIndexProvider;
+import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndex;
+import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndexEvent;
+import org.eclipse.papyrus.infra.tools.util.ReferenceCounted;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Queues;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * A controller of the indexing process for {@link WorkspaceModelIndex}s,
+ * including initial loading of an index and invocation of incremental
+ * indexing as resources in the workspace change.
+ */
+public class IndexManager {
+ private static final int MAX_INDEX_RETRIES = 3;
+
+ private static final IndexManager INSTANCE = new IndexManager();
+
+ private final IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
+ private final IResourceChangeListener workspaceListener = new WorkspaceListener();
+
+ private final Map<IProject, AbstractIndexJob> activeJobs = Maps.newHashMap();
+ private final ContentTypeService contentTypeService;
+
+ private Map<QualifiedName, InternalModelIndex> indices;
+ private JobWrangler jobWrangler;
+ private final CopyOnWriteArrayList<IndexListener> listeners = new CopyOnWriteArrayList<>();
+
+ static {
+ // This cannot be done in the constructor because indices that I load
+ // depend on the INSTANCE field already being set
+ INSTANCE.startManager();
+ }
+
+ public IndexManager() {
+ super();
+
+ contentTypeService = ContentTypeService.getInstance();
+ }
+
+ public static IndexManager getInstance() {
+ return INSTANCE;
+ }
+
+ public void dispose() {
+ if (indices != null) {
+ wsRoot.getWorkspace().removeResourceChangeListener(workspaceListener);
+ Job.getJobManager().cancel(this);
+
+ indices.values().forEach(InternalModelIndex::dispose);
+ // don't null the 'indices' to prevent starting again
+
+ ContentTypeService.dispose(contentTypeService);
+ }
+ }
+
+ public void startManager() {
+ if (indices != null) {
+ throw new IllegalStateException("index manager already started"); //$NON-NLS-1$
+ }
+
+ // Load our indices and find out from them how many
+ // jobs we need make available
+ indices = loadIndices();
+ int maxConcurrentJobs = indices.values().stream()
+ .mapToInt(InternalModelIndex::getMaxIndexJobs)
+ .max()
+ .orElse(5);
+ jobWrangler = new JobWrangler(maxConcurrentJobs);
+
+ // Start the indices now
+ indices.values().forEach(this::startIndex);
+
+ // And load or index from scratch
+ index(Arrays.asList(wsRoot.getProjects()));
+ wsRoot.getWorkspace().addResourceChangeListener(workspaceListener, IResourceChangeEvent.POST_CHANGE);
+ }
+
+ private void startIndex(InternalModelIndex index) {
+ index.start(this);
+ }
+
+ protected Map<QualifiedName, InternalModelIndex> loadIndices() {
+ Map<QualifiedName, InternalModelIndex> result = Maps.newHashMap();
+
+ for (IConfigurationElement config : Platform.getExtensionRegistry().getConfigurationElementsFor(Activator.PLUGIN_ID, "index")) { //$NON-NLS-1$
+ if ("indexProvider".equals(config.getName())) { //$NON-NLS-1$
+ try {
+ IWorkspaceModelIndexProvider provider = (IWorkspaceModelIndexProvider) config.createExecutableExtension("class"); //$NON-NLS-1$
+ WorkspaceModelIndex<?> index = provider.get();
+
+ if (index == null) {
+ Activator.log.warn("No index provided by " + config.getContributor().getName()); //$NON-NLS-1$
+ } else {
+ QualifiedName key = index.getIndexKey();
+ if (key == null) {
+ Activator.log.warn("Index has no key and will be ignored: " + index); //$NON-NLS-1$
+ } else {
+ InternalModelIndex internal = index;
+ // Ensure that the index can load classes from its
+ // persistent store that are defined in its owner's
+ // bundle
+ internal.setOwnerClassLoader(provider.getClass().getClassLoader());
+ result.put(key, internal);
+ }
+ }
+ } catch (ClassCastException e) {
+ Activator.log.error("Expected IWorkspaceModelIndexProvider in " + config.getContributor().getName(), e); //$NON-NLS-1$
+ } catch (CoreException e) {
+ Activator.log.log(e.getStatus());
+ } catch (Exception e) {
+ Activator.log.error("Failed to obtain index from provider in " + config.getContributor().getName(), e); //$NON-NLS-1$
+ }
+ }
+ }
+
+ return result;
+ }
+
+ IContentType[] getContentTypes(IFile file) {
+ return contentTypeService.getContentTypes(file);
+ }
+
+ /**
+ * Obtains an asynchronous future result that is scheduled to run after
+ * any pending indexing work has completed.
+ *
+ * @param index
+ * the index that is making the request
+ * @param callable
+ * the operation to schedule
+ *
+ * @return the future result of the operation
+ */
+ <V> ListenableFuture<V> afterIndex(InternalModelIndex index, Callable<V> callable) {
+ ListenableFuture<V> result;
+
+ if (Job.getJobManager().find(this).length == 0) {
+ // Result is available now
+ try {
+ result = Futures.immediateFuture(callable.call());
+ } catch (Exception e) {
+ result = Futures.immediateFailedFuture(e);
+ }
+ } else {
+ JobBasedFuture<V> job = new JobBasedFuture<V>("Wait for workspace model index") {
+ {
+ setSystem(true);
+ }
+
+ @Override
+ protected V compute(IProgressMonitor monitor) throws Exception {
+ V result;
+
+ Job.getJobManager().join(IndexManager.this, monitor);
+ result = callable.call();
+
+ return result;
+ }
+ };
+ job.schedule();
+ result = job;
+ }
+
+ return result;
+ }
+
+ void index(Collection<? extends IProject> projects) {
+ List<IndexProjectJob> jobs = Lists.newArrayListWithCapacity(projects.size());
+ for (IProject next : projects) {
+ jobs.add(new IndexProjectJob(next));
+ }
+ schedule(jobs);
+ }
+
+ void index(IProject project) {
+ schedule(new IndexProjectJob(project));
+ }
+
+ void process(IFile file) throws CoreException {
+ IProject project = file.getProject();
+
+ safeIterateIndices(index -> {
+ if (index.match(file)) {
+ index.process(file);
+ } else {
+ index.remove(project, file);
+ }
+ });
+ }
+
+ private void safeIterateIndices(IndexAction action) throws CoreException {
+ CoreException exception = null;
+
+ for (InternalModelIndex index : indices.values()) {
+ try {
+ action.apply(index);
+ } catch (CoreException e) {
+ if (exception != null) {
+ exception = e;
+ }
+ }
+ }
+
+ if (exception != null) {
+ throw exception;
+ }
+ }
+
+ void remove(IProject project, IFile file) throws CoreException {
+ safeIterateIndices(index -> index.remove(project, file));
+ }
+
+ void remove(IProject project) throws CoreException {
+ safeIterateIndices(index -> index.remove(project));
+ }
+
+ ReindexProjectJob reindex(IProject project, Collection<? extends IndexDelta> deltas) {
+ ReindexProjectJob result = null;
+
+ synchronized (activeJobs) {
+ AbstractIndexJob active = activeJobs.get(project);
+
+ if (active != null) {
+ switch (active.kind()) {
+ case REINDEX:
+ ReindexProjectJob reindex = (ReindexProjectJob) active;
+ reindex.addDeltas(deltas);
+ break;
+ case INDEX:
+ IndexProjectJob index = (IndexProjectJob) active;
+ ReindexProjectJob followup = index.getFollowup();
+ if (followup != null) {
+ followup.addDeltas(deltas);
+ } else {
+ followup = new ReindexProjectJob(project, deltas);
+ index.setFollowup(followup);
+ }
+ break;
+ case MASTER:
+ throw new IllegalStateException("Master job is in the active table."); //$NON-NLS-1$
+ }
+ } else {
+ // No active job. We'll need a new one
+ result = new ReindexProjectJob(project, deltas);
+ }
+ }
+
+ return result;
+ }
+
+ IResourceVisitor getWorkspaceVisitor(final IProgressMonitor monitor) {
+ return new IResourceVisitor() {
+
+ @Override
+ public boolean visit(IResource resource) throws CoreException {
+ if (resource.getType() == IResource.FILE) {
+ process((IFile) resource);
+ }
+
+ return !monitor.isCanceled();
+ }
+ };
+ }
+
+ private void schedule(Collection<? extends AbstractIndexJob> jobs) {
+ // Synchronize on the active jobs because this potentially alters the wrangler's follow-up job
+ synchronized (activeJobs) {
+ jobWrangler.add(jobs);
+ }
+ }
+
+ private void schedule(AbstractIndexJob job) {
+ // Synchronize on the active jobs because this potentially alters the wrangler's follow-up job
+ synchronized (activeJobs) {
+ jobWrangler.add(job);
+ }
+ }
+
+ public void addListener(WorkspaceModelIndex<?> index, IWorkspaceModelIndexListener listener) {
+ listeners.addIfAbsent(new IndexListener(index, listener));
+ }
+
+ public void removeListener(WorkspaceModelIndex<?> index, IWorkspaceModelIndexListener listener) {
+ listeners.removeIf(l -> Objects.equal(l.index, index) && Objects.equal(l.listener, listener));
+ }
+
+ private void notifyStarting(AbstractIndexJob indexJob) {
+ if (!listeners.isEmpty()) {
+ Map<WorkspaceModelIndex<?>, WorkspaceModelIndexEvent> events = Maps.newHashMap();
+ java.util.function.Function<WorkspaceModelIndex<?>, WorkspaceModelIndexEvent> eventFunction = index -> {
+ switch (indexJob.kind()) {
+ case INDEX:
+ return new WorkspaceModelIndexEvent(index, WorkspaceModelIndexEvent.ABOUT_TO_CALCULATE, indexJob.getProject());
+ case REINDEX:
+ return new WorkspaceModelIndexEvent(index, WorkspaceModelIndexEvent.ABOUT_TO_RECALCULATE, indexJob.getProject());
+ default:
+ throw new IllegalArgumentException(indexJob.kind().name());
+ }
+ };
+
+ switch (indexJob.kind()) {
+ case INDEX:
+ for (IndexListener next : listeners) {
+ try {
+ next.listener.indexAboutToCalculate(events.computeIfAbsent(next.index, eventFunction));
+ } catch (Exception e) {
+ Activator.log.error("Uncaught exception in index listsner.", e); //$NON-NLS-1$
+ }
+ }
+ break;
+ case REINDEX:
+ for (IndexListener next : listeners) {
+ try {
+ next.listener.indexAboutToRecalculate(events.computeIfAbsent(next.index, eventFunction));
+ } catch (Exception e) {
+ Activator.log.error("Uncaught exception in index listsner.", e); //$NON-NLS-1$
+ }
+ }
+ break;
+ case MASTER:
+ // Pass
+ break;
+ }
+ }
+ }
+
+ private void notifyFinished(AbstractIndexJob indexJob, IStatus status) {
+ if (!listeners.isEmpty()) {
+ if ((status != null) && (status.getSeverity() >= IStatus.ERROR)) {
+ Map<WorkspaceModelIndex<?>, WorkspaceModelIndexEvent> events = Maps.newHashMap();
+ java.util.function.Function<WorkspaceModelIndex<?>, WorkspaceModelIndexEvent> eventFunction = index -> new WorkspaceModelIndexEvent(index, WorkspaceModelIndexEvent.FAILED, indexJob.getProject());
+
+ for (IndexListener next : listeners) {
+ try {
+ next.listener.indexFailed(events.computeIfAbsent(next.index, eventFunction));
+ } catch (Exception e) {
+ Activator.log.error("Uncaught exception in index listsner.", e); //$NON-NLS-1$
+ }
+ }
+ } else {
+ Map<WorkspaceModelIndex<?>, WorkspaceModelIndexEvent> events = Maps.newHashMap();
+ java.util.function.Function<WorkspaceModelIndex<?>, WorkspaceModelIndexEvent> eventFunction = index -> {
+ switch (indexJob.kind()) {
+ case INDEX:
+ return new WorkspaceModelIndexEvent(index, WorkspaceModelIndexEvent.CALCULATED, indexJob.getProject());
+ case REINDEX:
+ return new WorkspaceModelIndexEvent(index, WorkspaceModelIndexEvent.RECALCULATED, indexJob.getProject());
+ default:
+ throw new IllegalArgumentException(indexJob.kind().name());
+ }
+ };
+
+ switch (indexJob.kind()) {
+ case INDEX:
+ for (IndexListener next : listeners) {
+ try {
+ next.listener.indexCalculated(events.computeIfAbsent(next.index, eventFunction));
+ } catch (Exception e) {
+ Activator.log.error("Uncaught exception in index listsner.", e); //$NON-NLS-1$
+ }
+ }
+ break;
+ case REINDEX:
+ for (IndexListener next : listeners) {
+ try {
+ next.listener.indexRecalculated(events.computeIfAbsent(next.index, eventFunction));
+ } catch (Exception e) {
+ Activator.log.error("Uncaught exception in index listsner.", e); //$NON-NLS-1$
+ }
+ }
+ break;
+ case MASTER:
+ // Pass
+ break;
+ }
+ }
+ }
+ }
+
+ //
+ // Nested types
+ //
+
+ private enum JobKind {
+ MASTER, INDEX, REINDEX;
+
+ boolean isSystem() {
+ return this != MASTER;
+ }
+ }
+
+ private abstract class AbstractIndexJob extends Job {
+ private final IProject project;
+
+ private volatile Semaphore permit;
+
+ AbstractIndexJob(String name, IProject project) {
+ this(name, project, true);
+ }
+
+ AbstractIndexJob(String name, IProject project, boolean register) {
+ super(name);
+
+ this.project = project;
+ this.permit = permit;
+
+ if ((project != null) && register) {
+ setRule(project);
+ synchronized (activeJobs) {
+ if (!activeJobs.containsKey(project)) {
+ activeJobs.put(project, this);
+ }
+ }
+ }
+
+ setSystem(kind().isSystem());
+ }
+
+ @Override
+ public boolean belongsTo(Object family) {
+ return family == IndexManager.this;
+ }
+
+ final IProject getProject() {
+ return project;
+ }
+
+ abstract JobKind kind();
+
+ @Override
+ protected final IStatus run(IProgressMonitor monitor) {
+ IStatus result;
+
+ try {
+ result = doRun(monitor);
+ } finally {
+ synchronized (activeJobs) {
+ AbstractIndexJob followup = getFollowup();
+
+ if (project != null) {
+ if (followup == null) {
+ activeJobs.remove(project);
+ } else {
+ activeJobs.put(project, followup);
+ }
+ }
+
+ if (followup != null) {
+ // Kick off the follow-up job
+ IndexManager.this.schedule(followup);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ final Semaphore getPermit() {
+ return permit;
+ }
+
+ final void setPermit(Semaphore permit) {
+ this.permit = permit;
+ }
+
+ protected abstract IStatus doRun(IProgressMonitor monitor);
+
+ protected AbstractIndexJob getFollowup() {
+ return null;
+ }
+ }
+
+ private class JobWrangler extends AbstractIndexJob {
+ private final Lock lock = new ReentrantLock();
+
+ private final Deque<AbstractIndexJob> queue = Queues.newArrayDeque();
+
+ private final AtomicBoolean active = new AtomicBoolean();
+ private final Semaphore indexJobSemaphore;
+
+ private volatile boolean cancelled;
+
+ JobWrangler(int maxConcurrentJobs) {
+ super("Workspace model indexer", null);
+
+ indexJobSemaphore = new Semaphore((maxConcurrentJobs <= 0) ? Integer.MAX_VALUE : maxConcurrentJobs);
+ }
+
+ @Override
+ JobKind kind() {
+ return JobKind.MASTER;
+ }
+
+ void add(AbstractIndexJob job) {
+ lock.lock();
+
+ try {
+ scheduleIfNeeded();
+ queue.add(job);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private void scheduleIfNeeded() {
+ if (active.compareAndSet(false, true)) {
+ // I am a new job
+ schedule();
+ }
+ }
+
+ void add(Iterable<? extends AbstractIndexJob> jobs) {
+ lock.lock();
+
+ try {
+ for (AbstractIndexJob next : jobs) {
+ add(next);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ protected void canceling() {
+ cancelled = true;
+ getThread().interrupt();
+ }
+
+ @Override
+ protected IStatus doRun(IProgressMonitor progressMonitor) {
+ final AtomicInteger pending = new AtomicInteger(); // How many permits have we issued?
+ final Condition pendingChanged = lock.newCondition();
+
+ final SubMonitor monitor = SubMonitor.convert(progressMonitor, IProgressMonitor.UNKNOWN);
+
+ IStatus result = Status.OK_STATUS;
+
+ IJobChangeListener listener = new JobChangeAdapter() {
+ private final Map<IProject, Integer> retries = Maps.newHashMap();
+
+ private Semaphore getIndexJobPermit(Job job) {
+ return (job instanceof AbstractIndexJob)
+ ? ((AbstractIndexJob) job).getPermit()
+ : null;
+ }
+
+ @Override
+ public void aboutToRun(IJobChangeEvent event) {
+ Job starting = event.getJob();
+
+ if (getIndexJobPermit(starting) == indexJobSemaphore) {
+ // one of mine is starting
+ AbstractIndexJob indexJob = (AbstractIndexJob) starting;
+ notifyStarting(indexJob);
+ }
+ }
+
+ @Override
+ public void done(IJobChangeEvent event) {
+ final Job finished = event.getJob();
+ if (getIndexJobPermit(finished) == indexJobSemaphore) {
+ try {
+ // one of mine has finished
+ AbstractIndexJob indexJob = (AbstractIndexJob) finished;
+ IProject project = indexJob.getProject();
+
+ notifyFinished(indexJob, event.getResult());
+
+ if (project != null) {
+ synchronized (retries) {
+ if ((event.getResult() != null) && (event.getResult().getSeverity() >= IStatus.ERROR)) {
+ // Indexing failed to complete. Need to re-build the index
+ int count = retries.containsKey(project) ? retries.get(project) : 0;
+ if (count++ < MAX_INDEX_RETRIES) {
+ // Only retry up to three times
+ index(project);
+ }
+ retries.put(project, ++count);
+ } else {
+ // Successful re-indexing. Forget the retries
+ retries.remove(project);
+ }
+ }
+ }
+ } finally {
+ // Release this job's permit for the next one in the queue
+ indexJobSemaphore.release();
+
+ // And it's no longer pending
+ pending.decrementAndGet();
+
+ lock.lock();
+ try {
+ pendingChanged.signalAll();
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+ }
+ };
+
+ getJobManager().addJobChangeListener(listener);
+
+ lock.lock();
+
+ try {
+ out: for (;;) {
+ monitor.setWorkRemaining(queue.size());
+
+ for (AbstractIndexJob next = queue.poll(); next != null; next = queue.poll()) {
+ lock.unlock();
+ try {
+ if (cancelled) {
+ throw new InterruptedException();
+ }
+
+ // Enforce the concurrent jobs limit
+ indexJobSemaphore.acquire();
+ next.setPermit(indexJobSemaphore);
+ pending.incrementAndGet();
+
+ // Now go
+ next.schedule();
+
+ monitor.worked(1);
+ } catch (InterruptedException e) {
+ // In case the interrupted happened some other way
+ cancelled = true;
+
+ // We were cancelled. Push this job back and re-schedule
+ lock.lock();
+ try {
+ queue.addFirst(next);
+ } finally {
+ lock.unlock();
+ }
+ result = Status.CANCEL_STATUS;
+ break out;
+ } finally {
+ lock.lock();
+ }
+ }
+
+ if ((pending.get() <= 0) && queue.isEmpty()) {
+ // Nothing left to wait for
+ break out;
+ } else if (pending.get() > 0) {
+ try {
+ if (cancelled) {
+ throw new InterruptedException();
+ }
+
+ pendingChanged.await();
+ } catch (InterruptedException e) {
+ // In case the interrupted happened some other way
+ cancelled = true;
+
+ // We were cancelled. Re-schedule
+ result = Status.CANCEL_STATUS;
+ break out;
+ }
+ }
+ }
+
+ // We've finished wrangling index jobs, for now
+ } finally {
+ try {
+ // If we were canceled then we re-schedule after a delay to recover
+ if (cancelled) {
+ // We cannot un-cancel a job, so we must replace ourselves with a new job
+ schedule(1000L);
+ cancelled = false;
+ } else {
+ // Don't think we're active any longer
+ active.compareAndSet(true, false);
+
+ // Double-check
+ if (!queue.isEmpty()) {
+ // We'll have to go around again
+ scheduleIfNeeded();
+ }
+ }
+ } finally {
+ lock.unlock();
+ getJobManager().removeJobChangeListener(listener);
+ }
+ }
+
+ return result;
+ }
+ }
+
+ private class IndexProjectJob extends AbstractIndexJob {
+ private ReindexProjectJob followup;
+
+ IndexProjectJob(IProject project) {
+ super("Indexing project " + project.getName(), project);
+ }
+
+ @Override
+ JobKind kind() {
+ return JobKind.INDEX;
+ }
+
+ @Override
+ protected IStatus doRun(IProgressMonitor monitor) {
+ IStatus result = Status.OK_STATUS;
+ final IProject project = getProject();
+
+ monitor.beginTask("Indexing models in project " + project.getName(), IProgressMonitor.UNKNOWN);
+
+ try {
+ if (project.isAccessible()) {
+ project.accept(getWorkspaceVisitor(monitor));
+ } else {
+ remove(project);
+ }
+
+ if (monitor.isCanceled()) {
+ result = Status.CANCEL_STATUS;
+ }
+ } catch (CoreException e) {
+ result = e.getStatus();
+ } finally {
+ monitor.done();
+ }
+
+ return result;
+ }
+
+ void setFollowup(ReindexProjectJob followup) {
+ this.followup = followup;
+ }
+
+ @Override
+ protected ReindexProjectJob getFollowup() {
+ return followup;
+ }
+ }
+
+ private class WorkspaceListener implements IResourceChangeListener {
+ @Override
+ public void resourceChanged(IResourceChangeEvent event) {
+ final Multimap<IProject, IndexDelta> deltas = ArrayListMultimap.create();
+
+ try {
+ event.getDelta().accept(new IResourceDeltaVisitor() {
+
+ @Override
+ public boolean visit(IResourceDelta delta) throws CoreException {
+ if (delta.getResource().getType() == IResource.FILE) {
+ IFile file = (IFile) delta.getResource();
+
+ switch (delta.getKind()) {
+ case IResourceDelta.CHANGED:
+ if ((delta.getFlags() & (IResourceDelta.SYNC | IResourceDelta.CONTENT | IResourceDelta.REPLACED)) != 0) {
+ // Re-index in place
+ deltas.put(file.getProject(), new IndexDelta(file, IndexDelta.DeltaKind.REINDEX));
+ }
+ break;
+ case IResourceDelta.REMOVED:
+ deltas.put(file.getProject(), new IndexDelta(file, IndexDelta.DeltaKind.UNINDEX));
+ break;
+ case IResourceDelta.ADDED:
+ deltas.put(file.getProject(), new IndexDelta(file, IndexDelta.DeltaKind.INDEX));
+ break;
+ }
+ }
+ return true;
+ }
+ });
+ } catch (CoreException e) {
+ Activator.log.error("Failed to analyze resource changes for re-indexing.", e); //$NON-NLS-1$
+ }
+
+ if (!deltas.isEmpty()) {
+ List<ReindexProjectJob> jobs = Lists.newArrayListWithCapacity(deltas.keySet().size());
+ for (IProject next : deltas.keySet()) {
+ ReindexProjectJob reindex = reindex(next, deltas.get(next));
+ if (reindex != null) {
+ jobs.add(reindex);
+ }
+ }
+ schedule(jobs);
+ }
+ }
+ }
+
+ private static final class IndexDelta {
+ private final IFile file;
+
+ private final DeltaKind kind;
+
+ IndexDelta(IFile file, DeltaKind kind) {
+ this.file = file;
+ this.kind = kind;
+ }
+
+ DeltaKind kind() {
+ return kind;
+ }
+
+ IFile file() {
+ return file;
+ }
+
+ //
+ // Nested types
+ //
+
+ enum DeltaKind {
+ INDEX, REINDEX, UNINDEX;
+ }
+ }
+
+ private class ReindexProjectJob extends AbstractIndexJob {
+ private final IProject project;
+ private final ConcurrentLinkedQueue<IndexDelta> deltas;
+
+ ReindexProjectJob(IProject project, Collection<? extends IndexDelta> deltas) {
+ super("Re-indexing project " + project.getName(), project);
+
+ this.project = project;
+ this.deltas = Queues.newConcurrentLinkedQueue(deltas);
+ }
+
+ @Override
+ JobKind kind() {
+ return JobKind.REINDEX;
+ }
+
+ void addDeltas(Iterable<? extends IndexDelta> deltas) {
+ Iterables.addAll(this.deltas, deltas);
+ }
+
+ @Override
+ protected IStatus doRun(IProgressMonitor monitor) {
+ IStatus result = Status.OK_STATUS;
+
+ monitor.beginTask("Re-indexing models in project " + project.getName(), IProgressMonitor.UNKNOWN);
+
+ try {
+ for (IndexDelta next = deltas.poll(); next != null; next = deltas.poll()) {
+ if (monitor.isCanceled()) {
+ result = Status.CANCEL_STATUS;
+ break;
+ }
+
+ try {
+ switch (next.kind()) {
+ case INDEX:
+ case REINDEX:
+ process(next.file());
+ break;
+ case UNINDEX:
+ remove(project, next.file());
+ break;
+ }
+ } catch (CoreException e) {
+ result = e.getStatus();
+ break;
+ } finally {
+ monitor.worked(1);
+ }
+ }
+ } finally {
+ monitor.done();
+ }
+
+ return result;
+ }
+
+ @Override
+ protected AbstractIndexJob getFollowup() {
+ // If I still have work to do, then I am my own follow-up
+ return deltas.isEmpty() ? null : this;
+ }
+ }
+
+ private static final class ContentTypeService extends ReferenceCounted<ContentTypeService> {
+ private static ContentTypeService instance = null;
+
+ private final ExecutorService serialExecution = new JobExecutorService();
+
+ private final IContentTypeManager mgr = Platform.getContentTypeManager();
+
+ private ContentTypeService() {
+ super();
+ }
+
+ synchronized static ContentTypeService getInstance() {
+ ContentTypeService result = instance;
+
+ if (result == null) {
+ result = new ContentTypeService();
+ instance = result;
+ }
+
+ return result.retain();
+ }
+
+ synchronized static void dispose(ContentTypeService service) {
+ service.release();
+ }
+
+ @Override
+ protected void dispose() {
+ serialExecution.shutdownNow();
+
+ if (instance == this) {
+ instance = null;
+ }
+ }
+
+ IContentType[] getContentTypes(final IFile file) {
+ Future<IContentType[]> futureResult = serialExecution.submit(new Callable<IContentType[]>() {
+
+ @Override
+ public IContentType[] call() {
+ IContentType[] result = null;
+ InputStream input = null;
+
+ if (file.isAccessible()) {
+ try {
+ input = file.getContents(true);
+ result = mgr.findContentTypesFor(input, file.getName());
+ } catch (Exception e) {
+ Activator.log.error("Failed to index file " + file.getFullPath(), e); //$NON-NLS-1$
+ } finally {
+ if (input != null) {
+ try {
+ input.close();
+ } catch (IOException e) {
+ Activator.log.error("Failed to close indexed file " + file.getFullPath(), e); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+ });
+
+ return Futures.getUnchecked(futureResult);
+ }
+ }
+
+ @FunctionalInterface
+ private interface IndexAction {
+ void apply(InternalModelIndex index) throws CoreException;
+ }
+
+ private static final class IndexListener {
+ final WorkspaceModelIndex<?> index;
+ final IWorkspaceModelIndexListener listener;
+
+ IndexListener(WorkspaceModelIndex<?> index, IWorkspaceModelIndexListener listener) {
+ super();
+
+ this.index = index;
+ this.listener = listener;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((index == null) ? 0 : index.hashCode());
+ result = prime * result + ((listener == null) ? 0 : listener.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof IndexListener)) {
+ return false;
+ }
+ IndexListener other = (IndexListener) obj;
+ if (index == null) {
+ if (other.index != null) {
+ return false;
+ }
+ } else if (!index.equals(other.index)) {
+ return false;
+ }
+ if (listener == null) {
+ if (other.listener != null) {
+ return false;
+ }
+ } else if (!listener.equals(other.listener)) {
+ return false;
+ }
+ return true;
+ }
+
+ }
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/IndexPersistenceManager.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/IndexPersistenceManager.java
new file mode 100644
index 00000000000..31864050568
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/IndexPersistenceManager.java
@@ -0,0 +1,256 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.internal.resource.index;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import org.eclipse.core.resources.ISaveContext;
+import org.eclipse.core.resources.ISaveParticipant;
+import org.eclipse.core.resources.ISavedState;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.papyrus.infra.emf.Activator;
+import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndex;
+
+import com.google.common.collect.Maps;
+
+/**
+ * Persistence manager for {@link WorkspaceModelIndex}es.
+ */
+public class IndexPersistenceManager {
+ private static final IPath INDEX_DIR = new Path("index").addTrailingSeparator(); //$NON-NLS-1$
+
+ private static final String ZIP_ENTRY = "Contents"; //$NON-NLS-1$
+
+ public static final IndexPersistenceManager INSTANCE = new IndexPersistenceManager();
+
+ private final Map<WorkspaceModelIndex<?>, IIndexSaveParticipant> workspaceIndices = Maps.newConcurrentMap();
+
+ // Index file paths relative to the plug-in state location, by index name
+ private Map<String, IPath> indexFiles = Collections.emptyMap();
+
+ /**
+ * Not instantiable by clients.
+ */
+ private IndexPersistenceManager() {
+ super();
+ }
+
+ /**
+ * Initializes the persistence manager with the previous Eclipse session's
+ * saved state.
+ *
+ * @param state
+ * the previous session's state, or {@code null} if none
+ * (for example, if this is the first run)
+ *
+ * @throws CoreException
+ * on failure to initialize the index persistence manager
+ */
+ public void initialize(ISavedState state) throws CoreException {
+ indexFiles = Collections.unmodifiableMap(
+ Stream.of(state.getFiles())
+ .collect(Collectors.toMap(IPath::toString, state::lookup)));
+ }
+
+ /**
+ * Registers a persistent model index.
+ *
+ * @param index
+ * the index to register
+ * @param saveParticipant
+ * its workspace-save delegate
+ *
+ * @return an input stream providing the previous session's index data, or {@code null}
+ * if none is available, in which case presumably a full indexing is required.
+ * The caller is required to {@link InputStream#close() close} this stream
+ */
+ public InputStream addIndex(WorkspaceModelIndex<?> index, IIndexSaveParticipant saveParticipant) {
+ ZipInputStream result = null;
+
+ workspaceIndices.put(index, saveParticipant);
+
+ IPath indexFile = indexFiles.get(index.getName());
+ File storeFile = (indexFile != null) ? getStoreFile(indexFile) : null;
+ if (storeFile != null) {
+ if (storeFile.exists()) {
+ try {
+ result = new ZipInputStream(new FileInputStream(storeFile));
+
+ // Get the Contents entry
+ result.getNextEntry();
+ } catch (Exception e) {
+ Activator.log.error("Failed to open index file for " + index.getName(), e); //$NON-NLS-1$
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Removes an index from the persistence manager.
+ *
+ * @param index
+ * the index to remove
+ */
+ public void removeIndex(WorkspaceModelIndex<?> index) {
+ workspaceIndices.remove(index);
+ }
+
+ private IPath getIndexLocation() {
+ return Activator.getDefault().getStateLocation().append(INDEX_DIR);
+ }
+
+ private File getStoreFile(IPath storePath) {
+ return Activator.getDefault().getStateLocation().append(storePath).toFile();
+ }
+
+ private IPath getStorePath(WorkspaceModelIndex<?> index, int saveNumber) {
+ return INDEX_DIR.append(index.getName()).addFileExtension(String.valueOf(saveNumber));
+ }
+
+ private IPath getStoreLocation(WorkspaceModelIndex<?> index, int saveNumber) {
+ return Activator.getDefault().getStateLocation().append(getStorePath(index, saveNumber));
+ }
+
+ /**
+ * Obtains a workspace save participant to which the bundle's main participant
+ * delegates the index portion of workspace save.
+ * <p>
+ * <b>Note</b> that this delegate must never tell the {@link ISaveContext} that
+ * it needs a {@linkplain ISaveContext#needSaveNumber() save number} or a
+ * {@linkplain ISaveContext#needDelta() delta} as that is the responsibility
+ * of the bundle's save participant. Also, it is only ever invoked on a
+ * full workspace save.
+ * </p>
+ *
+ * @return the workspace save participant delegate
+ */
+ public ISaveParticipant getSaveParticipant() {
+ return new ISaveParticipant() {
+
+ private Map<String, IPath> newIndexFiles;
+
+ @Override
+ public void prepareToSave(ISaveContext context) throws CoreException {
+ // Ensure that our state location index directory exists
+ File indexDirectory = getIndexLocation().toFile();
+ if (!indexDirectory.exists()) {
+ indexDirectory.mkdir();
+ }
+ }
+
+ @Override
+ public void saving(ISaveContext context) throws CoreException {
+ // Save our indices
+ for (Map.Entry<WorkspaceModelIndex<?>, IIndexSaveParticipant> next : workspaceIndices.entrySet()) {
+ WorkspaceModelIndex<?> index = next.getKey();
+ IIndexSaveParticipant save = next.getValue();
+
+ if (save != null) {
+ File storeFile = getStoreLocation(index, context.getSaveNumber()).toFile();
+
+ try (OutputStream store = createStoreOutput(storeFile)) {
+ save.save(index, store);
+ } catch (IOException e) {
+ storeFile.delete(); // In case there's something there, it can't be trusted
+ throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
+ "Failed to save index " + index.getName(), e)); //$NON-NLS-1$
+ }
+ }
+ }
+
+ // Compute the new index file mappings
+ newIndexFiles = workspaceIndices.keySet().stream()
+ .collect(Collectors.toMap(
+ WorkspaceModelIndex::getName,
+ index -> getStorePath(index, context.getSaveNumber())));
+
+ // Remove old index mappings
+ for (String next : indexFiles.keySet()) {
+ context.map(new Path(next), null);
+ }
+
+ // Add new index mappings
+ for (Map.Entry<String, IPath> next : newIndexFiles.entrySet()) {
+ context.map(new Path(next.getKey()), next.getValue());
+ }
+ }
+
+ private OutputStream createStoreOutput(File storeFile) throws IOException {
+ ZipOutputStream result = new ZipOutputStream(new FileOutputStream(storeFile));
+ ZipEntry entry = new ZipEntry(ZIP_ENTRY);
+ result.putNextEntry(entry);
+ return result;
+ }
+
+ @Override
+ public void doneSaving(ISaveContext context) {
+ // Delete the old index files
+ try {
+ indexFiles.values().forEach(p -> getStoreFile(p).delete());
+ } catch (Exception e) {
+ // This doesn't stop us proceeding
+ Activator.log.error("Failed to clean up old index files", e); //$NON-NLS-1$
+ }
+
+ // Grab our new index files
+ indexFiles = newIndexFiles;
+ newIndexFiles = null;
+ }
+
+ @Override
+ public void rollback(ISaveContext context) {
+ try {
+ if (newIndexFiles != null) {
+ // Delete the new save files and mappings that we created
+ newIndexFiles.values().stream()
+ .map(IndexPersistenceManager.this::getStoreFile)
+ .forEach(File::delete);
+
+ // And the mappings
+ newIndexFiles.keySet().stream()
+ .map(Path::new)
+ .forEach(p -> context.map(p, null));
+
+ newIndexFiles = null;
+
+ // Then restore the old mappings
+ indexFiles.forEach((name, location) -> context.map(new Path(name), location));
+ }
+ } catch (Exception e) {
+ Activator.log.error("Failed to roll back model indices.", e); //$NON-NLS-1$
+ }
+
+ }
+ };
+ }
+
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/InternalModelIndex.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/InternalModelIndex.java
new file mode 100644
index 00000000000..739f84e2135
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/internal/resource/index/InternalModelIndex.java
@@ -0,0 +1,118 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.internal.resource.index;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.util.concurrent.Callable;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndex;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Internal implementation details of a {@link WorkspaceModelIndex}.
+ */
+public abstract class InternalModelIndex {
+
+ private final QualifiedName indexKey;
+ private final int maxIndexJobs;
+
+ /** My manager. */
+ private IndexManager manager;
+
+ /** A class loader that knows the classes of the owner (bundle) context. */
+ private ClassLoader ownerClassLoader;
+
+ /**
+ * Initializes me.
+ */
+ public InternalModelIndex(QualifiedName indexKey, int maxIndexJobs) {
+ super();
+
+ this.indexKey = indexKey;
+ this.maxIndexJobs = maxIndexJobs;
+ }
+
+ /**
+ * Initializes me.
+ */
+ public InternalModelIndex(QualifiedName indexKey) {
+ this(indexKey, 0);
+ }
+
+ public final QualifiedName getIndexKey() {
+ return indexKey;
+ }
+
+ public final int getMaxIndexJobs() {
+ return maxIndexJobs;
+ }
+
+ protected final IContentType[] getContentTypes(IFile file) {
+ return manager.getContentTypes(file);
+ }
+
+ /**
+ * Obtains an asynchronous future result that is scheduled to run after
+ * any pending indexing work has completed.
+ *
+ * @param callable
+ * the operation to schedule
+ *
+ * @return the future result of the operation
+ */
+ protected <V> ListenableFuture<V> afterIndex(final Callable<V> callable) {
+ return manager.afterIndex(this, callable);
+ }
+
+ void setOwnerClassLoader(ClassLoader ownerClassLoader) {
+ this.ownerClassLoader = ownerClassLoader;
+ }
+
+ protected final ObjectInputStream createObjectInput(InputStream underlying) throws IOException {
+ return (ownerClassLoader == null)
+ ? new ObjectInputStream(underlying)
+ : new ObjectInputStream(underlying) {
+ @Override
+ protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
+ return Class.forName(desc.getName(), true, ownerClassLoader);
+ }
+ };
+ }
+
+ protected abstract void dispose();
+
+ void start(IndexManager manager) {
+ this.manager = manager;
+ start();
+ }
+
+ protected abstract void start();
+
+ protected abstract boolean match(IFile file);
+
+ protected abstract void process(IFile file) throws CoreException;
+
+ protected abstract void remove(IProject project, IFile file) throws CoreException;
+
+ protected abstract void remove(IProject project) throws CoreException;
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/ICrossReferenceIndex.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/ICrossReferenceIndex.java
new file mode 100644
index 00000000000..920eab7628f
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/ICrossReferenceIndex.java
@@ -0,0 +1,274 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.resource;
+
+import static org.eclipse.papyrus.infra.emf.internal.resource.InternalIndexUtil.getSemanticModelFileExtensions;
+
+import java.util.Set;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.plugin.EcorePlugin;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.papyrus.infra.emf.internal.resource.CrossReferenceIndex;
+import org.eclipse.papyrus.infra.emf.internal.resource.OnDemandCrossReferenceIndex;
+
+import com.google.common.collect.SetMultimap;
+import com.google.common.util.concurrent.ListenableFuture;
+
+
+/**
+ * API for an index of cross-resource proxy references in the workspace, especially
+ * containment proxies of the "shard" variety: controlled units that are not openable
+ * in their own editors but must be opened from the root resource of the controlled unit
+ * graph.
+ *
+ * @since 2.1
+ */
+public interface ICrossReferenceIndex {
+
+ /**
+ * Obtains the cross-reference index for the given resource set.
+ *
+ * @param resourceSet
+ * a resource-set in which resources are managed on which
+ * cross-reference queries are to be applied, or {@code null}
+ * if there is no contextual resource set, in which case
+ * the default heuristic- or otherwise-determined kinds of
+ * resources will be indexed
+ */
+ static ICrossReferenceIndex getInstance(ResourceSet resourceSet) {
+ ICrossReferenceIndex result;
+
+ if (!EcorePlugin.IS_ECLIPSE_RUNNING || Job.getJobManager().isSuspended()) {
+ // We cannot rely on jobs and the workspace to calculate the index
+ // in the background
+ result = new OnDemandCrossReferenceIndex(getSemanticModelFileExtensions(resourceSet));
+ } else {
+ result = CrossReferenceIndex.getInstance();
+ }
+
+ return result;
+ }
+
+ /**
+ * Asynchronously queries the mapping of URIs of resources to URIs of others
+ * that they cross-reference to.
+ *
+ * @return a future result of the mapping of resource URIs to cross-referenced URIs
+ */
+ ListenableFuture<SetMultimap<URI, URI>> getOutgoingCrossReferencesAsync();
+
+ /**
+ * Queries the mapping of URIs of resources to URIs of others
+ * that they cross-reference to.
+ *
+ * @return the mapping of resource URIs to cross-referenced URIs URIs
+ *
+ * @throws CoreException
+ * if the index either fails to compute the cross-references or if
+ * the calling thread is interrupted in waiting for the result
+ */
+ SetMultimap<URI, URI> getOutgoingCrossReferences() throws CoreException;
+
+ /**
+ * Asynchronously queries the URIs of other resources that a given resource
+ * cross-references to.
+ *
+ * @param resourceURI
+ * the URI of a resource
+ * @return a future result of the resource URIs that it cross-references to
+ */
+ ListenableFuture<Set<URI>> getOutgoingCrossReferencesAsync(URI resourceURI);
+
+ /**
+ * Queries the URIs of other resources that a given resource
+ * cross-references to.
+ *
+ * @param resourceURI
+ * the URI of a resource
+ * @return the resource URIs that it cross-references to
+ *
+ * @throws CoreException
+ * if the index either fails to compute the cross-references or if
+ * the calling thread is interrupted in waiting for the result
+ */
+ Set<URI> getOutgoingCrossReferences(URI resourceURI) throws CoreException;
+
+ /**
+ * Asynchronously queries the mapping of URIs of resources to URIs of others
+ * from which they are cross-referenced.
+ *
+ * @return a future result of the mapping of resource URIs to cross-referencing URIs
+ */
+ ListenableFuture<SetMultimap<URI, URI>> getIncomingCrossReferencesAsync();
+
+ /**
+ * Queries the mapping of URIs of resources to URIs of others
+ * from which they are cross-referenced.
+ *
+ * @return the mapping of resource URIs to cross-referencing URIs
+ *
+ * @throws CoreException
+ * if the index either fails to compute the cross-references or if
+ * the calling thread is interrupted in waiting for the result
+ */
+ SetMultimap<URI, URI> getIncomingCrossReferences() throws CoreException;
+
+ /**
+ * Asynchronously queries the URIs of other resources that cross-reference to
+ * a given resource.
+ *
+ * @param resourceURI
+ * the URI of a resource
+ * @return a future result of the resource URIs that cross-reference to it
+ */
+ ListenableFuture<Set<URI>> getIncomingCrossReferencesAsync(URI resourceURI);
+
+ /**
+ * Queries the URIs of other resources that cross-reference to
+ * a given resource.
+ *
+ * @param resourceURI
+ * the URI of a resource
+ * @return the resource URIs that cross-reference to it
+ *
+ * @throws CoreException
+ * if the index either fails to compute the cross-references or if
+ * the calling thread is interrupted in waiting for the result
+ */
+ Set<URI> getIncomingCrossReferences(URI resourceURI) throws CoreException;
+
+ /**
+ * Asynchronously queries whether a resource is a "shard".
+ *
+ * @param resourceURI
+ * the URI of a resource
+ * @return a future result of whether the resource is a "shard"
+ */
+ ListenableFuture<Boolean> isShardAsync(URI resourceURI);
+
+ /**
+ * Queries whether a resource is a "shard".
+ *
+ * @param resourceURI
+ * the URI of a resource
+ * @return whether the resource is a "shard"
+ *
+ * @throws CoreException
+ * if the index either fails to compute the shard-ness or if
+ * the calling thread is interrupted in waiting for the result
+ */
+ boolean isShard(URI resourceURI) throws CoreException;
+
+ /**
+ * Asynchronously queries the mapping of URIs of resources to URIs of shards that are their immediate
+ * children.
+ *
+ * @return a future result of the mapping of resource URIs to shard URIs
+ */
+ ListenableFuture<SetMultimap<URI, URI>> getShardsAsync();
+
+ /**
+ * Queries the mapping of URIs of resources to URIs of shards that are their immediate
+ * children.
+ *
+ * @return the mapping of resource URIs to shard URIs
+ *
+ * @throws CoreException
+ * if the index either fails to compute the shards or if
+ * the calling thread is interrupted in waiting for the result
+ */
+ SetMultimap<URI, URI> getShards() throws CoreException;
+
+ /**
+ * Asynchronously queries the URIs of resources that are immediate shards of a
+ * given resource.
+ *
+ * @param resourceURI
+ * the URI of a resource
+ * @return a future result of the URIs of shards that are its immediate children
+ */
+ ListenableFuture<Set<URI>> getShardsAsync(URI resourceURI);
+
+ /**
+ * Queries the URIs of resources that are immediate shards of a
+ * given resource.
+ *
+ * @param resourceURI
+ * the URI of a resource
+ * @return the URIs of shards that are its immediate children
+ *
+ * @throws CoreException
+ * if the index either fails to compute the shards or if
+ * the calling thread is interrupted in waiting for the result
+ */
+ Set<URI> getShards(URI resourceURI) throws CoreException;
+
+ /**
+ * Asynchronously queries URIs of resources that are immediate parents of a given
+ * (potential) shard resource.
+ *
+ * @param shardURI
+ * the URI of a potential shard resource. It needs not necessarily actually
+ * be a shard, in which case it trivially wouldn't have any parents
+ * @return the future result of the URIs of resources that are immediate parents of
+ * the shard
+ */
+ ListenableFuture<Set<URI>> getParentsAsync(URI shardURI);
+
+ /**
+ * Queries URIs of resources that are immediate parents of a given
+ * (potential) shard resource.
+ *
+ * @param shardURI
+ * the URI of a potential shard resource. It needs not necessarily actually
+ * be a shard, in which case it trivially wouldn't have any parents
+ * @return the URIs of resources that are immediate parents of
+ * the shard
+ *
+ * @throws CoreException
+ * if the index either fails to compute the parents or if
+ * the calling thread is interrupted in waiting for the result
+ */
+ Set<URI> getParents(URI shardURI) throws CoreException;
+
+ /**
+ * Asynchronously queries URIs of resources that are roots (ultimate parents) of a given
+ * (potential) shard resource.
+ *
+ * @param shardURI
+ * the URI of a potential shard resource. It needs not necessarily actually
+ * be a shard, in which case it trivially wouldn't have any parents
+ * @return the future result of the URIs of resources that are roots of its parent graph
+ */
+ ListenableFuture<Set<URI>> getRootsAsync(URI shardURI);
+
+ /**
+ * Queries URIs of resources that are roots (ultimate parents) of a given
+ * (potential) shard resource.
+ *
+ * @param shardURI
+ * the URI of a potential shard resource. It needs not necessarily actually
+ * be a shard, in which case it trivially wouldn't have any parents
+ * @return the URIs of resources that are roots of its parent graph
+ *
+ * @throws CoreException
+ * if the index either fails to compute the roots or if
+ * the calling thread is interrupted in waiting for the result
+ */
+ Set<URI> getRoots(URI shardURI) throws CoreException;
+
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/ShardResourceHelper.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/ShardResourceHelper.java
new file mode 100644
index 00000000000..29c004eb5c6
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/ShardResourceHelper.java
@@ -0,0 +1,418 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.resource;
+
+import static org.eclipse.papyrus.infra.emf.internal.resource.AbstractCrossReferenceIndex.SHARD_ANNOTATION_SOURCE;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.command.CommandWrapper;
+import org.eclipse.emf.common.command.IdentityCommand;
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.notify.Notification;
+import org.eclipse.emf.common.notify.Notifier;
+import org.eclipse.emf.common.notify.impl.AdapterImpl;
+import org.eclipse.emf.ecore.EAnnotation;
+import org.eclipse.emf.ecore.EModelElement;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EcoreFactory;
+import org.eclipse.emf.ecore.EcorePackage;
+import org.eclipse.emf.ecore.InternalEObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.emf.edit.command.AddCommand;
+import org.eclipse.emf.edit.command.RemoveCommand;
+import org.eclipse.emf.edit.domain.EditingDomain;
+import org.eclipse.papyrus.infra.emf.utils.EMFHelper;
+import org.eclipse.papyrus.infra.tools.util.TypeUtils;
+
+/**
+ * A convenience wrapper for {@link EObject}s and/or {@link Resource}s that
+ * are dependent "shard" units of a Papyrus model. A shard helper must
+ * always be {@linkplain #close() closed} after it is no longer needed,
+ * because it attaches adapters to the model.
+ *
+ * @since 2.1
+ */
+public class ShardResourceHelper implements AutoCloseable {
+
+ private final Resource resource;
+ private final EObject object;
+
+ private boolean closed;
+ private boolean initialized;
+
+ private EAnnotation annotation;
+ private Adapter annotationAdapter;
+
+ /**
+ * Initializes me on a shard {@code resource} that is expected to contain
+ * only one root element (it doesn't store multiple distinct sub-trees
+ * of the model).
+ *
+ * @param resource
+ * a "resource" resource
+ *
+ * @see #ShardResourceHelper(EObject)
+ */
+ public ShardResourceHelper(Resource resource) {
+ this(resource, null);
+ }
+
+ /**
+ * Initializes me on an {@code element} in a shard resource that uniquely
+ * identifies a sub-tree of potentially more than one stored in the resource.
+ * If there is any possibility that a resource stores multiple sub-trees,
+ * prefer this constructor over {@linkplain #ShardResourceHelper(Resource) the other}.
+ *
+ * @param element
+ * an element in a "resource" resource
+ */
+ public ShardResourceHelper(EObject element) {
+ this(element.eResource(), element);
+ }
+
+ private ShardResourceHelper(Resource resource, EObject object) {
+ super();
+
+ this.resource = resource;
+ this.object = object;
+ }
+
+ /**
+ * Is my resource a shard?
+ *
+ * @return whether my resource is a shard of its parent
+ */
+ public boolean isShard() {
+ return getAnnotation() != null;
+ }
+
+ /**
+ * Changes my resource from a shard to an independent controlled unit, or vice-versa.
+ * In the context of an editor and/or editing-domain, it is usually more appropriate
+ * to use the {@link #getSetShardCommand(boolean)} API for manipulation by command.
+ *
+ * @param isShard
+ * whether my resource should be a shard. If it already matches
+ * this state, then do nothing
+ *
+ * @see #getSetShardCommand(boolean)
+ */
+ public void setShard(boolean isShard) {
+ checkClosed();
+
+ if (isShard != isShard()) {
+ if (getAnnotation() != null) {
+ // We are un-sharding
+ EcoreUtil.remove(getAnnotation());
+ } else {
+ // We are sharding
+ EAnnotation annotation = EcoreFactory.eINSTANCE.createEAnnotation();
+ annotation.setSource(SHARD_ANNOTATION_SOURCE);
+ Notifier annotationOwner;
+
+ EObject shardElement = getShardElement();
+ if (shardElement instanceof EModelElement) {
+ // Add it to the shard element
+ ((EModelElement) shardElement).getEAnnotations().add(annotation);
+ annotationOwner = shardElement;
+ } else if (shardElement != null) {
+ // Add it after the shard element
+ int index = resource.getContents().indexOf(shardElement) + 1;
+ resource.getContents().add(index, annotation);
+ annotationOwner = resource;
+ } else {
+ // Try to add it after the principal model object
+ resource.getContents().add(Math.min(1, resource.getContents().size()), annotation);
+ annotationOwner = resource;
+ }
+
+ // In any case, the parent is the resource storing the element's container
+ if ((shardElement != null) && (shardElement.eContainer() != null)) {
+ annotation.getReferences().add(shardElement.eContainer());
+ }
+
+ setAnnotation(annotation);
+ attachAnnotationAdapter(annotationOwner);
+ }
+ }
+ }
+
+ /**
+ * Finds the element that is the root of the particular sub-tree stored in
+ * this resource, from the context provided by the client.
+ *
+ * @return the shard root element as best determined from the context, or
+ * {@code null} in the worst case that the resource is empty
+ */
+ private EObject getShardElement() {
+ checkClosed();
+
+ EObject result = null;
+
+ if (object != null) {
+ // Find the object in its content tree that is a root of our resource
+ for (result = object; result != null; result = result.eContainer()) {
+ InternalEObject internal = (InternalEObject) result;
+ if (internal.eDirectResource() == resource) {
+ // Found it
+ break;
+ }
+ }
+ }
+
+ if ((result == null) && !resource.getContents().isEmpty()) {
+ // Just take the first element as the shard element
+ result = resource.getContents().get(0);
+ }
+
+ return result;
+ }
+
+ /**
+ * Obtains a command to change my resource from a shard to an independent
+ * controlled unit, or vice-versa.
+ *
+ * @param isShard
+ * whether my resource should be a shard. If it already matches
+ * this state, then the resulting command will have no effect
+ *
+ * @return the set-shard command
+ *
+ * @see #setShard(boolean)
+ */
+ public Command getSetShardCommand(boolean isShard) {
+ Command result;
+
+ if (isShard() == isShard) {
+ result = IdentityCommand.INSTANCE;
+ } else if (getAnnotation() != null) {
+ // Delete the annotation
+ EAnnotation annotation = getAnnotation();
+ if (annotation.getEModelElement() != null) {
+ result = RemoveCommand.create(EMFHelper.resolveEditingDomain(annotation),
+ annotation.getEModelElement(),
+ EcorePackage.Literals.EMODEL_ELEMENT__EANNOTATIONS,
+ annotation);
+ } else {
+ result = new RemoveCommand(EMFHelper.resolveEditingDomain(resource),
+ resource.getContents(),
+ annotation);
+ }
+ } else {
+ // Create the annotation
+ EAnnotation annotation = EcoreFactory.eINSTANCE.createEAnnotation();
+ annotation.setSource(SHARD_ANNOTATION_SOURCE);
+
+ EditingDomain domain;
+ EObject shardElement = getShardElement();
+ Notifier annotationOwner;
+
+ if (shardElement instanceof EModelElement) {
+ // Add it to the shard element
+ domain = EMFHelper.resolveEditingDomain(shardElement);
+ result = AddCommand.create(domain, shardElement,
+ EcorePackage.Literals.EMODEL_ELEMENT__EANNOTATIONS,
+ annotation);
+ annotationOwner = shardElement;
+ } else if (shardElement != null) {
+ // Add it after the shard element
+ int index = resource.getContents().indexOf(shardElement) + 1;
+ domain = EMFHelper.resolveEditingDomain(shardElement);
+ result = new AddCommand(domain, resource.getContents(), annotation, index);
+ annotationOwner = resource;
+ } else {
+ // Try to add it after the principal model object
+ domain = EMFHelper.resolveEditingDomain(resource);
+ int index = Math.min(1, resource.getContents().size());
+ result = new AddCommand(domain, resource.getContents(), annotation, index);
+ annotationOwner = resource;
+ }
+
+ // In any case, the parent is the resource storing the element's container
+ if ((shardElement != null) && (shardElement.eContainer() != null)) {
+ result = result.chain(AddCommand.create(domain, annotation,
+ EcorePackage.Literals.EANNOTATION__REFERENCES,
+ shardElement.eContainer()));
+ }
+
+ // Ensure attachment of the adapter on first execution and record the
+ // annotation, if not already closed
+ result = new CommandWrapper(result) {
+ @Override
+ public void execute() {
+ super.execute();
+
+ if (!ShardResourceHelper.this.isClosed()) {
+ setAnnotation(annotation);
+ attachAnnotationAdapter(annotationOwner);
+ }
+ }
+ };
+ }
+
+ return result;
+ }
+
+ /**
+ * Closes me, ensuring at least that any adapter I have attached to the model
+ * that retains me is detached. Once I have been closed, I cannot be used
+ * any longer.
+ */
+ @Override
+ public void close() {
+ closed = true;
+
+ doClose();
+ }
+
+ protected void doClose() {
+ clearAnnotation();
+ detachAnnotationAdapter();
+ }
+
+ /**
+ * Queries whether I have been {@linkplain #close() closed}.
+ *
+ * @return whether I have been closed
+ */
+ public final boolean isClosed() {
+ return closed;
+ }
+
+ protected final void checkClosed() {
+ if (isClosed()) {
+ throw new IllegalStateException("closed"); //$NON-NLS-1$
+ }
+ }
+
+ private EAnnotation getAnnotation() {
+ checkClosed();
+
+ if (!initialized) {
+ setAnnotation(findAnnotation());
+ initialized = true;
+ }
+
+ return annotation;
+ }
+
+ private EAnnotation findAnnotation() {
+ EAnnotation result = null;
+
+ if (!resource.getContents().isEmpty()) {
+ EObject shardElement = getShardElement();
+ Notifier annotationOwner;
+
+ if (shardElement instanceof EModelElement) {
+ result = ((EModelElement) shardElement).getEAnnotation(SHARD_ANNOTATION_SOURCE);
+ annotationOwner = shardElement;
+ } else {
+ // Maybe it's just in the resource?
+ List<EObject> contents = resource.getContents();
+ annotationOwner = resource;
+
+ if (shardElement != null) {
+ int index = contents.indexOf(shardElement) + 1;
+ if (index < contents.size()) {
+ EAnnotation maybe = TypeUtils.as(contents.get(index), EAnnotation.class);
+ if ((maybe != null) && SHARD_ANNOTATION_SOURCE.equals(maybe.getSource())) {
+ // That's it
+ result = maybe;
+ }
+ }
+ }
+
+ if ((result == null) && (object == null)) {
+ // If we don't have a specific sub-tree in mind, look for any
+ // shard annotation
+ result = contents.stream()
+ .filter(EAnnotation.class::isInstance).map(EAnnotation.class::cast)
+ .filter(a -> SHARD_ANNOTATION_SOURCE.equals(a.getSource()))
+ .findFirst().orElse(null);
+ }
+ }
+
+ if (result != null) {
+ attachAnnotationAdapter(annotationOwner);
+ }
+ }
+
+ return result;
+ }
+
+ private void clearAnnotation() {
+ initialized = false;
+ setAnnotation(null);
+ }
+
+ private void setAnnotation(EAnnotation annotation) {
+ this.annotation = annotation;
+ }
+
+ private void attachAnnotationAdapter(Notifier annotationOwner) {
+ // If we still have the annotation, then it's still attached
+ if (annotationAdapter == null) {
+ annotationAdapter = new AdapterImpl() {
+ @Override
+ public void notifyChanged(Notification msg) {
+ if (msg.getEventType() == Notification.REMOVING_ADAPTER) {
+ // My target was unloaded
+ clearAnnotation();
+ } else if ((msg.getFeature() == EcorePackage.Literals.EMODEL_ELEMENT__EANNOTATIONS)
+ || ((msg.getNotifier() == resource) && (msg.getFeatureID(Resource.class) == Resource.RESOURCE__CONTENTS))) {
+
+ // Annotation of the model element or resource changed
+ boolean clear = false;
+
+ switch (msg.getEventType()) {
+ case Notification.SET:
+ case Notification.UNSET:
+ case Notification.REMOVE:
+ clear = (msg.getOldValue() == getAnnotation());
+ break;
+ case Notification.ADD:
+ case Notification.ADD_MANY:
+ // If we don't have an annotation, we'll try to find it
+ clear = getAnnotation() == null;
+ break;
+ case Notification.REMOVE_MANY:
+ clear = ((Collection<?>) msg.getOldValue()).contains(getAnnotation());
+ break;
+ }
+
+ if (clear) {
+ // In case the annotation moved or was replaced,
+ // we'll compute it again on-the-fly
+ clearAnnotation();
+ }
+ }
+ }
+ };
+
+ annotationOwner.eAdapters().add(annotationAdapter);
+ }
+ }
+
+ private void detachAnnotationAdapter() {
+ if (annotationAdapter != null) {
+ Adapter adapter = annotationAdapter;
+ annotationAdapter = null;
+ adapter.getTarget().eAdapters().remove(adapter);
+ }
+ }
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/ShardResourceLocator.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/ShardResourceLocator.java
new file mode 100644
index 00000000000..397e693707a
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/ShardResourceLocator.java
@@ -0,0 +1,178 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.resource;
+
+import static org.eclipse.papyrus.infra.emf.internal.resource.InternalIndexUtil.getSemanticModelFileExtensions;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.emf.common.util.TreeIterator;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.InternalEObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl.ResourceLocator;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.emf.ecore.util.InternalEList;
+import org.eclipse.papyrus.infra.emf.Activator;
+
+/**
+ * A {@link ResourceLocator} that can be used with any {@link ResourceSet}
+ * to ensure that when a shard resource is demand-loaded by proxy resolution,
+ * it is loaded from the top down to ensure that dependencies such as profile
+ * applications in UML models are ensured before loading the shard.
+ *
+ * @since 2.1
+ */
+public class ShardResourceLocator extends ResourceLocator {
+
+ private final Set<Resource> inDemandLoadHelper = new HashSet<>();
+
+ private final Supplier<? extends ICrossReferenceIndex> index;
+
+ private final Set<String> semanticModelExtensions;
+
+ /**
+ * Installs me in the given resource set. I use the best available
+ * {@link ICrossReferenceIndex} for resolution of shard relationships.
+ *
+ * @param resourceSet
+ * the resource set for which I shall provide
+ */
+ public ShardResourceLocator(ResourceSetImpl resourceSet) {
+ this(resourceSet, () -> ICrossReferenceIndex.getInstance(resourceSet));
+ }
+
+ /**
+ * Installs me in the given resource set with a particular {@code index}.
+ *
+ * @param resourceSet
+ * the resource set for which I shall provide
+ * @param index
+ * the index to use for resolving shard relationships
+ */
+ public ShardResourceLocator(ResourceSetImpl resourceSet, ICrossReferenceIndex index) {
+ this(resourceSet, () -> index);
+ }
+
+ /**
+ * Installs me in the given resource set with a dynamic {@code index} supplier.
+ *
+ * @param resourceSet
+ * the resource set for which I shall provide
+ * @param index
+ * a dynamic supplier of the index to use for resolving shard relationships
+ */
+ public ShardResourceLocator(ResourceSetImpl resourceSet, Supplier<? extends ICrossReferenceIndex> index) {
+ super(resourceSet);
+
+ this.index = index;
+ this.semanticModelExtensions = getSemanticModelFileExtensions(resourceSet);
+ }
+
+ /**
+ * Handles shard resources by loading their roots first and the chain(s) of resources
+ * all the way down to the shard.
+ */
+ @Override
+ public Resource getResource(URI uri, boolean loadOnDemand) {
+ if (loadOnDemand && uri.isPlatformResource()
+ && semanticModelExtensions.contains(uri.fileExtension())) {
+
+ // Is it already loaded? This saves blocking on the cross-reference index
+ Resource existing = getResource(uri, false);
+ if ((existing == null) || !existing.isLoaded()) {
+ // Do our peculiar process
+ handleShard(uri);
+ }
+ }
+
+ return basicGetResource(uri, loadOnDemand);
+ }
+
+ /**
+ * Handles the case of demand-loading of a shard by loading it from the root resource
+ * on down.
+ *
+ * @param uri
+ * the URI of a resource that may be a shard
+ */
+ protected void handleShard(URI uri) {
+ try {
+ Set<URI> parents = index.get().getParents(uri);
+
+ if (!parents.isEmpty()) {
+ // Load from the root resource down
+ parents.stream()
+ .filter(this::notLoaded)
+ .forEach(r -> loadParentResource(r, uri));
+ }
+ } catch (CoreException e) {
+ Activator.log.log(e.getStatus());
+ }
+ }
+
+ protected boolean notLoaded(URI uri) {
+ Resource resource = resourceSet.getResource(uri, false);
+ return (resource == null) || !resource.isLoaded();
+ }
+
+ protected void loadParentResource(URI parentURI, URI shard) {
+ // This operates recursively on the demand-load helper
+ Resource parent = resourceSet.getResource(parentURI, true);
+
+ // Unlock the shardresource, now
+ inDemandLoadHelper.remove(shard);
+
+ // Scan for the cross-resource containment
+ URI shardURI = normalize(shard);
+ for (TreeIterator<EObject> iter = EcoreUtil.getAllProperContents(parent, false); iter.hasNext();) {
+ EObject next = iter.next();
+ if (next.eIsProxy()) {
+ // Must always only compare normalized URIs to determine 'same resource'
+ URI proxyURI = normalize(((InternalEObject) next).eProxyURI());
+ if (proxyURI.trimFragment().equals(shardURI)) {
+ // This is our parent object
+ EObject parentObject = next.eContainer();
+
+ // Resolve the reference
+ EReference containment = next.eContainmentFeature();
+ if (!containment.isMany()) {
+ // Easy case
+ parentObject.eGet(containment, true);
+ } else {
+ InternalEList<?> list = (InternalEList<?>) parentObject.eGet(containment);
+ int index = list.basicIndexOf(next);
+ if (index >= 0) {
+ // Resolve it
+ list.get(index);
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ protected URI normalize(URI uri) {
+ return resourceSet.getURIConverter().normalize(uri);
+ }
+
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/index/IWorkspaceModelIndexProvider.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/index/IWorkspaceModelIndexProvider.java
new file mode 100644
index 00000000000..fb18c57198b
--- /dev/null
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/index/IWorkspaceModelIndexProvider.java
@@ -0,0 +1,27 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.resource.index;
+
+import java.util.function.Supplier;
+
+/**
+ * A provider of a model index on the <tt>org.eclipse.papyrus.infra.emf.index</tt>
+ * extension point.
+ *
+ * @since 2.1
+ */
+@FunctionalInterface
+public interface IWorkspaceModelIndexProvider extends Supplier<WorkspaceModelIndex<?>> {
+ // Nothing to add
+}
diff --git a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/index/WorkspaceModelIndex.java b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/index/WorkspaceModelIndex.java
index 98e6b063472..91b17fc5ef3 100644
--- a/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/index/WorkspaceModelIndex.java
+++ b/plugins/infra/emf/org.eclipse.papyrus.infra.emf/src/org/eclipse/papyrus/infra/emf/resource/index/WorkspaceModelIndex.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2014, 2015 Christian W. Damus and others.
+ * Copyright (c) 2014, 2016 Christian W. Damus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -15,63 +15,41 @@ package org.eclipse.papyrus.infra.emf.resource.index;
import java.io.IOException;
import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Deque;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IResourceChangeEvent;
-import org.eclipse.core.resources.IResourceChangeListener;
-import org.eclipse.core.resources.IResourceDelta;
-import org.eclipse.core.resources.IResourceDeltaVisitor;
-import org.eclipse.core.resources.IResourceVisitor;
-import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.QualifiedName;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.content.IContentType;
-import org.eclipse.core.runtime.content.IContentTypeManager;
-import org.eclipse.core.runtime.jobs.IJobChangeEvent;
-import org.eclipse.core.runtime.jobs.IJobChangeListener;
-import org.eclipse.core.runtime.jobs.Job;
-import org.eclipse.core.runtime.jobs.JobChangeAdapter;
-import org.eclipse.osgi.util.NLS;
-import org.eclipse.papyrus.infra.core.utils.JobBasedFuture;
-import org.eclipse.papyrus.infra.core.utils.JobExecutorService;
import org.eclipse.papyrus.infra.emf.Activator;
-import org.eclipse.papyrus.infra.tools.util.ReferenceCounted;
+import org.eclipse.papyrus.infra.emf.internal.resource.index.IIndexSaveParticipant;
+import org.eclipse.papyrus.infra.emf.internal.resource.index.IndexManager;
+import org.eclipse.papyrus.infra.emf.internal.resource.index.IndexPersistenceManager;
+import org.eclipse.papyrus.infra.emf.internal.resource.index.InternalModelIndex;
import com.google.common.base.Function;
-import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Queues;
import com.google.common.collect.SetMultimap;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -79,58 +57,86 @@ import com.google.common.util.concurrent.ListenableFuture;
/**
* A general-purpose index of model resources in the Eclipse workspace.
*/
-public class WorkspaceModelIndex<T> {
- private static final int MAX_INDEX_RETRIES = 3;
+public class WorkspaceModelIndex<T> extends InternalModelIndex {
+ private static final long INDEX_RECORD_SERIAL_VERSION = 1L;
private final IndexHandler<? extends T> indexer;
+ private final PersistentIndexHandler<T> pIndexer;
- private final QualifiedName indexKey;
+ private final String indexName;
private final IContentType contentType;
+ private final IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
private final SetMultimap<IProject, IFile> index = HashMultimap.create();
- private final IResourceChangeListener workspaceListener = new WorkspaceListener();
- private final Map<IProject, AbstractIndexJob> activeJobs = Maps.newHashMap();
- private final ContentTypeService contentTypeService;
private final Set<String> fileExtensions;
-
- private final JobWrangler jobWrangler;
-
- private final CopyOnWriteArrayList<IWorkspaceModelIndexListener> listeners = Lists.newCopyOnWriteArrayList();
+ private boolean started;
public WorkspaceModelIndex(String name, String contentType, IndexHandler<? extends T> indexer) {
this(name, contentType, indexer, 0);
}
public WorkspaceModelIndex(String name, String contentType, IndexHandler<? extends T> indexer, int maxConcurrentJobs) {
- super();
+ this(name, contentType,
+ Platform.getContentTypeManager().getContentType(contentType).getFileSpecs(IContentType.FILE_EXTENSION_SPEC),
+ indexer, maxConcurrentJobs);
+ }
+
+ /**
+ * @since 2.1
+ */
+ public WorkspaceModelIndex(String name, String contentType, String[] fileExtensions, IndexHandler<? extends T> indexer, int maxConcurrentJobs) {
+ this(name, contentType, fileExtensions, indexer, null, maxConcurrentJobs);
+ }
+
+ /**
+ * @since 2.1
+ */
+ public WorkspaceModelIndex(String name, String contentType, PersistentIndexHandler<T> indexer) {
+ this(name, contentType, indexer, 0);
+ }
+
+ /**
+ * @since 2.1
+ */
+ public WorkspaceModelIndex(String name, String contentType, PersistentIndexHandler<T> indexer, int maxConcurrentJobs) {
+ this(name, contentType,
+ Platform.getContentTypeManager().getContentType(contentType).getFileSpecs(IContentType.FILE_EXTENSION_SPEC),
+ indexer, maxConcurrentJobs);
+ }
- this.indexKey = new QualifiedName("org.eclipse.papyrus.modelindex", name); //$NON-NLS-1$
+ /**
+ * @since 2.1
+ */
+ public WorkspaceModelIndex(String name, String contentType, String[] fileExtensions, PersistentIndexHandler<T> indexer, int maxConcurrentJobs) {
+ this(name, contentType, fileExtensions, indexer, indexer, maxConcurrentJobs);
+ }
+
+ private WorkspaceModelIndex(String name, String contentType, String[] fileExtensions, IndexHandler<? extends T> indexer, PersistentIndexHandler<T> pIndexer, int maxConcurrentJobs) {
+ super(new QualifiedName(Activator.PLUGIN_ID, "index:" + name), maxConcurrentJobs); //$NON-NLS-1$
+
+ this.indexName = name;
this.contentType = Platform.getContentTypeManager().getContentType(contentType);
this.indexer = indexer;
+ this.pIndexer = pIndexer;
- String[] fileSpecs = this.contentType.getFileSpecs(IContentType.FILE_EXTENSION_SPEC);
- if ((fileSpecs != null) && (fileSpecs.length > 0)) {
- fileExtensions = ImmutableSet.copyOf(fileSpecs);
+ if ((fileExtensions != null) && (fileExtensions.length > 0)) {
+ this.fileExtensions = ImmutableSet.copyOf(fileExtensions);
} else {
- fileExtensions = null;
+ this.fileExtensions = null;
}
-
- contentTypeService = ContentTypeService.getInstance();
- jobWrangler = new JobWrangler(maxConcurrentJobs);
-
- startIndex();
}
+ @Override
public void dispose() {
- ResourcesPlugin.getWorkspace().removeResourceChangeListener(workspaceListener);
- Job.getJobManager().cancel(this);
- ContentTypeService.dispose(contentTypeService);
+ if (pIndexer != null) {
+ IndexPersistenceManager.INSTANCE.removeIndex(this);
+ }
synchronized (index) {
for (IFile next : index.values()) {
try {
- next.setSessionProperty(indexKey, null);
+ next.setSessionProperty(getIndexKey(), null);
} catch (CoreException e) {
// Just continue, best-effort. There's nothing else to do
}
@@ -140,23 +146,153 @@ public class WorkspaceModelIndex<T> {
}
}
- private void startIndex() {
- IWorkspace workspace = ResourcesPlugin.getWorkspace();
- workspace.addResourceChangeListener(workspaceListener, IResourceChangeEvent.POST_CHANGE);
+ /**
+ * @since 2.1
+ */
+ @Override
+ protected final void start() {
+ if (started) {
+ throw new IllegalStateException("index already started: " + getName()); //$NON-NLS-1$
+ }
+ started = true;
- index(Arrays.asList(workspace.getRoot().getProjects()));
+ // If we support persistence, initialize from the store
+ if (pIndexer != null) {
+ InputStream storeInput = IndexPersistenceManager.INSTANCE.addIndex(this, createSaveParticipant());
+ if (storeInput != null) {
+ try {
+ loadIndex(storeInput);
+ } catch (IOException e) {
+ // The input was already closed, if it could be
+ Activator.log.error("Failed to load index data for " + getName(), e); //$NON-NLS-1$
+ }
+ }
+ }
}
- void index(Collection<? extends IProject> projects) {
- List<IndexProjectJob> jobs = Lists.newArrayListWithCapacity(projects.size());
- for (IProject next : projects) {
- jobs.add(new IndexProjectJob(next));
+ private void loadIndex(InputStream storeInput) throws IOException {
+ List<IndexRecord> store = loadStore(storeInput);
+
+ synchronized (index) {
+ for (IndexRecord record : store) {
+ if (record.file.isAccessible()) {
+ try {
+ record.file.setSessionProperty(getIndexKey(), record);
+ index.put(record.file.getProject(), record.file);
+ } catch (CoreException e) {
+ // Doesn't matter; it will be indexed from scratch, then
+ Activator.log.log(e.getStatus());
+ }
+ }
+ }
}
- schedule(jobs);
}
- void index(IProject project) {
- schedule(new IndexProjectJob(project));
+ private List<IndexRecord> loadStore(InputStream storeInput) throws IOException {
+ List<IndexRecord> result = Collections.emptyList();
+
+ try (InputStream outer = storeInput; ObjectInputStream input = createObjectInput(outer)) {
+ // Load the version. So far, we're at the first version
+ long version = input.readLong();
+ if (version != INDEX_RECORD_SERIAL_VERSION) {
+ throw new IOException("Unexpected index record serial version " + version); //$NON-NLS-1$
+ }
+
+ // Read the number of records
+ int count = input.readInt();
+ result = new ArrayList<>(count);
+
+ // Read the records
+ for (int i = 0; i < count; i++) {
+ try {
+ result.add(readIndexRecord(input));
+ } catch (ClassNotFoundException e) {
+ throw new IOException(e);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private IndexRecord readIndexRecord(ObjectInput in) throws IOException, ClassNotFoundException {
+ // Load the file
+ IPath path = new Path((String) in.readObject());
+ IFile file = wsRoot.getFile(path);
+
+ // Load the index data
+ @SuppressWarnings("unchecked")
+ T index = (T) in.readObject();
+
+ return new IndexRecord(file, index);
+ }
+
+ private IIndexSaveParticipant createSaveParticipant() {
+ return new IIndexSaveParticipant() {
+ @Override
+ public void save(WorkspaceModelIndex<?> index, OutputStream storeOutput) throws IOException, CoreException {
+ if (index == WorkspaceModelIndex.this) {
+ List<IndexRecord> store;
+
+ synchronized (index) {
+ store = index.index.values().stream()
+ .filter(IResource::isAccessible)
+ .map(f -> {
+ IndexRecord result = null;
+
+ try {
+ @SuppressWarnings("unchecked")
+ IndexRecord __ = (IndexRecord) f.getSessionProperty(getIndexKey());
+ result = __;
+ } catch (CoreException e) {
+ // Doesn't matter; we'll just index it next time
+ Activator.log.log(e.getStatus());
+ }
+
+ return result;
+ })
+ .collect(Collectors.toList());
+ }
+
+ saveStore(store, storeOutput);
+ }
+ }
+ };
+ }
+
+ private void saveStore(List<IndexRecord> store, OutputStream storeOutput) throws IOException {
+ try (ObjectOutputStream output = new ObjectOutputStream(storeOutput)) {
+ // Write the version
+ output.writeLong(INDEX_RECORD_SERIAL_VERSION);
+
+ // Write the number of records
+ output.writeInt(store.size());
+
+ // Write the records
+ for (IndexRecord next : store) {
+ writeIndexRecord(next, output);
+ }
+ }
+ }
+
+ private void writeIndexRecord(IndexRecord record, ObjectOutput out) throws IOException {
+ out.writeObject(record.file.getFullPath().toPortableString());
+ out.writeObject(record.index);
+ }
+
+ /**
+ * Obtains the name of this index.
+ *
+ * @return my name
+ * @since 2.1
+ */
+ public final String getName() {
+ return indexName;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("WorkspaceModelIndex(%s)", getName()); //$NON-NLS-1$
}
/**
@@ -174,8 +310,9 @@ public class WorkspaceModelIndex<T> {
}
/**
- * Obtains an asynchronous future result that is scheduled to run after any pending indexing work has completed.
- * The {@code callable} is invoked under synchronization on the index, so it must be careful about how it
+ * Obtains an asynchronous future result that is scheduled to run after any
+ * pending indexing work has completed. The {@code callable} is invoked under
+ * synchronization on the index, so it must be careful about how it
* synchronizes on other objects to avoid deadlocks.
*
* @param callable
@@ -183,39 +320,13 @@ public class WorkspaceModelIndex<T> {
*
* @return the future result of the operation
*/
- public <V> ListenableFuture<V> afterIndex(final Callable<V> callable) {
- ListenableFuture<V> result;
-
- if (Job.getJobManager().find(this).length == 0) {
- // Result is available now
- try {
- result = Futures.immediateFuture(callable.call());
- } catch (Exception e) {
- result = Futures.immediateFailedFuture(e);
+ @Override
+ public <V> ListenableFuture<V> afterIndex(Callable<V> callable) {
+ return super.afterIndex(() -> {
+ synchronized (index) {
+ return callable.call();
}
- } else {
- JobBasedFuture<V> job = new JobBasedFuture<V>(NLS.bind("Wait for model index \"{0}\"", indexKey.getLocalName())) {
- {
- // setSystem(true);
- }
-
- @Override
- protected V compute(IProgressMonitor monitor) throws Exception {
- V result;
-
- Job.getJobManager().join(WorkspaceModelIndex.this, monitor);
- synchronized (index) {
- result = callable.call();
- }
-
- return result;
- }
- };
- job.schedule();
- result = job;
- }
-
- return result;
+ });
}
/**
@@ -259,9 +370,9 @@ public class WorkspaceModelIndex<T> {
for (IFile next : index.values()) {
try {
@SuppressWarnings("unchecked")
- T value = (T) next.getSessionProperty(indexKey);
- if (value != null) {
- result.put(next, value);
+ IndexRecord record = (IndexRecord) next.getSessionProperty(getIndexKey());
+ if (record != null) {
+ result.put(next, record.index);
}
} catch (CoreException e) {
Activator.log.error("Failed to access index data for file " + next.getFullPath(), e); //$NON-NLS-1$
@@ -271,17 +382,32 @@ public class WorkspaceModelIndex<T> {
return result.build();
}
- void process(IFile file) throws CoreException {
+ /**
+ * @since 2.1
+ */
+ @Override
+ protected final void process(IFile file) throws CoreException {
IProject project = file.getProject();
if (match(file)) {
- add(project, file);
+ @SuppressWarnings("unchecked")
+ IndexRecord record = (IndexRecord) file.getSessionProperty(getIndexKey());
+ if ((record == null) || record.isObsolete()) {
+ add(project, file);
+ } else {
+ // If it's not obsolete, then we're loading it from persistent storage
+ init(project, file, record);
+ }
} else {
remove(project, file);
}
}
- boolean match(IFile file) {
+ /**
+ * @since 2.1
+ */
+ @Override
+ protected final boolean match(IFile file) {
boolean result = false;
// Don't even attempt to match the content type if the file extension doesn't match.
@@ -291,7 +417,7 @@ public class WorkspaceModelIndex<T> {
&& ((fileExtensions == null) || fileExtensions.contains(file.getFileExtension()))
&& file.isSynchronized(IResource.DEPTH_ZERO)) {
- IContentType[] contentTypes = contentTypeService.getContentTypes(file);
+ IContentType[] contentTypes = getContentTypes(file);
if (contentTypes != null) {
for (int i = 0; (i < contentTypes.length) && !result; i++) {
result = contentTypes[i].isKindOf(contentType);
@@ -302,180 +428,72 @@ public class WorkspaceModelIndex<T> {
return result;
}
- void add(IProject project, IFile file) throws CoreException {
- synchronized (index) {
- index.put(project, file);
- file.setSessionProperty(indexKey, indexer.index(file));
+ void init(IProject project, IFile file, IndexRecord record) throws CoreException {
+ if (pIndexer.load(file, record.index)) {
+ synchronized (index) {
+ index.put(project, file);
+ file.setSessionProperty(getIndexKey(), record);
+ }
}
}
- void remove(IProject project, IFile file) throws CoreException {
- synchronized (index) {
- index.remove(project, file);
- indexer.unindex(file);
+ void add(IProject project, IFile file) throws CoreException {
+ T data = indexer.index(file);
- if (file.exists()) {
- file.setSessionProperty(indexKey, null);
- }
+ synchronized (index) {
+ index.put(project, file);
+ file.setSessionProperty(getIndexKey(), new IndexRecord(file, data));
}
}
- void remove(IProject project) throws CoreException {
+ /**
+ * @since 2.1
+ */
+ @Override
+ protected final void remove(IProject project, IFile file) throws CoreException {
+ boolean unindex;
+
synchronized (index) {
- if (index.containsKey(project)) {
- for (IFile next : index.get(project)) {
- indexer.unindex(next);
- }
- index.removeAll(project);
- }
+ // Don't need to do any work on the index data if
+ // this wasn't in the index in the first place
+ unindex = index.remove(project, file);
}
- }
- ReindexProjectJob reindex(IProject project, Iterable<? extends IndexDelta> deltas) {
- ReindexProjectJob result = null;
-
- synchronized (activeJobs) {
- AbstractIndexJob active = activeJobs.get(project);
-
- if (active != null) {
- switch (active.kind()) {
- case REINDEX:
- @SuppressWarnings("unchecked")
- ReindexProjectJob reindex = (ReindexProjectJob) active;
- reindex.addDeltas(deltas);
- break;
- case INDEX:
- @SuppressWarnings("unchecked")
- IndexProjectJob index = (IndexProjectJob) active;
- ReindexProjectJob followup = index.getFollowup();
- if (followup != null) {
- followup.addDeltas(deltas);
- } else {
- followup = new ReindexProjectJob(project, deltas);
- index.setFollowup(followup);
- }
- break;
- case MASTER:
- throw new IllegalStateException("Master job is in the active table."); //$NON-NLS-1$
+ if (unindex) {
+ try {
+ indexer.unindex(file);
+ } finally {
+ if (file.exists()) {
+ file.setSessionProperty(getIndexKey(), null);
}
- } else {
- // No active job. We'll need a new one
- result = new ReindexProjectJob(project, deltas);
}
}
-
- return result;
}
- IResourceVisitor getWorkspaceVisitor(final IProgressMonitor monitor) {
- return new IResourceVisitor() {
-
- @Override
- public boolean visit(IResource resource) throws CoreException {
- if (resource.getType() == IResource.FILE) {
- process((IFile) resource);
- }
-
- return !monitor.isCanceled();
- }
- };
- }
+ /**
+ * @since 2.1
+ */
+ @Override
+ protected final void remove(IProject project) throws CoreException {
+ Set<IFile> files;
- private void schedule(Collection<? extends AbstractIndexJob> jobs) {
- // Synchronize on the active jobs because this potentially alters the wrangler's follow-up job
- synchronized (activeJobs) {
- jobWrangler.add(jobs);
+ synchronized (index) {
+ files = index.containsKey(project)
+ ? index.removeAll(project)
+ : null;
}
- }
- private void schedule(AbstractIndexJob job) {
- // Synchronize on the active jobs because this potentially alters the wrangler's follow-up job
- synchronized (activeJobs) {
- jobWrangler.add(job);
+ if (files != null) {
+ files.forEach(indexer::unindex);
}
}
public void addListener(IWorkspaceModelIndexListener listener) {
- listeners.addIfAbsent(listener);
+ IndexManager.getInstance().addListener(this, listener);
}
public void removeListener(IWorkspaceModelIndexListener listener) {
- listeners.remove(listener);
- }
-
- private void notifyStarting(AbstractIndexJob indexJob) {
- if (!listeners.isEmpty()) {
- WorkspaceModelIndexEvent event;
-
- switch (indexJob.kind()) {
- case INDEX:
- event = new WorkspaceModelIndexEvent(this, WorkspaceModelIndexEvent.ABOUT_TO_CALCULATE, indexJob.getProject());
- for (IWorkspaceModelIndexListener next : listeners) {
- try {
- next.indexAboutToCalculate(event);
- } catch (Exception e) {
- Activator.log.error("Uncaught exception in index listsner.", e); //$NON-NLS-1$
- }
- }
- break;
- case REINDEX:
- event = new WorkspaceModelIndexEvent(this, WorkspaceModelIndexEvent.ABOUT_TO_RECALCULATE, indexJob.getProject());
- for (IWorkspaceModelIndexListener next : listeners) {
- try {
- next.indexAboutToRecalculate(event);
- } catch (Exception e) {
- Activator.log.error("Uncaught exception in index listsner.", e); //$NON-NLS-1$
- }
- }
- break;
- case MASTER:
- // Pass
- break;
- }
- }
- }
-
- private void notifyFinished(AbstractIndexJob indexJob, IStatus status) {
- if (!listeners.isEmpty()) {
- WorkspaceModelIndexEvent event;
-
- if ((status != null) && (status.getSeverity() >= IStatus.ERROR)) {
- event = new WorkspaceModelIndexEvent(this, WorkspaceModelIndexEvent.FAILED, indexJob.getProject());
- for (IWorkspaceModelIndexListener next : listeners) {
- try {
- next.indexFailed(event);
- } catch (Exception e) {
- Activator.log.error("Uncaught exception in index listsner.", e); //$NON-NLS-1$
- }
- }
- } else {
- switch (indexJob.kind()) {
- case INDEX:
- event = new WorkspaceModelIndexEvent(this, WorkspaceModelIndexEvent.CALCULATED, indexJob.getProject());
- for (IWorkspaceModelIndexListener next : listeners) {
- try {
- next.indexCalculated(event);
- } catch (Exception e) {
- Activator.log.error("Uncaught exception in index listsner.", e); //$NON-NLS-1$
- }
- }
- break;
- case REINDEX:
- event = new WorkspaceModelIndexEvent(this, WorkspaceModelIndexEvent.RECALCULATED, indexJob.getProject());
- for (IWorkspaceModelIndexListener next : listeners) {
- try {
- next.indexRecalculated(event);
- } catch (Exception e) {
- Activator.log.error("Uncaught exception in index listsner.", e); //$NON-NLS-1$
- }
- }
- break;
- case MASTER:
- // Pass
- break;
- }
- }
- }
+ IndexManager.getInstance().removeListener(this, listener);
}
//
@@ -505,537 +523,48 @@ public class WorkspaceModelIndex<T> {
void unindex(IFile file);
}
- private enum JobKind {
- MASTER, INDEX, REINDEX;
-
- boolean isSystem() {
- return this != MASTER;
- }
- }
-
- private abstract class AbstractIndexJob extends Job {
- private final IProject project;
-
- private volatile Semaphore permit;
-
- AbstractIndexJob(String name, IProject project) {
- super(name);
-
- this.project = project;
- this.permit = permit;
-
- if (project != null) {
- setRule(project);
- synchronized (activeJobs) {
- if (!activeJobs.containsKey(project)) {
- activeJobs.put(project, this);
- }
- }
- }
-
- setSystem(kind().isSystem());
- }
-
- @Override
- public boolean belongsTo(Object family) {
- return family == WorkspaceModelIndex.this;
- }
-
- final IProject getProject() {
- return project;
- }
-
- abstract JobKind kind();
-
- @Override
- protected final IStatus run(IProgressMonitor monitor) {
- IStatus result;
-
- try {
- result = doRun(monitor);
- } finally {
- synchronized (activeJobs) {
- AbstractIndexJob followup = getFollowup();
-
- if (project != null) {
- if (followup == null) {
- activeJobs.remove(project);
- } else {
- activeJobs.put(project, followup);
- }
- }
-
- if (followup != null) {
- // Kick off the follow-up job
- WorkspaceModelIndex.this.schedule(followup);
- }
- }
- }
-
- return result;
- }
-
- final Semaphore getPermit() {
- return permit;
- }
-
- final void setPermit(Semaphore permit) {
- this.permit = permit;
- }
-
- protected abstract IStatus doRun(IProgressMonitor monitor);
-
- protected AbstractIndexJob getFollowup() {
- return null;
- }
- }
-
- private class JobWrangler extends AbstractIndexJob {
- private final Lock lock = new ReentrantLock();
-
- private final Deque<AbstractIndexJob> queue = Queues.newArrayDeque();
-
- private final AtomicBoolean active = new AtomicBoolean();
- private final Semaphore indexJobSemaphore;
-
- JobWrangler(int maxConcurrentJobs) {
- super("Workspace model indexer", null);
-
- indexJobSemaphore = new Semaphore((maxConcurrentJobs <= 0) ? Integer.MAX_VALUE : maxConcurrentJobs);
- }
-
- @Override
- JobKind kind() {
- return JobKind.MASTER;
- }
-
- void add(AbstractIndexJob job) {
- lock.lock();
-
- try {
- scheduleIfNeeded();
- queue.add(job);
- } finally {
- lock.unlock();
- }
- }
-
- private void scheduleIfNeeded() {
- if (active.compareAndSet(false, true)) {
- // I am a new job
- schedule();
- }
- }
-
- void add(Iterable<? extends AbstractIndexJob> jobs) {
- lock.lock();
-
- try {
- for (AbstractIndexJob next : jobs) {
- add(next);
- }
- } finally {
- lock.unlock();
- }
- }
-
- @Override
- protected IStatus doRun(IProgressMonitor progressMonitor) {
- final AtomicInteger pending = new AtomicInteger(); // How many permits have we issued?
- final Condition pendingChanged = lock.newCondition();
-
- final SubMonitor monitor = SubMonitor.convert(progressMonitor, IProgressMonitor.UNKNOWN);
-
- IStatus result = Status.OK_STATUS;
-
- IJobChangeListener listener = new JobChangeAdapter() {
- private final Map<IProject, Integer> retries = Maps.newHashMap();
-
- private Semaphore getIndexJobPermit(Job job) {
- return (job instanceof WorkspaceModelIndex<?>.AbstractIndexJob)
- ? ((WorkspaceModelIndex<?>.AbstractIndexJob) job).getPermit()
- : null;
- }
-
- @Override
- public void aboutToRun(IJobChangeEvent event) {
- Job starting = event.getJob();
-
- if (getIndexJobPermit(starting) == indexJobSemaphore) {
- // one of mine is starting
- @SuppressWarnings("unchecked")
- AbstractIndexJob indexJob = (AbstractIndexJob) starting;
- notifyStarting(indexJob);
- }
- }
-
- @Override
- public void done(IJobChangeEvent event) {
- final Job finished = event.getJob();
- if (getIndexJobPermit(finished) == indexJobSemaphore) {
- try {
- // one of mine has finished
- @SuppressWarnings("unchecked")
- AbstractIndexJob indexJob = (AbstractIndexJob) finished;
- IProject project = indexJob.getProject();
-
- notifyFinished(indexJob, event.getResult());
-
- if (project != null) {
- synchronized (retries) {
- if ((event.getResult() != null) && (event.getResult().getSeverity() >= IStatus.ERROR)) {
- // Indexing failed to complete. Need to re-build the index
- int count = retries.containsKey(project) ? retries.get(project) : 0;
- if (count++ < MAX_INDEX_RETRIES) {
- // Only retry up to three times
- index(project);
- }
- retries.put(project, ++count);
- } else {
- // Successful re-indexing. Forget the retries
- retries.remove(project);
- }
- }
- }
- } finally {
- // Release this job's permit for the next one in the queue
- indexJobSemaphore.release();
-
- // And it's no longer pending
- pending.decrementAndGet();
-
- lock.lock();
- try {
- pendingChanged.signalAll();
- } finally {
- lock.unlock();
- }
- }
- }
- }
- };
-
- getJobManager().addJobChangeListener(listener);
-
- lock.lock();
-
- try {
- out: for (;;) {
- for (AbstractIndexJob next = queue.poll(); next != null; next = queue.poll()) {
- lock.unlock();
- try {
- if (monitor.isCanceled()) {
- Thread.currentThread().interrupt();
- }
-
- // Enforce the concurrent jobs limit
- indexJobSemaphore.acquire();
- next.setPermit(indexJobSemaphore);
- pending.incrementAndGet();
-
- // Now go
- next.schedule();
- } catch (InterruptedException e) {
- // We were cancelled. Push this job back and re-schedule
- lock.lock();
- try {
- queue.addFirst(next);
- } finally {
- lock.unlock();
- }
- result = Status.CANCEL_STATUS;
- break out;
- } finally {
- lock.lock();
- }
- }
-
- if ((pending.get() <= 0) && queue.isEmpty()) {
- // Nothing left to wait for
- break out;
- } else if (pending.get() > 0) {
- try {
- if (monitor.isCanceled()) {
- Thread.currentThread().interrupt();
- }
-
- pendingChanged.await();
- } catch (InterruptedException e) {
- // We were cancelled. Re-schedule
- result = Status.CANCEL_STATUS;
- break out;
- }
- }
- }
-
- // We've finished wrangling index jobs, for now
- } finally {
- active.compareAndSet(true, false);
-
- // If we were canceled then we re-schedule after a delay to recover
- if (result == Status.CANCEL_STATUS) {
- // We cannot un-cancel a job, so we must replace ourselves with a new job
- schedule(1000L);
- } else {
- // Double-check
- if (!queue.isEmpty()) {
- // We'll have to go around again
- scheduleIfNeeded();
- }
- }
-
- lock.unlock();
-
- getJobManager().removeJobChangeListener(listener);
- }
-
- return result;
- }
- }
-
- private class IndexProjectJob extends AbstractIndexJob {
- private ReindexProjectJob followup;
-
- IndexProjectJob(IProject project) {
- super("Indexing project " + project.getName(), project);
- }
-
- @Override
- JobKind kind() {
- return JobKind.INDEX;
- }
-
- @Override
- protected IStatus doRun(IProgressMonitor monitor) {
- IStatus result = Status.OK_STATUS;
- final IProject project = getProject();
-
- monitor.beginTask("Indexing models in project " + project.getName(), IProgressMonitor.UNKNOWN);
-
- try {
- if (project.isAccessible()) {
- project.accept(getWorkspaceVisitor(monitor));
- } else {
- remove(project);
- }
-
- if (monitor.isCanceled()) {
- result = Status.CANCEL_STATUS;
- }
- } catch (CoreException e) {
- result = e.getStatus();
- } finally {
- monitor.done();
- }
-
- return result;
- }
-
- void setFollowup(ReindexProjectJob followup) {
- this.followup = followup;
- }
-
- @Override
- protected ReindexProjectJob getFollowup() {
- return followup;
- }
- }
-
- private class WorkspaceListener implements IResourceChangeListener {
- @Override
- public void resourceChanged(IResourceChangeEvent event) {
- final Multimap<IProject, IndexDelta> deltas = ArrayListMultimap.create();
-
- try {
- event.getDelta().accept(new IResourceDeltaVisitor() {
-
- @Override
- public boolean visit(IResourceDelta delta) throws CoreException {
- if (delta.getResource().getType() == IResource.FILE) {
- IFile file = (IFile) delta.getResource();
-
- switch (delta.getKind()) {
- case IResourceDelta.CHANGED:
- if ((delta.getFlags() & (IResourceDelta.SYNC | IResourceDelta.CONTENT | IResourceDelta.REPLACED)) != 0) {
- // Re-index in place
- deltas.put(file.getProject(), new IndexDelta(file, IndexDelta.DeltaKind.REINDEX));
- }
- break;
- case IResourceDelta.REMOVED:
- deltas.put(file.getProject(), new IndexDelta(file, IndexDelta.DeltaKind.UNINDEX));
- break;
- case IResourceDelta.ADDED:
- deltas.put(file.getProject(), new IndexDelta(file, IndexDelta.DeltaKind.INDEX));
- break;
- }
- }
- return true;
- }
- });
- } catch (CoreException e) {
- Activator.log.error("Failed to analyze resource changes for re-indexing.", e); //$NON-NLS-1$
- }
-
- if (!deltas.isEmpty()) {
- List<ReindexProjectJob> jobs = Lists.newArrayListWithCapacity(deltas.keySet().size());
- for (IProject next : deltas.keySet()) {
- ReindexProjectJob reindex = reindex(next, deltas.get(next));
- if (reindex != null) {
- jobs.add(reindex);
- }
- }
- schedule(jobs);
- }
- }
- }
-
- private static class IndexDelta {
- private final IFile file;
-
- private final DeltaKind kind;
-
- IndexDelta(IFile file, DeltaKind kind) {
- this.file = file;
- this.kind = kind;
- }
-
- //
- // Nested types
- //
-
- enum DeltaKind {
- INDEX, REINDEX, UNINDEX
- }
- }
-
- private class ReindexProjectJob extends AbstractIndexJob {
- private final IProject project;
- private final ConcurrentLinkedQueue<IndexDelta> deltas;
-
- ReindexProjectJob(IProject project, Iterable<? extends IndexDelta> deltas) {
- super("Re-indexing project " + project.getName(), project);
- this.project = project;
- this.deltas = Queues.newConcurrentLinkedQueue(deltas);
- }
-
- @Override
- JobKind kind() {
- return JobKind.REINDEX;
- }
-
- void addDeltas(Iterable<? extends IndexDelta> deltas) {
- Iterables.addAll(this.deltas, deltas);
- }
-
- @Override
- protected IStatus doRun(IProgressMonitor monitor) {
- IStatus result = Status.OK_STATUS;
-
- monitor.beginTask("Re-indexing models in project " + project.getName(), IProgressMonitor.UNKNOWN);
-
- try {
- for (IndexDelta next = deltas.poll(); next != null; next = deltas.poll()) {
- if (monitor.isCanceled()) {
- result = Status.CANCEL_STATUS;
- break;
- }
-
- try {
- switch (next.kind) {
- case INDEX:
- case REINDEX:
- process(next.file);
- break;
- case UNINDEX:
- remove(project, next.file);
- break;
- }
- } catch (CoreException e) {
- result = e.getStatus();
- break;
- } finally {
- monitor.worked(1);
- }
- }
- } finally {
- monitor.done();
- }
-
- return result;
- }
-
- @Override
- protected AbstractIndexJob getFollowup() {
- // If I still have work to do, then I am my own follow-up
- return deltas.isEmpty() ? null : this;
- }
+ /**
+ * Extension interface for index handlers that provide persistable index
+ * data associated with each file. This enables storage of the index in
+ * the workspace metadata for quick initialization on start-up, requiring
+ * re-calculation of the index only for files that were changed since the
+ * workspace was last closed.
+ *
+ * @param <T>
+ * the index data store type, which must be {@link Serializable}
+ * @since 2.1
+ */
+ public static interface PersistentIndexHandler<T> extends IndexHandler<T> {
+ /**
+ * Initializes the {@code index} data for a file from the persistent store.
+ *
+ * @param file
+ * a file in the workspace
+ * @param index
+ * its previously stored index
+ *
+ * @return whether the {@code index} data were successfully integrated.
+ * A {@code false} result indicates that the file must be indexed
+ * from scratch
+ */
+ boolean load(IFile file, T index);
}
- private static final class ContentTypeService extends ReferenceCounted<ContentTypeService> {
- private static ContentTypeService instance = null;
-
- private final ExecutorService serialExecution = new JobExecutorService();
-
- private final IContentTypeManager mgr = Platform.getContentTypeManager();
+ private final class IndexRecord {
+ private IFile file;
+ private long generation;
+ private T index;
- private ContentTypeService() {
+ IndexRecord(IFile file, T index) {
super();
- }
-
- synchronized static ContentTypeService getInstance() {
- ContentTypeService result = instance;
- if (result == null) {
- result = new ContentTypeService();
- instance = result;
- }
-
- return result.retain();
- }
-
- synchronized static void dispose(ContentTypeService service) {
- service.release();
- }
-
- @Override
- protected void dispose() {
- serialExecution.shutdownNow();
-
- if (instance == this) {
- instance = null;
- }
+ this.file = file;
+ this.generation = file.getModificationStamp();
+ this.index = index;
}
- IContentType[] getContentTypes(final IFile file) {
- Future<IContentType[]> futureResult = serialExecution.submit(new Callable<IContentType[]>() {
-
- @Override
- public IContentType[] call() {
- IContentType[] result = null;
- InputStream input = null;
-
- if (file.isAccessible()) {
- try {
- input = file.getContents(true);
- result = mgr.findContentTypesFor(input, file.getName());
- } catch (Exception e) {
- Activator.log.error("Failed to index file " + file.getFullPath(), e); //$NON-NLS-1$
- } finally {
- if (input != null) {
- try {
- input.close();
- } catch (IOException e) {
- Activator.log.error("Failed to close indexed file " + file.getFullPath(), e); //$NON-NLS-1$
- }
- }
- }
- }
-
- return result;
- }
- });
-
- return Futures.getUnchecked(futureResult);
+ boolean isObsolete() {
+ return file.getModificationStamp() != generation;
}
}
}
diff --git a/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/META-INF/MANIFEST.MF b/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/META-INF/MANIFEST.MF
index 0869377db87..134a924bbfc 100644
--- a/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/META-INF/MANIFEST.MF
+++ b/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/META-INF/MANIFEST.MF
@@ -68,7 +68,7 @@ Require-Bundle: org.eclipse.emf.ecore.edit;bundle-version="[2.9.0,3.0.0)",
Bundle-Vendor: %providerName
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
-Bundle-Version: 2.0.0.qualifier
+Bundle-Version: 2.0.100.qualifier
Bundle-Localization: plugin
Bundle-Name: %pluginName
Bundle-Activator: org.eclipse.papyrus.infra.gmfdiag.common.Activator
diff --git a/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/plugin.xml b/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/plugin.xml
index c5a59ba0347..f9ef42b7cb7 100644
--- a/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/plugin.xml
+++ b/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/plugin.xml
@@ -80,6 +80,10 @@
description="Model for notation"
fileExtension="notation"
required="true">
+ <modelSnippet
+ classname="org.eclipse.papyrus.infra.core.resource.AdjunctResourceModelSnippet"
+ description="Snippet for notation resource of referenced model resources">
+ </modelSnippet>
</model>
</extension>
diff --git a/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/pom.xml b/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/pom.xml
index 46604fb985c..b50589776be 100644
--- a/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/pom.xml
+++ b/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/pom.xml
@@ -7,6 +7,6 @@
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.papyrus.infra.gmfdiag.common</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.100-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project> \ No newline at end of file
diff --git a/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/src/org/eclipse/papyrus/infra/gmfdiag/common/model/NotationModel.java b/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/src/org/eclipse/papyrus/infra/gmfdiag/common/model/NotationModel.java
index 7455ae503ff..dd64e382f43 100644
--- a/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/src/org/eclipse/papyrus/infra/gmfdiag/common/model/NotationModel.java
+++ b/plugins/infra/gmfdiag/org.eclipse.papyrus.infra.gmfdiag.common/src/org/eclipse/papyrus/infra/gmfdiag/common/model/NotationModel.java
@@ -8,18 +8,13 @@
*
* Contributors:
* LIFL - Initial API and implementation
- * Christian W. Damus - bug 485220
+ * Christian W. Damus - bugs 485220, 496299
*
*****************************************************************************/
package org.eclipse.papyrus.infra.gmfdiag.common.model;
-import java.util.Collections;
-
-import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
-import org.eclipse.emf.ecore.resource.ResourceSet;
-import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.osgi.util.NLS;
import org.eclipse.papyrus.infra.core.resource.BadArgumentExcetion;
@@ -124,28 +119,6 @@ public class NotationModel extends EMFLogicalModel implements IModel {
return false;
}
- @Override
- public void handle(Resource resource) {
- super.handle(resource);
- if (resource == null) {
- return;
- }
-
- // If the parameter resource is already a notation resource, nothing to do
- if (!isRelatedResource(resource)) {
- URI notationURI = resource.getURI().trimFileExtension().appendFileExtension(NOTATION_FILE_EXTENSION);
- ResourceSet resourceSet = getResourceSet();
- if (resourceSet != null && resourceSet.getURIConverter() != null) {
- URIConverter converter = resourceSet.getURIConverter();
- if (converter.exists(notationURI, Collections.emptyMap())) {
- // If the notation resource associated to the parameter resource exists, load it
- getResourceSet().getResource(notationURI, true);
- }
- }
- }
- }
-
-
/**
* Get a diagram by its name.
*
diff --git a/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/.classpath b/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/.classpath
index 2d1a4302f04..eca7bdba8f0 100644
--- a/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/.classpath
+++ b/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/.classpath
@@ -1,7 +1,7 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
- <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/.settings/org.eclipse.jdt.core.prefs b/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/.settings/org.eclipse.jdt.core.prefs
index 4759947300a..62a08f4494d 100644
--- a/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/.settings/org.eclipse.jdt.core.prefs
+++ b/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/.settings/org.eclipse.jdt.core.prefs
@@ -1,10 +1,10 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
-org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.5
+org.eclipse.jdt.core.compiler.source=1.8
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
diff --git a/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/META-INF/MANIFEST.MF b/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/META-INF/MANIFEST.MF
index 26463e85211..4715b2dd387 100644
--- a/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/META-INF/MANIFEST.MF
+++ b/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/META-INF/MANIFEST.MF
@@ -18,10 +18,10 @@ Require-Bundle: org.eclipse.emf.edit.ui;bundle-version="[2.12.0,3.0.0)";visibili
org.eclipse.papyrus.infra.types.core;bundle-version="[2.0.0,3.0.0)"
Bundle-Vendor: %providerName
Bundle-ActivationPolicy: lazy
-Bundle-Version: 1.2.100.qualifier
+Bundle-Version: 1.4.0.qualifier
Bundle-Localization: plugin
Bundle-Name: %pluginName
Bundle-Activator: org.eclipse.papyrus.infra.services.controlmode.ControlModePlugin
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.eclipse.papyrus.infra.services.controlmode;singleton:=true
-Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
diff --git a/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/pom.xml b/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/pom.xml
index cf60c30a12c..b0cf7c2c462 100644
--- a/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/pom.xml
+++ b/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/pom.xml
@@ -7,6 +7,6 @@
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.papyrus.infra.services.controlmode</artifactId>
- <version>1.2.100-SNAPSHOT</version>
+ <version>1.4.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
diff --git a/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/src/org/eclipse/papyrus/infra/services/controlmode/commands/BasicUncontrolCommand.java b/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/src/org/eclipse/papyrus/infra/services/controlmode/commands/BasicUncontrolCommand.java
index 17079758f62..1424aeece67 100644
--- a/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/src/org/eclipse/papyrus/infra/services/controlmode/commands/BasicUncontrolCommand.java
+++ b/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/src/org/eclipse/papyrus/infra/services/controlmode/commands/BasicUncontrolCommand.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2013 Atos.
+ * Copyright (c) 2013, 2016 Atos, Christian W. Damus, and others.
*
*
* All rights reserved. This program and the accompanying materials
@@ -10,6 +10,7 @@
* Contributors:
* Arthur Daussy (Atos) arthur.daussy@atos.net - Initial API and implementation
* Gabriel Pascual (ALL4TEC) gabriel.pascual@all4tec.ent - Bug 436998
+ * Christian W. Damus - bug 496299
*****************************************************************************/
package org.eclipse.papyrus.infra.services.controlmode.commands;
@@ -24,6 +25,7 @@ import org.eclipse.emf.workspace.util.WorkspaceSynchronizer;
import org.eclipse.gmf.runtime.common.core.command.CommandResult;
import org.eclipse.papyrus.infra.core.resource.ModelSet;
import org.eclipse.papyrus.infra.core.services.ServiceException;
+import org.eclipse.papyrus.infra.emf.resource.ShardResourceHelper;
import org.eclipse.papyrus.infra.emf.utils.ServiceUtilsForResource;
import org.eclipse.papyrus.infra.services.controlmode.ControlModePlugin;
import org.eclipse.papyrus.infra.services.controlmode.ControlModeRequest;
@@ -85,7 +87,12 @@ public class BasicUncontrolCommand extends AbstractControlCommand {
IUncontrolledObjectsProvider service = ServiceUtilsForResource.getInstance().getService(IUncontrolledObjectsProvider.class, resource);
service.addUncontrolledObject(resource, uncontrolledObject);
} catch (ServiceException e) {
- ControlModePlugin.log.error(UNCONTROL_OBJECT_ERROR, e); //$NON-NLS-1$
+ ControlModePlugin.log.error(UNCONTROL_OBJECT_ERROR, e); // $NON-NLS-1$
+ }
+
+ // If it was a "shard", make sure it isn't, now
+ try (ShardResourceHelper helper = new ShardResourceHelper(uncontrolledObject)) {
+ helper.setShard(false);
}
// Remove uncontrolled object to its resource
@@ -96,6 +103,6 @@ public class BasicUncontrolCommand extends AbstractControlCommand {
return CommandResult.newOKCommandResult();
}
- return CommandResult.newErrorCommandResult(UNCONTROL_RESOURCE_ERROR); //$NON-NLS-1$
+ return CommandResult.newErrorCommandResult(UNCONTROL_RESOURCE_ERROR); // $NON-NLS-1$
}
}
diff --git a/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/src/org/eclipse/papyrus/infra/services/controlmode/commands/LoadDiagramCommand.java b/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/src/org/eclipse/papyrus/infra/services/controlmode/commands/LoadDiagramCommand.java
index 9294831ddf4..d0722c26f73 100644
--- a/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/src/org/eclipse/papyrus/infra/services/controlmode/commands/LoadDiagramCommand.java
+++ b/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode/src/org/eclipse/papyrus/infra/services/controlmode/commands/LoadDiagramCommand.java
@@ -79,7 +79,7 @@ public class LoadDiagramCommand implements Runnable {
* @param pageManager
* the page manager in which to reload them, or {@code null} if none
*
- * @since 1.2
+ * @since 1.3
*/
public LoadDiagramCommand(Resource resource, IPageManager pageManager) {
super();
@@ -92,6 +92,7 @@ public class LoadDiagramCommand implements Runnable {
* Reloads hte pages associated with my resource, if any and if there is a
* page manager.
*/
+ @Override
public void run() {
if (pageManager != null) {
diff --git a/plugins/infra/ui/org.eclipse.papyrus.infra.ui/META-INF/MANIFEST.MF b/plugins/infra/ui/org.eclipse.papyrus.infra.ui/META-INF/MANIFEST.MF
index 405a6b4eafb..d3f993e710c 100644
--- a/plugins/infra/ui/org.eclipse.papyrus.infra.ui/META-INF/MANIFEST.MF
+++ b/plugins/infra/ui/org.eclipse.papyrus.infra.ui/META-INF/MANIFEST.MF
@@ -32,10 +32,11 @@ Require-Bundle: org.eclipse.papyrus.infra.core;bundle-version="[2.0.0,3.0.0)";vi
org.eclipse.emf.edit.ui;bundle-version="[2.12.0,3.0.0)",
org.eclipse.papyrus.infra.widgets;bundle-version="[2.0.0,3.0.0)";visibility:=reexport,
org.eclipse.ui.views;bundle-version="[3.8.0,4.0.0)";visibility:=reexport,
- org.eclipse.papyrus.infra.emf;bundle-version="[2.0.0,3.0.0)",
- org.eclipse.papyrus.infra.widgets.toolbox;bundle-version="[1.2.0,2.0.0)"
+ org.eclipse.papyrus.infra.emf;bundle-version="[2.0.1,3.0.0)",
+ org.eclipse.papyrus.infra.widgets.toolbox;bundle-version="[1.2.0,2.0.0)",
+ org.eclipse.papyrus.infra.tools;bundle-version="[2.0.1,3.0.0)"
Bundle-Vendor: %providerName
-Bundle-Version: 1.2.0.qualifier
+Bundle-Version: 1.4.0.qualifier
Eclipse-BuddyPolicy: dependent
Bundle-ManifestVersion: 2
Bundle-Activator: org.eclipse.papyrus.infra.ui.Activator
diff --git a/plugins/infra/ui/org.eclipse.papyrus.infra.ui/pom.xml b/plugins/infra/ui/org.eclipse.papyrus.infra.ui/pom.xml
index f6a01ae6b41..a4c1a625ebe 100644
--- a/plugins/infra/ui/org.eclipse.papyrus.infra.ui/pom.xml
+++ b/plugins/infra/ui/org.eclipse.papyrus.infra.ui/pom.xml
@@ -7,7 +7,7 @@
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.papyrus.infra.ui</artifactId>
- <version>1.2.0-SNAPSHOT</version>
+ <version>1.4.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
<description>Plugin dedicated to manage generic menus and actions, linked to EMF but not to UML nor GMF technologies.</description>
</project>
diff --git a/plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/editor/CoreMultiDiagramEditor.java b/plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/editor/CoreMultiDiagramEditor.java
index b6a33a1dca9..6c6d7b1f228 100644
--- a/plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/editor/CoreMultiDiagramEditor.java
+++ b/plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/editor/CoreMultiDiagramEditor.java
@@ -12,7 +12,7 @@
* Christian W. Damus (CEA) - bug 410346
* Christian W. Damus (CEA) - bug 431953 (pre-requisite refactoring of ModelSet service start-up)
* Christian W. Damus (CEA) - bug 437217
- * Christian W. Damus - bugs 469464, 469188, 485220
+ * Christian W. Damus - bugs 469464, 469188, 485220, 496299
*
*****************************************************************************/
@@ -36,7 +36,6 @@ import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.emf.common.notify.AdapterFactory;
-import org.eclipse.emf.common.ui.URIEditorInput;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
@@ -71,6 +70,8 @@ import org.eclipse.papyrus.infra.core.services.ServiceMultiException;
import org.eclipse.papyrus.infra.core.services.ServiceStartKind;
import org.eclipse.papyrus.infra.core.services.ServicesRegistry;
import org.eclipse.papyrus.infra.core.utils.ServiceUtils;
+import org.eclipse.papyrus.infra.emf.resource.ICrossReferenceIndex;
+import org.eclipse.papyrus.infra.emf.resource.ShardResourceLocator;
import org.eclipse.papyrus.infra.ui.Activator;
import org.eclipse.papyrus.infra.ui.contentoutline.ContentOutlineRegistry;
import org.eclipse.papyrus.infra.ui.editor.IReloadableEditor.DirtyPolicy;
@@ -84,14 +85,13 @@ import org.eclipse.papyrus.infra.ui.multidiagram.actionbarcontributor.CoreCompos
import org.eclipse.papyrus.infra.ui.services.EditorLifecycleManager;
import org.eclipse.papyrus.infra.ui.services.internal.EditorLifecycleManagerImpl;
import org.eclipse.papyrus.infra.ui.services.internal.InternalEditorLifecycleManager;
+import org.eclipse.papyrus.infra.ui.util.EditorUtils;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorActionBarContributor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
-import org.eclipse.ui.IFileEditorInput;
-import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.IViewReference;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchActionConstants;
@@ -149,7 +149,7 @@ public class CoreMultiDiagramEditor extends AbstractMultiPageSashEditor implemen
*/
protected ISaveAndDirtyService saveAndDirtyService;
- private final List<IPropertySheetPage> propertiesPages = new LinkedList<IPropertySheetPage>();
+ private final List<IPropertySheetPage> propertiesPages = new LinkedList<>();
private final List<Runnable> closeActions = new ArrayList<>();
@@ -253,12 +253,12 @@ public class CoreMultiDiagramEditor extends AbstractMultiPageSashEditor implemen
/**
* Editor reload listeners.
*/
- private CopyOnWriteArrayList<IEditorReloadListener> reloadListeners = new CopyOnWriteArrayList<IEditorReloadListener>();
+ private CopyOnWriteArrayList<IEditorReloadListener> reloadListeners = new CopyOnWriteArrayList<>();
/**
* A pending reload operation (awaiting next activation of the editor).
*/
- private final AtomicReference<DeferredReload> pendingReload = new AtomicReference<DeferredReload>();
+ private final AtomicReference<DeferredReload> pendingReload = new AtomicReference<>();
public CoreMultiDiagramEditor() {
super();
@@ -586,30 +586,37 @@ public class CoreMultiDiagramEditor extends AbstractMultiPageSashEditor implemen
servicesRegistry.add(EditorLifecycleManager.class, 1, lifecycleManager, ServiceStartKind.LAZY);
// Start servicesRegistry
- URI uri;
- IEditorInput input = getEditorInput();
- if (input instanceof IFileEditorInput) {
- uri = URI.createPlatformResourceURI(((IFileEditorInput) input).getFile().getFullPath().toString(), true);
- } else if (input instanceof URIEditorInput) {
- uri = ((URIEditorInput) input).getURI();
- } else {
- uri = URI.createURI(((IURIEditorInput) input).getURI().toString());
- }
+ URI uri = EditorUtils.getResourceURI(getEditorInput());
try {
// Start the ModelSet first, and load if from the specified File.
// Also start me so that I may be retrieved from the registry by other services
- List<Class<?>> servicesToStart = new ArrayList<Class<?>>(1);
+ List<Class<?>> servicesToStart = new ArrayList<>(1);
servicesToStart.add(ModelSet.class);
servicesToStart.add(IMultiDiagramEditor.class);
servicesRegistry.startServicesByClassKeys(servicesToStart);
resourceSet = servicesRegistry.getService(ModelSet.class);
+
+ // Install shard resource handling
+ new ShardResourceLocator(resourceSet);
+
+ // Resolve a possible shard URI
+ uri = EditorUtils.resolveShardRoot(
+ ICrossReferenceIndex.getInstance(resourceSet), uri);
+
+ // Load it up
resourceSet.loadModels(uri);
// start remaining services
servicesRegistry.startRegistry();
+
+ // In case of a shard
+ String name = uri.lastSegment();
+ if (!name.equals(getPartName())) {
+ setPartName(name);
+ }
} catch (ModelMultiException e) {
try {
// with the ModelMultiException it is still possible to open the
@@ -625,7 +632,6 @@ public class CoreMultiDiagramEditor extends AbstractMultiPageSashEditor implemen
// throw new PartInitException("could not initialize services", e);
}
-
// Get required services
try {
diff --git a/plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/EditorUtils.java b/plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/EditorUtils.java
index e476a005dd1..acc7299b2e1 100644
--- a/plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/EditorUtils.java
+++ b/plugins/infra/ui/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/EditorUtils.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2008, 2013 CEA LIST and others.
+ * Copyright (c) 2008, 2016 CEA LIST, Christian W. Damus, and others.
*
*
* All rights reserved. This program and the accompanying materials
@@ -12,14 +12,17 @@
* <a href="mailto:thomas.szadel@atosorigin.com">Thomas Szadel</a>: Code simplification and NPE
* management.
* Christian W. Damus (CEA LIST) - API for determining URI of a resource in an editor
+ * Christian W. Damus - bug 496299
*
*****************************************************************************/
package org.eclipse.papyrus.infra.ui.util;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
import org.eclipse.emf.common.ui.URIEditorInput;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
@@ -35,6 +38,8 @@ import org.eclipse.papyrus.infra.core.services.ServiceException;
import org.eclipse.papyrus.infra.core.services.ServiceNotFoundException;
import org.eclipse.papyrus.infra.core.services.ServicesRegistry;
import org.eclipse.papyrus.infra.core.utils.DiResourceSet;
+import org.eclipse.papyrus.infra.emf.resource.ICrossReferenceIndex;
+import org.eclipse.papyrus.infra.emf.resource.ShardResourceHelper;
import org.eclipse.papyrus.infra.ui.Activator;
import org.eclipse.papyrus.infra.ui.editor.CoreMultiDiagramEditor;
import org.eclipse.papyrus.infra.ui.editor.IMultiDiagramEditor;
@@ -48,6 +53,10 @@ import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
/**
* Set of utility methods for the CoreEditor. <br>
* WARNING : Some of these methods rely on
@@ -81,7 +90,7 @@ public class EditorUtils {
if (page == null) {
return null;
}
- List<IMultiDiagramEditor> list = new ArrayList<IMultiDiagramEditor>();
+ List<IMultiDiagramEditor> list = new ArrayList<>();
for (IEditorReference editorRef : page.getEditorReferences()) {
IEditorPart editorPart = editorRef.getEditor(false);
if (editorPart instanceof IMultiDiagramEditor) {
@@ -109,7 +118,7 @@ public class EditorUtils {
if (openedEditors == null) {
return new IMultiDiagramEditor[0];
}
- List<IMultiDiagramEditor> list = new ArrayList<IMultiDiagramEditor>(openedEditors.length);
+ List<IMultiDiagramEditor> list = new ArrayList<>(openedEditors.length);
for (IMultiDiagramEditor editorPart : openedEditors) {
if (editorPart.getEditorInput() instanceof IFileEditorInput && diFile.equals(((IFileEditorInput) editorPart.getEditorInput()).getFile())) {
@@ -719,4 +728,56 @@ public class EditorUtils {
return result;
}
+
+ /**
+ * Resolves the root resource URI from the URI of a resource that may be a
+ * shard or may be an independent model unit.
+ *
+ * @param index
+ * the shard index to consult
+ * @param resourceURI
+ * a resource URI
+ *
+ * @return the root, which may just be the input resource URI if it is a root
+ *
+ * @see ShardResourceHelper
+ * @see ICrossReferenceIndex#getRoots(URI)
+ * @since 1.3
+ */
+ public static URI resolveShardRoot(ICrossReferenceIndex index, URI resourceURI) {
+ URI result;
+
+ try {
+ Set<URI> roots = index.getRoots(resourceURI);
+
+ // TODO: Handle case of multiple roots
+ result = Iterables.getFirst(roots, resourceURI);
+ } catch (CoreException e) {
+ Activator.log.log(e.getStatus());
+ result = resourceURI;
+ }
+
+ return result;
+ }
+
+ /**
+ * Asynchronously resolves the root resource URI from the URI of a resource that
+ * may be a shard or may be an independent model unit.
+ *
+ * @param index
+ * the shard index to consult
+ * @param resourceURI
+ * a resource URI
+ *
+ * @return the root, which may just be the input resource URI if it is a root
+ *
+ * @see ShardResourceHelper
+ * @see ICrossReferenceIndex#getRootsAsync(URI)
+ * @since 1.3
+ */
+ public static ListenableFuture<URI> resolveShardRootAsync(ICrossReferenceIndex index, URI resourceURI) {
+ // TODO: Handle case of multiple roots
+ return Futures.transform(index.getRootsAsync(resourceURI),
+ (Set<URI> roots) -> Iterables.getFirst(roots, resourceURI));
+ }
}
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/META-INF/MANIFEST.MF b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/META-INF/MANIFEST.MF
index 57aa6851c66..49a27b0cde2 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/META-INF/MANIFEST.MF
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/META-INF/MANIFEST.MF
@@ -17,7 +17,7 @@ Export-Package: org.eclipse.papyrus.uml.decoratormodel.internal.ui,
Bundle-Vendor: %providerName
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
-Bundle-Version: 1.2.0.qualifier
+Bundle-Version: 1.2.100.qualifier
Bundle-Localization: plugin
Bundle-Name: %pluginName
Bundle-Activator: org.eclipse.papyrus.uml.decoratormodel.internal.ui.Activator
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/pom.xml b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/pom.xml
index 8186d2bdf5d..e2c0459797d 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/pom.xml
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/pom.xml
@@ -7,6 +7,6 @@
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.papyrus.uml.decoratormodel.ui</artifactId>
- <version>1.2.0-SNAPSHOT</version>
+ <version>1.2.100-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/src/org/eclipse/papyrus/uml/decoratormodel/internal/ui/Activator.java b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/src/org/eclipse/papyrus/uml/decoratormodel/internal/ui/Activator.java
index 983a0ace86d..b355661ffc0 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/src/org/eclipse/papyrus/uml/decoratormodel/internal/ui/Activator.java
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel.ui/src/org/eclipse/papyrus/uml/decoratormodel/internal/ui/Activator.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2014 Christian W. Damus and others.
+ * Copyright (c) 2014, 2016 Christian W. Damus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -56,9 +56,6 @@ public class Activator extends AbstractUIPlugin {
plugin = this;
log = new LogHelper(this);
-
- // Kick the index
- DecoratorModelIndex.getInstance();
}
@Override
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/.classpath b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/.classpath
index 6a42377b56a..f0c55498599 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/.classpath
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/.classpath
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="src-gen"/>
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/.settings/org.eclipse.jdt.core.prefs b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/.settings/org.eclipse.jdt.core.prefs
index f08be2b06c4..b3aa6d60f94 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/.settings/org.eclipse.jdt.core.prefs
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/.settings/org.eclipse.jdt.core.prefs
@@ -1,10 +1,10 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
-org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.7
+org.eclipse.jdt.core.compiler.source=1.8
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/META-INF/MANIFEST.MF b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/META-INF/MANIFEST.MF
index a39f4e075ad..46f4031396f 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/META-INF/MANIFEST.MF
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/META-INF/MANIFEST.MF
@@ -3,6 +3,7 @@ Require-Bundle: org.eclipse.emf.ecore;bundle-version="[2.12.0,3.0.0)";visibility
org.eclipse.uml2.types;bundle-version="[2.0.0,3.0.0)";visibility:=reexport,
org.eclipse.uml2.uml;bundle-version="[5.2.0,6.0.0)";visibility:=reexport,
org.eclipse.uml2.common;bundle-version="[2.1.0,3.0.0)";visibility:=reexport,
+ org.eclipse.papyrus.infra.emf;bundle-version="[2.1.0,3.0.0)";visibility:=reexport,
org.eclipse.papyrus.uml.tools.utils;bundle-version="[2.0.0,3.0.0)",
org.eclipse.papyrus.uml.tools;bundle-version="[2.0.0,3.0.0)",
org.eclipse.papyrus.infra.emf.readonly;bundle-version="[2.0.0,3.0.0)"
@@ -24,11 +25,11 @@ Export-Package: org.eclipse.papyrus.uml.decoratormodel,
Bundle-Vendor: %providerName
Bundle-ActivationPolicy: lazy;exclude:=org.eclipse.papyrus.uml.decoratormodel.internal.expressions
Bundle-ClassPath: .
-Bundle-Version: 1.2.0.qualifier
+Bundle-Version: 1.2.100.qualifier
Bundle-Localization: plugin
Bundle-Name: %pluginName
Bundle-Activator: org.eclipse.papyrus.uml.decoratormodel.Activator
Bundle-ManifestVersion: 2
Bundle-Description: %pluginDescription
Bundle-SymbolicName: org.eclipse.papyrus.uml.decoratormodel;singleton:=true
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/plugin.xml b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/plugin.xml
index 52618d1c682..56eb68012fd 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/plugin.xml
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/plugin.xml
@@ -91,4 +91,11 @@
</service>
</extension>
+ <extension
+ point="org.eclipse.papyrus.infra.emf.index">
+ <indexProvider
+ class="org.eclipse.papyrus.uml.decoratormodel.internal.resource.DecoratorModelIndex$IndexProvider">
+ </indexProvider>
+ </extension>
+
</plugin>
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/pom.xml b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/pom.xml
index 9a828660cdb..157bff19397 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/pom.xml
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/pom.xml
@@ -7,6 +7,6 @@
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.papyrus.uml.decoratormodel</artifactId>
- <version>1.2.0-SNAPSHOT</version>
+ <version>1.2.100-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/Activator.java b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/Activator.java
index ac9b77baa38..f7d40d364c7 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/Activator.java
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/Activator.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2013, 2015 CEA LIST, Christian W. Damus, and others.
+ * Copyright (c) 2013, 2016 CEA LIST, Christian W. Damus, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -8,9 +8,7 @@
*
* Contributors:
* Remi Schnekenburger (CEA LIST) - Initial API and implementation
- * Christian W. Damus - bug 399859
- * Christian W. Damus - bug 456934
- * Christian W. Damus - bug 469464
+ * Christian W. Damus - bugs 399859, 456934, 469464, 496299
*
*****************************************************************************/
@@ -18,7 +16,6 @@ package org.eclipse.papyrus.uml.decoratormodel;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.papyrus.infra.core.log.LogHelper;
-import org.eclipse.papyrus.uml.decoratormodel.internal.resource.DecoratorModelIndex;
import org.osgi.framework.BundleContext;
/**
@@ -46,16 +43,10 @@ public class Activator extends Plugin {
super.start(context);
plugin = this;
log = new LogHelper(this);
-
- // Kick the index
- DecoratorModelIndex.getInstance();
}
@Override
public void stop(BundleContext context) throws Exception {
- // Shut down the index
- DecoratorModelIndex.getInstance().dispose();
-
plugin = null;
log = null;
super.stop(context);
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/DecoratorModelIndex.java b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/DecoratorModelIndex.java
index 36c4d837e59..f69eb2f1581 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/DecoratorModelIndex.java
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/DecoratorModelIndex.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2014, 2015 Christian W. Damus and others.
+ * Copyright (c) 2014, 2016 Christian W. Damus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -16,6 +16,7 @@ package org.eclipse.papyrus.uml.decoratormodel.internal.resource;
import static org.eclipse.papyrus.uml.decoratormodel.Activator.TRACE_INDEX;
import java.io.InputStream;
+import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@@ -23,6 +24,7 @@ import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import java.util.stream.Collectors;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
@@ -34,13 +36,15 @@ import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.URI;
+import org.eclipse.papyrus.infra.emf.resource.index.IWorkspaceModelIndexProvider;
import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndex;
-import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndex.IndexHandler;
+import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndex.PersistentIndexHandler;
import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndexAdapter;
import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndexEvent;
import org.eclipse.papyrus.uml.decoratormodel.Activator;
import org.eclipse.papyrus.uml.decoratormodel.helper.DecoratorModelUtils;
import org.eclipse.papyrus.uml.decoratormodel.internal.messages.Messages;
+import org.eclipse.papyrus.uml.decoratormodel.internal.resource.index.AbstractUMLIndexHandler;
import org.eclipse.papyrus.uml.decoratormodel.internal.resource.index.ModelIndexHandler;
import org.eclipse.papyrus.uml.decoratormodel.internal.resource.index.ProfileIndexHandler;
import org.eclipse.uml2.common.util.CacheAdapter;
@@ -79,17 +83,26 @@ public class DecoratorModelIndex {
private final Map<URI, SetMultimap<URI, URI>> userModelToResourceToAppliedProfiles = Maps.newHashMap();
- private final WorkspaceModelIndex<DecoratorModelIndex> index;
+ private final WorkspaceModelIndex<IndexedFile<?>> index;
private final CopyOnWriteArrayList<IDecoratorModelIndexListener> listeners = Lists.newCopyOnWriteArrayList();
+ static {
+ // This cannot be done in the constructor because it depends on the INSTANCE
+ // already being set
+ INSTANCE.initialize();
+ }
+
/**
* Not instantiable by clients.
*/
private DecoratorModelIndex() {
super();
- index = new WorkspaceModelIndex<DecoratorModelIndex>("papyrusUMLProfiles", UMLPackage.eCONTENT_TYPE, indexer(), MAX_INDEX_JOBS); //$NON-NLS-1$
+ index = new WorkspaceModelIndex<IndexedFile<?>>("papyrusUMLProfiles", UMLPackage.eCONTENT_TYPE, indexer(), MAX_INDEX_JOBS); //$NON-NLS-1$
+ }
+
+ private void initialize() {
index.addListener(new WorkspaceModelIndexAdapter() {
@Override
protected void indexAboutToCalculateOrRecalculate(WorkspaceModelIndexEvent event) {
@@ -565,13 +578,21 @@ public class DecoratorModelIndex {
}
}
- private void indexDecoratorModel(IFile file) {
+ private IndexedDecoratorModel indexDecoratorModel(IFile file) {
final URI decoratorURI = URI.createPlatformResourceURI(file.getFullPath().toString(), true);
ProfileIndexHandler handler = new ProfileIndexHandler(decoratorURI);
runIndexHandler(file, decoratorURI, handler);
+ IndexedDecoratorModel result = new IndexedDecoratorModel(handler);
+ indexDecoratorModel(file, result);
+ return result;
+ }
+
+ private void indexDecoratorModel(IFile file, IndexedDecoratorModel index) {
+ final URI decoratorURI = URI.createPlatformResourceURI(file.getFullPath().toString(), true);
+
synchronized (sync) {
// first, remove all links to the decorator model
for (URI next : decoratorToModels.get(decoratorURI)) {
@@ -584,10 +605,10 @@ public class DecoratorModelIndex {
}
// update the forward mapping
- decoratorToModels.replaceValues(decoratorURI, handler.getReferencedModelURIs());
+ decoratorToModels.replaceValues(decoratorURI, index.getReferencedModelURIs());
// update the reverse mapping
- for (URI next : handler.getReferencedModelURIs()) {
+ for (URI next : index.getReferencedModelURIs()) {
modelToDecorators.put(next, decoratorURI);
}
@@ -600,19 +621,19 @@ public class DecoratorModelIndex {
}
// update the package links
- for (URI next : handler.getPackageToProfileApplications().keySet()) {
+ for (URI next : index.getProfileApplicationsByPackage().keySet()) {
packageToDecoratorModels.put(next, decoratorURI);
}
// and update the package-to-profiles-to-definitions index
- decoratorModelToPackageToProfileApplications.put(decoratorURI, handler.getPackageToProfileApplications());
+ decoratorModelToPackageToProfileApplications.put(decoratorURI, index.getProfileApplicationsByPackage());
// and the externalization name index
- decoratorModelNames.put(decoratorURI, handler.getExternalizationName());
+ decoratorModelNames.put(decoratorURI, index.getExternalizationName());
// and the applied profiles by resource (external and internal)
Set<URI> userModelsProcessed = Sets.newHashSet();
- for (Map.Entry<URI, Map<URI, URI>> next : handler.getPackageToProfileApplications().entrySet()) {
+ for (Map.Entry<URI, Map<URI, URI>> next : index.getProfileApplicationsByPackage().entrySet()) {
URI userModelURI = next.getKey().trimFragment();
if (userModelsProcessed.add(userModelURI)) {
SetMultimap<URI, URI> resourceToAppliedProfiles = userModelToResourceToAppliedProfiles.get(userModelURI);
@@ -661,13 +682,21 @@ public class DecoratorModelIndex {
}
}
- private void indexUserModel(IFile file) {
+ private IndexedUserModel indexUserModel(IFile file) {
final URI userModelURI = URI.createPlatformResourceURI(file.getFullPath().toString(), true);
ModelIndexHandler handler = new ModelIndexHandler(userModelURI);
runIndexHandler(file, userModelURI, handler);
+ IndexedUserModel result = new IndexedUserModel(handler);
+ indexUserModel(file, result);
+ return result;
+ }
+
+ private void indexUserModel(IFile file, IndexedUserModel index) {
+ final URI userModelURI = URI.createPlatformResourceURI(file.getFullPath().toString(), true);
+
synchronized (sync) {
// remove all internal profile applications of this resource
SetMultimap<URI, URI> resourceToAppliedProfiles = userModelToResourceToAppliedProfiles.get(userModelURI);
@@ -676,7 +705,7 @@ public class DecoratorModelIndex {
}
// then add the applied profiles by resource (external and internal)
- for (Map<URI, URI> next : handler.getProfileApplicationsByPackage().values()) {
+ for (Map<URI, URI> next : index.getProfileApplicationsByPackage().values()) {
if (resourceToAppliedProfiles == null) {
resourceToAppliedProfiles = HashMultimap.create();
userModelToResourceToAppliedProfiles.put(userModelURI, resourceToAppliedProfiles);
@@ -698,16 +727,18 @@ public class DecoratorModelIndex {
}
}
- private IndexHandler<DecoratorModelIndex> indexer() {
- return new IndexHandler<DecoratorModelIndex>() {
+ private PersistentIndexHandler<IndexedFile<?>> indexer() {
+ return new PersistentIndexHandler<IndexedFile<?>>() {
@Override
- public DecoratorModelIndex index(IFile file) {
+ public IndexedFile<?> index(IFile file) {
+ IndexedFile<?> result;
+
if (DecoratorModelUtils.isDecoratorModel(file)) {
- DecoratorModelIndex.this.indexDecoratorModel(file);
+ result = DecoratorModelIndex.this.indexDecoratorModel(file);
} else {
- DecoratorModelIndex.this.indexUserModel(file);
+ result = DecoratorModelIndex.this.indexUserModel(file);
}
- return DecoratorModelIndex.this;
+ return result;
}
@Override
@@ -715,6 +746,21 @@ public class DecoratorModelIndex {
DecoratorModelIndex.this.unindexDecoratorModel(file);
DecoratorModelIndex.this.unindexUserModel(file);
}
+
+ @Override
+ public boolean load(IFile file, IndexedFile<?> index) {
+ boolean result = true;
+
+ if (index instanceof IndexedDecoratorModel) {
+ DecoratorModelIndex.this.indexDecoratorModel(file, (IndexedDecoratorModel) index);
+ } else if (index instanceof IndexedUserModel) {
+ DecoratorModelIndex.this.indexUserModel(file, (IndexedUserModel) index);
+ } else {
+ result = false;
+ }
+
+ return result;
+ }
};
}
@@ -753,4 +799,88 @@ public class DecoratorModelIndex {
protected abstract V doCall();
}
+
+ static abstract class IndexedFile<H extends AbstractUMLIndexHandler> implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private Map<String, Map<String, String>> profileApplicationByPackage;
+
+ private transient Map<URI, Map<URI, URI>> profileApplicationByPackageURI;
+
+ IndexedFile(H handler) {
+ super();
+
+ profileApplicationByPackage = handler.getProfileApplicationsByPackage();
+ }
+
+ Map<URI, Map<URI, URI>> getProfileApplicationsByPackage() {
+ if (profileApplicationByPackageURI == null) {
+ profileApplicationByPackageURI = mapOfMapsToURIs(profileApplicationByPackage);
+ }
+ return profileApplicationByPackageURI;
+ }
+
+ final Set<URI> setToURIs(Set<String> uris) {
+ return uris.stream()
+ .map(URI::createURI)
+ .collect(Collectors.toSet());
+ }
+
+ final Map<URI, Map<URI, URI>> mapOfMapsToURIs(Map<String, Map<String, String>> uris) {
+ Map<URI, Map<URI, URI>> result = Maps.newHashMap();
+ uris.forEach((uri, map) -> {
+ Map<URI, URI> uriMap = Maps.newHashMap();
+ map.forEach((a, b) -> uriMap.put(URI.createURI(a), URI.createURI(b)));
+ result.put(URI.createURI(uri), uriMap);
+ });
+ return result;
+ }
+ }
+
+ static final class IndexedDecoratorModel extends IndexedFile<ProfileIndexHandler> {
+
+ private static final long serialVersionUID = 1L;
+
+ private String externalizationName;
+ private Set<String> referencedModels;
+
+ private transient Set<URI> referencedModelURIs;
+
+ IndexedDecoratorModel(ProfileIndexHandler handler) {
+ super(handler);
+
+ externalizationName = handler.getExternalizationName();
+ referencedModels = handler.getReferencedModelURIs();
+ }
+
+ String getExternalizationName() {
+ return externalizationName;
+ }
+
+ Set<URI> getReferencedModelURIs() {
+ if (referencedModelURIs == null) {
+ referencedModelURIs = setToURIs(referencedModels);
+ }
+ return referencedModelURIs;
+ }
+ }
+
+ static final class IndexedUserModel extends IndexedFile<ModelIndexHandler> {
+
+ private static final long serialVersionUID = 1L;
+
+ IndexedUserModel(ModelIndexHandler handler) {
+ super(handler);
+ }
+ }
+
+ /**
+ * Index provider on the extension point.
+ */
+ public static final class IndexProvider implements IWorkspaceModelIndexProvider {
+ @Override
+ public WorkspaceModelIndex<?> get() {
+ return DecoratorModelIndex.INSTANCE.index;
+ }
+ }
}
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/AbstractUMLIndexHandler.java b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/AbstractUMLIndexHandler.java
index 1eb4b23d987..0daf46e3964 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/AbstractUMLIndexHandler.java
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/AbstractUMLIndexHandler.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2014, 2015 Christian W. Damus and others.
+ * Copyright (c) 2014, 2016 Christian W. Damus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -35,7 +35,7 @@ import com.google.common.collect.Maps;
*/
public abstract class AbstractUMLIndexHandler extends DefaultHandler {
protected final URI fileURI;
- private final Map<URI, Map<URI, URI>> packageToProfileApplications = Maps.newHashMap();
+ private final Map<String, Map<String, String>> packageToProfileApplications = Maps.newHashMap();
private String umlNamespace;
private String umlPrefix;
@@ -67,7 +67,7 @@ public abstract class AbstractUMLIndexHandler extends DefaultHandler {
return fileURI;
}
- public Map<URI, Map<URI, URI>> getProfileApplicationsByPackage() {
+ public Map<String, Map<String, String>> getProfileApplicationsByPackage() {
return packageToProfileApplications;
}
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ModelIndexHandler.java b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ModelIndexHandler.java
index 3fa5f3a2947..964838a5b5e 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ModelIndexHandler.java
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ModelIndexHandler.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2015 Christian W. Damus and others.
+ * Copyright (c) 2015, 2016 Christian W. Damus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -33,7 +33,6 @@ import com.google.common.collect.Multimap;
* </ul>
*/
public class ModelIndexHandler extends AbstractUMLIndexHandler {
- private final Map<URI, Map<URI, URI>> packageToProfileApplications = Maps.newHashMap();
private final Multimap<String, URIPair> packageProfileApplications = ArrayListMultimap.create();
private String profileApplication;
@@ -57,11 +56,6 @@ public class ModelIndexHandler extends AbstractUMLIndexHandler {
}
@Override
- public Map<URI, Map<URI, URI>> getProfileApplicationsByPackage() {
- return packageToProfileApplications;
- }
-
- @Override
protected void initializeUMLElementNames() {
super.initializeUMLElementNames();
@@ -107,15 +101,15 @@ public class ModelIndexHandler extends AbstractUMLIndexHandler {
URI applyingPackageURI = fileURI.appendFragment(packageID);
Collection<URIPair> profileApplications = packageProfileApplications.get(packageID);
if (!profileApplications.isEmpty()) {
- Map<URI, URI> map = packageToProfileApplications.get(applyingPackageURI);
+ Map<String, String> map = getProfileApplicationsByPackage().get(applyingPackageURI.toString());
if (map == null) {
map = Maps.newHashMap();
- packageToProfileApplications.put(applyingPackageURI, map);
+ getProfileApplicationsByPackage().put(applyingPackageURI.toString(), map);
}
for (URIPair profileApplication : profileApplications) {
// If we can't determine the Ecore definition, the profile is not properly applied
if (profileApplication.second != null) {
- map.put(profileApplication.first, profileApplication.second);
+ map.put(profileApplication.first.toString(), profileApplication.second.toString());
}
}
}
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ProfileIndexHandler.java b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ProfileIndexHandler.java
index f5b7e20387f..448ee4f54e8 100644
--- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ProfileIndexHandler.java
+++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ProfileIndexHandler.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2014, 2015 Christian W. Damus and others.
+ * Copyright (c) 2014, 2016 Christian W. Damus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -33,8 +33,7 @@ import com.google.common.collect.Sets;
* </ul>
*/
public class ProfileIndexHandler extends AbstractUMLIndexHandler {
- private final Set<URI> referencedModelURIs = Sets.newHashSet();
- private final Map<URI, Map<URI, URI>> packageToProfileApplications = Maps.newHashMap();
+ private final Set<String> referencedModelURIs = Sets.newHashSet();
private String externalizationName;
private String dependencyType;
@@ -56,14 +55,10 @@ public class ProfileIndexHandler extends AbstractUMLIndexHandler {
super(fileURI);
}
- public Set<URI> getReferencedModelURIs() {
+ public Set<String> getReferencedModelURIs() {
return referencedModelURIs;
}
- public Map<URI, Map<URI, URI>> getPackageToProfileApplications() {
- return packageToProfileApplications;
- }
-
public String getExternalizationName() {
return externalizationName == null ? "<unnamed>" : externalizationName;
}
@@ -117,7 +112,7 @@ public class ProfileIndexHandler extends AbstractUMLIndexHandler {
protected void handleDependencyClient(UMLElement client) {
URI href = client.getHREF();
if (href != null) {
- referencedModelURIs.add(href.trimFragment());
+ referencedModelURIs.add(href.trimFragment().toString());
packageClients.put(currentPackage.id, href);
}
}
@@ -144,15 +139,15 @@ public class ProfileIndexHandler extends AbstractUMLIndexHandler {
if (applyingPackageURI != null) {
Collection<URIPair> profileApplications = packageProfileApplications.get(packageID);
if (!profileApplications.isEmpty()) {
- Map<URI, URI> map = packageToProfileApplications.get(applyingPackageURI);
+ Map<String, String> map = getProfileApplicationsByPackage().get(applyingPackageURI.toString());
if (map == null) {
map = Maps.newHashMap();
- packageToProfileApplications.put(applyingPackageURI, map);
+ getProfileApplicationsByPackage().put(applyingPackageURI.toString(), map);
}
for (URIPair profileApplication : profileApplications) {
// If we can't determine the Ecore definition, the profile is not properly applied
if (profileApplication.second != null) {
- map.put(profileApplication.first, profileApplication.second);
+ map.put(profileApplication.first.toString(), profileApplication.second.toString());
}
}
}
diff --git a/plugins/uml/tools/org.eclipse.papyrus.uml.tools/src/org/eclipse/papyrus/uml/tools/providers/SemanticUMLContentProvider.java b/plugins/uml/tools/org.eclipse.papyrus.uml.tools/src/org/eclipse/papyrus/uml/tools/providers/SemanticUMLContentProvider.java
index 5345394c484..fa0bb6e144a 100644
--- a/plugins/uml/tools/org.eclipse.papyrus.uml.tools/src/org/eclipse/papyrus/uml/tools/providers/SemanticUMLContentProvider.java
+++ b/plugins/uml/tools/org.eclipse.papyrus.uml.tools/src/org/eclipse/papyrus/uml/tools/providers/SemanticUMLContentProvider.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2012, 2014 CEA LIST, Christian W. Damus, and others.
+ * Copyright (c) 2012, 2016 CEA LIST, Christian W. Damus, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -9,13 +9,14 @@
* Contributors:
* Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
* Christian W. Damus (CEA) - bug 410346
- * Christian W. Damus - bug 451338
+ * Christian W. Damus - bugs 451338, 496299
*
*****************************************************************************/
package org.eclipse.papyrus.uml.tools.providers;
import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.URI;
@@ -199,8 +200,7 @@ public class SemanticUMLContentProvider extends SemanticEMFContentProvider {
}
}
return res;
- }
- else if (metaclass instanceof EClass) {
+ } else if (metaclass instanceof EClass) {
EClass metaEClass = (EClass) metaclass;
for (EObject stereotypeApplication : semanticElement.getStereotypeApplications()) {
if (metaEClass.isSuperTypeOf(stereotypeApplication.eClass())) {
@@ -254,8 +254,7 @@ public class SemanticUMLContentProvider extends SemanticEMFContentProvider {
if (stereotypeApplication != null) {
return stereotypeApplication;
}
- }
- else if (metaclassWanted instanceof EClass) {
+ } else if (metaclassWanted instanceof EClass) {
// new check based on EClass (stereotype definition)
EClass metaEClassWanted = (EClass) metaclassWanted;
for (EObject stereotypeApplication : element.getStereotypeApplications()) {
@@ -327,7 +326,7 @@ public class SemanticUMLContentProvider extends SemanticEMFContentProvider {
private class RootsAdapter {
- private boolean needsRefresh = false;
+ private final AtomicReference<Runnable> pendingRefresh = new AtomicReference<>();
private Object listener;
@@ -379,21 +378,30 @@ public class SemanticUMLContentProvider extends SemanticEMFContentProvider {
private synchronized void triggerRefresh() {
roots = getRoots(root);
+
// During display, a resource has been loaded (e.g. by a Label provider).
// Schedule an update (in the future, to avoid conflicts with a potential current update)
- if (viewer != null && viewer.getControl() != null && !viewer.getControl().isDisposed()) {
- needsRefresh = true;
- viewer.getControl().getDisplay().asyncExec(new Runnable() {
+ if ((viewer != null) && (viewer.getControl() != null) && !viewer.getControl().isDisposed()) {
+ if (pendingRefresh.compareAndSet(null, new Runnable() {
@Override
public void run() {
- if (!needsRefresh || viewer == null || viewer.getControl() == null || viewer.getControl().isDisposed()) {
+ if (!pendingRefresh.compareAndSet(this, null)
+ || (viewer == null) || (viewer.getControl() == null)
+ || viewer.getControl().isDisposed()) {
return;
}
- needsRefresh = false;
+
+ // Because containment proxy resolution that sets a root's
+ // eContainer is not detected by the adapter, so recompute
+ // again, now
+ roots = getRoots(root);
+
viewer.refresh();
};
- });
+ })) {
+ viewer.getControl().getDisplay().asyncExec(pendingRefresh.get());
+ }
}
}
}
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/.classpath b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/.classpath
index 4ff9e9735c7..293e77968b0 100644
--- a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/.classpath
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/.classpath
@@ -1,7 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
- <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins">
+ <accessrules>
+ <accessrule kind="accessible" pattern="org/eclipse/papyrus/infra/emf/internal/**"/>
+ </accessrules>
+ </classpathentry>
<classpathentry kind="src" path="tests"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/.settings/org.eclipse.jdt.core.prefs b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/.settings/org.eclipse.jdt.core.prefs
index 9ec8d31b26b..43d10874e2c 100644
--- a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/.settings/org.eclipse.jdt.core.prefs
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/.settings/org.eclipse.jdt.core.prefs
@@ -1,14 +1,14 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.7
+org.eclipse.jdt.core.compiler.source=1.8
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/META-INF/MANIFEST.MF b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/META-INF/MANIFEST.MF
index 3f619512f80..5762bedc61a 100644
--- a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/META-INF/MANIFEST.MF
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/META-INF/MANIFEST.MF
@@ -1,19 +1,20 @@
Manifest-Version: 1.0
Export-Package: org.eclipse.papyrus.infra.emf.advice,
+ org.eclipse.papyrus.infra.emf.resource,
org.eclipse.papyrus.infra.emf.resource.index,
org.eclipse.papyrus.infra.emf.tests,
org.eclipse.papyrus.infra.emf.utils
Require-Bundle: org.eclipse.emf.ecore.xmi;bundle-version="2.8.0",
org.junit;bundle-version="4.10.0",
org.eclipse.uml2.uml;bundle-version="5.0.0",
+ org.eclipse.papyrus.infra.emf;bundle-version="1.3.0",
org.eclipse.papyrus.junit.framework;bundle-version="1.2.0",
org.eclipse.papyrus.junit.utils;bundle-version="1.2.0",
- org.eclipse.papyrus.infra.types.core;bundle-version="1.2.0",
- org.eclipse.papyrus.infra.emf;bundle-version="1.2.0"
+ org.eclipse.papyrus.infra.types.core;bundle-version="1.2.0"
Bundle-Vendor: %providerName
-Bundle-Version: 1.2.0.qualifier
+Bundle-Version: 1.4.0.qualifier
Bundle-Name: %pluginName
Bundle-Localization: fragment
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.eclipse.papyrus.infra.emf.tests;singleton:=true
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/pom.xml b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/pom.xml
index 779f465b911..84adf7aa7c5 100644
--- a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/pom.xml
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/pom.xml
@@ -10,6 +10,6 @@
</parent>
<groupId>org.eclipse.papyrus</groupId>
<artifactId>org.eclipse.papyrus.infra.emf.tests</artifactId>
- <version>1.2.0-SNAPSHOT</version>
+ <version>1.4.0-SNAPSHOT</version>
<packaging>eclipse-test-plugin</packaging>
</project>
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package1.uml b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package1.uml
new file mode 100644
index 00000000000..c6669a567a2
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package1.uml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xmi:XMI xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:Ecore="http://www.eclipse.org/uml2/schemas/Ecore/5" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xsi:schemaLocation="http://www.eclipse.org/uml2/schemas/Ecore/5 pathmap://UML_PROFILES/Ecore.profile.uml#_z1OFcHjqEdy8S4Cr8Rc_NA">
+ <uml:Package xmi:id="_M6uS4EYCEeagQvw0AruCcg" name="package1">
+ <eAnnotations xmi:id="_0iFCIEYDEeagQvw0AruCcg" source="http://www.eclipse.org/papyrus/2016/resource/shard">
+ <references xmi:type="uml:Model" href="root.uml#_-T-r8EYBEeagQvw0AruCcg"/>
+ </eAnnotations>
+ <packagedElement xmi:type="uml:Package" href="package1/packageA.uml#_R5WJAEYCEeagQvw0AruCcg"/>
+ </uml:Package>
+ <Ecore:EPackage xmi:id="_esXvkEYCEeagQvw0AruCcg" base_Package="_M6uS4EYCEeagQvw0AruCcg"/>
+</xmi:XMI>
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package1/packageA.uml b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package1/packageA.uml
new file mode 100644
index 00000000000..5e245519bcd
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package1/packageA.uml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xmi:XMI xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:Ecore="http://www.eclipse.org/uml2/schemas/Ecore/5" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xsi:schemaLocation="http://www.eclipse.org/uml2/schemas/Ecore/5 pathmap://UML_PROFILES/Ecore.profile.uml#_z1OFcHjqEdy8S4Cr8Rc_NA">
+ <uml:Package xmi:id="_R5WJAEYCEeagQvw0AruCcg" name="packageA">
+ <eAnnotations xmi:id="_axVn8EYDEeagQvw0AruCcg" source="http://www.eclipse.org/papyrus/2016/resource/shard">
+ <references xmi:type="uml:Package" href="../package1.uml#_M6uS4EYCEeagQvw0AruCcg"/>
+ </eAnnotations>
+ <packagedElement xmi:type="uml:Class" href="packageA/foo.uml#_VYUxUEYCEeagQvw0AruCcg"/>
+ </uml:Package>
+ <Ecore:EPackage xmi:id="_dxA6kEYCEeagQvw0AruCcg" base_Package="_R5WJAEYCEeagQvw0AruCcg"/>
+</xmi:XMI>
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package1/packageA/foo.uml b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package1/packageA/foo.uml
new file mode 100644
index 00000000000..d538aa7d82a
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package1/packageA/foo.uml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xmi:XMI xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:Ecore="http://www.eclipse.org/uml2/schemas/Ecore/5" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xsi:schemaLocation="http://www.eclipse.org/uml2/schemas/Ecore/5 pathmap://UML_PROFILES/Ecore.profile.uml#_z1OFcHjqEdy8S4Cr8Rc_NA">
+ <uml:Class xmi:id="_VYUxUEYCEeagQvw0AruCcg" name="Foo">
+ <eAnnotations xmi:id="_anqukEYDEeagQvw0AruCcg" source="http://www.eclipse.org/papyrus/2016/resource/shard">
+ <references xmi:type="uml:Package" href="../packageA.uml#_R5WJAEYCEeagQvw0AruCcg"/>
+ </eAnnotations>
+ </uml:Class>
+ <Ecore:EClass xmi:id="_anRicEYCEeagQvw0AruCcg" base_Class="_VYUxUEYCEeagQvw0AruCcg"/>
+</xmi:XMI>
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package2.uml b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package2.uml
new file mode 100644
index 00000000000..0d530b1d276
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package2.uml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xmi:XMI xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:Ecore="http://www.eclipse.org/uml2/schemas/Ecore/5" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xsi:schemaLocation="http://www.eclipse.org/uml2/schemas/Ecore/5 pathmap://UML_PROFILES/Ecore.profile.uml#_z1OFcHjqEdy8S4Cr8Rc_NA">
+ <uml:Package xmi:id="_Qdf2EEYCEeagQvw0AruCcg" name="package2">
+ <eAnnotations xmi:id="_bGexgEYDEeagQvw0AruCcg" source="http://www.eclipse.org/papyrus/2016/resource/shard">
+ <references xmi:type="uml:Model" href="root.uml#_-T-r8EYBEeagQvw0AruCcg"/>
+ </eAnnotations>
+ <packagedElement xmi:type="uml:Package" href="package2/packageB.uml#_THKnAEYCEeagQvw0AruCcg"/>
+ </uml:Package>
+ <Ecore:EPackage xmi:id="_fsK_sEYCEeagQvw0AruCcg" base_Package="_Qdf2EEYCEeagQvw0AruCcg"/>
+</xmi:XMI>
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package2/packageB.uml b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package2/packageB.uml
new file mode 100644
index 00000000000..e8b1a5838ba
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package2/packageB.uml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xmi:XMI xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:Ecore="http://www.eclipse.org/uml2/schemas/Ecore/5" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xsi:schemaLocation="http://www.eclipse.org/uml2/schemas/Ecore/5 pathmap://UML_PROFILES/Ecore.profile.uml#_z1OFcHjqEdy8S4Cr8Rc_NA">
+ <uml:Package xmi:id="_THKnAEYCEeagQvw0AruCcg" name="packageB">
+ <eAnnotations xmi:id="_accWAEYDEeagQvw0AruCcg" source="http://www.eclipse.org/papyrus/2016/resource/shard">
+ <references xmi:type="uml:Package" href="../package2.uml#_Qdf2EEYCEeagQvw0AruCcg"/>
+ </eAnnotations>
+ <packagedElement xmi:type="uml:Class" href="packageB/bar.uml#_Yg658EYCEeagQvw0AruCcg"/>
+ </uml:Package>
+ <Ecore:EPackage xmi:id="_gopPAEYCEeagQvw0AruCcg" base_Package="_THKnAEYCEeagQvw0AruCcg"/>
+</xmi:XMI>
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package2/packageB/bar.uml b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package2/packageB/bar.uml
new file mode 100644
index 00000000000..db8808c46dd
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/package2/packageB/bar.uml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xmi:XMI xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:Ecore="http://www.eclipse.org/uml2/schemas/Ecore/5" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xsi:schemaLocation="http://www.eclipse.org/uml2/schemas/Ecore/5 pathmap://UML_PROFILES/Ecore.profile.uml#_z1OFcHjqEdy8S4Cr8Rc_NA">
+ <uml:Class xmi:id="_Yg658EYCEeagQvw0AruCcg" name="Bar">
+ <eAnnotations xmi:id="_P_TowEYDEeagQvw0AruCcg" source="http://www.eclipse.org/papyrus/2016/resource/shard">
+ <references xmi:type="uml:Package" href="../packageB.uml#_THKnAEYCEeagQvw0AruCcg"/>
+ </eAnnotations>
+ </uml:Class>
+ <Ecore:EClass xmi:id="_cfH04EYCEeagQvw0AruCcg" base_Class="_Yg658EYCEeagQvw0AruCcg"/>
+</xmi:XMI>
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/referencing.uml b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/referencing.uml
new file mode 100644
index 00000000000..4d575d4e308
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/referencing.uml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<uml:Model xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xmi:id="_0S-gEEYMEeagQvw0AruCcg" name="referencing">
+ <packagedElement xmi:type="uml:Package" xmi:id="_wTjjMEd3Eeayp81rgar4Qw" name="package1">
+ <packagedElement xmi:type="uml:Package" xmi:id="_y4j2kEd3Eeayp81rgar4Qw" name="packageA">
+ <packagedElement xmi:type="uml:Class" xmi:id="_3QKC4EYMEeagQvw0AruCcg" name="Subclass">
+ <generalization xmi:id="_9sFkgEYMEeagQvw0AruCcg">
+ <general xmi:type="uml:Class" href="package2/packageB/bar.uml#_Yg658EYCEeagQvw0AruCcg"/>
+ </generalization>
+ </packagedElement>
+ </packagedElement>
+ </packagedElement>
+ <packagedElement xmi:type="uml:Package" xmi:id="_yLgjUEd3Eeayp81rgar4Qw" name="package2">
+ <packagedElement xmi:type="uml:Package" xmi:id="_0ZwjkEd3Eeayp81rgar4Qw" name="packageB">
+ <packagedElement xmi:type="uml:Class" xmi:id="_2f4ikEd3Eeayp81rgar4Qw" name="Quux">
+ <generalization xmi:id="_2f4ikUd3Eeayp81rgar4Qw">
+ <general xmi:type="uml:Class" href="package2/packageB/bar.uml#_Yg658EYCEeagQvw0AruCcg"/>
+ </generalization>
+ </packagedElement>
+ </packagedElement>
+ </packagedElement>
+</uml:Model>
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/root.di b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/root.di
new file mode 100644
index 00000000000..bf9abab340f
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/root.di
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI"/>
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/root.notation b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/root.notation
new file mode 100644
index 00000000000..bf9abab340f
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/root.notation
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI"/>
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/root.uml b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/root.uml
new file mode 100644
index 00000000000..5427fd15867
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/resources/shards/root.uml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<uml:Model xmi:version="20131001" xmlns:xmi="http://www.omg.org/spec/XMI/20131001" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xmi:id="_-T-r8EYBEeagQvw0AruCcg" name="root">
+ <packagedElement xmi:type="uml:Package" href="package1.uml#_M6uS4EYCEeagQvw0AruCcg"/>
+ <packagedElement xmi:type="uml:Package" href="package2.uml#_Qdf2EEYCEeagQvw0AruCcg"/>
+ <profileApplication xmi:id="_-mL2QEYBEeagQvw0AruCcg">
+ <eAnnotations xmi:id="_-mNrcEYBEeagQvw0AruCcg" source="http://www.eclipse.org/uml2/2.0.0/UML">
+ <references xmi:type="ecore:EPackage" href="pathmap://UML_PROFILES/Ecore.profile.uml#_z1OFcHjqEdy8S4Cr8Rc_NA"/>
+ </eAnnotations>
+ <appliedProfile href="pathmap://UML_PROFILES/Ecore.profile.uml#_0"/>
+ </profileApplication>
+</uml:Model>
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/AbstractCrossReferenceIndexTest.java b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/AbstractCrossReferenceIndexTest.java
new file mode 100644
index 00000000000..3c251e5010a
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/AbstractCrossReferenceIndexTest.java
@@ -0,0 +1,182 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.resource;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.papyrus.infra.emf.internal.resource.CrossReferenceIndex;
+import org.eclipse.papyrus.infra.emf.internal.resource.OnDemandCrossReferenceIndex;
+import org.eclipse.papyrus.junit.utils.rules.AbstractHouseKeeperRule.CleanUp;
+import org.eclipse.papyrus.junit.utils.rules.AnnotationRule;
+import org.eclipse.papyrus.junit.utils.rules.HouseKeeper;
+import org.eclipse.papyrus.junit.utils.rules.ProjectFixture;
+import org.eclipse.uml2.uml.Classifier;
+import org.eclipse.uml2.uml.Element;
+import org.eclipse.uml2.uml.Package;
+import org.eclipse.uml2.uml.Stereotype;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Test framework for cross-reference index and shards-related tests.
+ */
+public abstract class AbstractCrossReferenceIndexTest {
+
+ @ClassRule
+ public static final ProjectFixture project = new ProjectFixture();
+
+ @Rule
+ public final HouseKeeper housekeeper = new HouseKeeper();
+
+ @Rule
+ public final AnnotationRule<String[]> resourcePaths = AnnotationRule.create(ProjectResource.class);
+
+ protected ResourceSet fixture;
+
+ @CleanUp
+ protected List<Resource> initialResources;
+
+ private final Function<? super ResourceSet, ? extends ICrossReferenceIndex> indexFunction;
+
+ /**
+ * Initializes me.
+ */
+ public AbstractCrossReferenceIndexTest() {
+ this(null);
+ }
+
+ /**
+ * Initializes me.
+ *
+ * @param onDemand
+ * {@code true} to force the on-demand index, {@code false} to force the
+ * full index, or {@code null} to let the framework decide the best
+ * available index
+ */
+ public AbstractCrossReferenceIndexTest(Boolean onDemand) {
+ super();
+
+ if (onDemand == null) {
+ // Best available
+ indexFunction = ICrossReferenceIndex::getInstance;
+ } else if (onDemand) {
+ // Force the on-demand index
+ indexFunction = OnDemandCrossReferenceIndex::new;
+ } else {
+ // Force the full index
+ indexFunction = __ -> CrossReferenceIndex.getInstance();
+ }
+ }
+
+ @BeforeClass
+ public static void createProjectContents() {
+ List<String> resources = ImmutableList.of(
+ "root.uml",
+ "package1.uml",
+ "package1/packageA.uml",
+ "package1/packageA/foo.uml",
+ "package2.uml",
+ "package2/packageB.uml",
+ "package2/packageB/bar.uml",
+ "referencing.uml");
+
+ resources.forEach(res -> {
+ try {
+ project.createFile(res, AbstractCrossReferenceIndexTest.class, "resources/shards/" + res);
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail("Failed to create test resource: " + e.getMessage());
+ }
+ });
+ }
+
+ @Before
+ public void createFixture() {
+ fixture = housekeeper.createResourceSet();
+
+ new ShardResourceLocator((ResourceSetImpl) fixture, index());
+
+ // Load resources
+ if (resourcePaths.get() != null) {
+ URI base = baseURI();
+ initialResources = Stream.of(resourcePaths.get())
+ .map(URI::createURI)
+ .map(uri -> uri.resolve(base))
+ .map(uri -> fixture.getResource(uri, true))
+ .collect(Collectors.toList());
+ }
+ }
+
+ protected final ICrossReferenceIndex index() {
+ return indexFunction.apply(fixture);
+ }
+
+ URI baseURI() {
+ return URI.createPlatformResourceURI(project.getProject().getName() + "/", true);
+ }
+
+ protected URI uri(String path) {
+ URI result = URI.createURI(path, true);
+ return result.resolve(baseURI());
+ }
+
+ protected Resource requireLoaded(String path) {
+ Resource result = fixture.getResource(uri(path), false);
+ assertThat("resource not created: " + path, result, notNullValue());
+ assertThat("resource not loaded: " + path, result.isLoaded(), is(true));
+ return result;
+ }
+
+ protected Stereotype requireStereotype(Element element, String qualifiedName) {
+ Stereotype result = element.getApplicableStereotype(qualifiedName);
+ assertThat("stereotype not applicable: " + qualifiedName, result, notNullValue());
+ assertThat("stereotype not applied: " + qualifiedName, element.isStereotypeApplied(result), is(true));
+ return result;
+ }
+
+ protected Stereotype requireEClass(Classifier classifier) {
+ return requireStereotype(classifier, "Ecore::EClass");
+ }
+
+ protected Stereotype requireEPackage(Package package_) {
+ return requireStereotype(package_, "Ecore::EPackage");
+ }
+
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ProjectResource {
+ String[] value();
+ }
+}
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/CrossReferenceIndexTest.java b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/CrossReferenceIndexTest.java
new file mode 100644
index 00000000000..d3fcca35732
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/CrossReferenceIndexTest.java
@@ -0,0 +1,150 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.resource;
+
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.papyrus.infra.emf.internal.resource.CrossReferenceIndex;
+import org.eclipse.uml2.uml.resource.UMLResource;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.SetMultimap;
+
+/**
+ * Tests for the {@link CrossReferenceIndex} class, the full indexer.
+ */
+public class CrossReferenceIndexTest extends AbstractCrossReferenceIndexTest {
+
+ /**
+ * Initializes me.
+ */
+ public CrossReferenceIndexTest() {
+ super(false); // Always the full indexer
+ }
+
+ //
+ // Don't need to load any resources for these tests
+ //
+
+ @Test
+ public void isShard() throws Exception {
+ assertThat(index().isShard(uri("package1/packageA/foo.uml")), is(true));
+ assertThat(index().isShard(uri("package1/packageA.uml")), is(true));
+ assertThat(index().isShard(uri("package1.uml")), is(true));
+ assertThat(index().isShard(uri("root.uml")), is(false));
+ }
+
+ @Test
+ public void shards() throws Exception {
+ assertThat(index().getShards(uri("package1/packageA/foo.uml")), is(emptySet()));
+ assertThat(index().getShards(uri("package1/packageA.uml")), is(singleton(uri("package1/packageA/foo.uml"))));
+ assertThat(index().getShards(uri("package1.uml")), is(singleton(uri("package1/packageA.uml"))));
+ assertThat(index().getShards(uri("root.uml")), // This one has two shards
+ is(ImmutableSet.of(uri("package1.uml"), uri("package2.uml"))));
+ }
+
+ @Test
+ public void parentShards() throws Exception {
+ assertThat(index().getParents(uri("package1/packageA/foo.uml")), is(singleton(uri("package1/packageA.uml"))));
+ assertThat(index().getParents(uri("package1/packageA.uml")), is(singleton(uri("package1.uml"))));
+ assertThat(index().getParents(uri("package1.uml")), is(singleton(uri("root.uml"))));
+ assertThat(index().getParents(uri("root.uml")), is(emptySet()));
+ }
+
+ @Test
+ public void roots() throws Exception {
+ assertThat(index().getRoots(uri("package1/packageA/foo.uml")), is(singleton(uri("root.uml"))));
+ assertThat(index().getRoots(uri("package1/packageA.uml")), is(singleton(uri("root.uml"))));
+ assertThat(index().getRoots(uri("package1.uml")), is(singleton(uri("root.uml"))));
+
+ // A root has no parents and, therefore, no root
+ assertThat(index().getRoots(uri("root.uml")), is(emptySet()));
+
+ // And this one has nothing to do with shards
+ assertThat(index().getRoots(uri("referencing.uml")), is(emptySet()));
+ }
+
+ @Test
+ public void outgoingReferences_givenURI() throws Exception {
+ // Shard relationship (cross-resource containment) is not a cross-reference
+ assertThat(index().getOutgoingCrossReferences(uri("package1.uml")), is(emptySet()));
+
+ // We find cross-references to non-workspace resources, though those aren't indexed
+ assertThat(index().getOutgoingCrossReferences(uri("root.uml")),
+ is(singleton(URI.createURI(UMLResource.ECORE_PROFILE_URI))));
+
+ // This has a cross-reference in the class generalization
+ assertThat(index().getOutgoingCrossReferences(uri("referencing.uml")),
+ is(singleton(uri("package2/packageB/bar.uml"))));
+
+ // This API is generalized for the Papyrus one-file
+ assertThat(index().getOutgoingCrossReferences(uri("referencing.di")),
+ is(singleton(uri("package2/packageB/bar.di"))));
+ }
+
+ @Test
+ public void incomingReferences_givenURI() throws Exception {
+ // Parent pointer in shard annotation is not a cross-reference
+ assertThat(index().getIncomingCrossReferences(uri("root.uml")), is(emptySet()));
+ assertThat(index().getIncomingCrossReferences(uri("package1.uml")), is(emptySet()));
+
+ // This has a cross-reference in the class generalization
+ assertThat(index().getIncomingCrossReferences(uri("package2/packageB/bar.uml")),
+ is(singleton(uri("referencing.uml"))));
+
+ // This API is generalized for the Papyrus one-file
+ assertThat(index().getIncomingCrossReferences(uri("package2/packageB/bar.di")),
+ is(singleton(uri("referencing.di"))));
+ }
+
+ @Test
+ public void outgoingReferences() throws Exception {
+ SetMultimap<URI, URI> xrefs = index().getOutgoingCrossReferences();
+
+ // Shard relationship (cross-resource containment) is not a cross-reference
+ assertThat(xrefs.get(uri("package1.uml")), is(emptySet()));
+
+ // We find cross-references to non-workspace resources, though those aren't indexed
+ assertThat(xrefs.get(uri("root.uml")),
+ is(singleton(URI.createURI(UMLResource.ECORE_PROFILE_URI))));
+
+ // This has a cross-reference in the class generalization
+ assertThat(xrefs.get(uri("referencing.uml")),
+ is(singleton(uri("package2/packageB/bar.uml"))));
+
+ // This API is *not* generalized for the Papyrus one-file
+ assertThat(xrefs.get(uri("referencing.di")), is(emptySet()));
+ }
+
+ @Test
+ public void incomingReferences() throws Exception {
+ SetMultimap<URI, URI> xrefs = index().getIncomingCrossReferences();
+
+ // Parent pointer in shard annotation is not a cross-reference
+ assertThat(xrefs.get(uri("root.uml")), is(emptySet()));
+ assertThat(xrefs.get(uri("package1.uml")), is(emptySet()));
+
+ // This has a cross-reference in the class generalization
+ assertThat(xrefs.get(uri("package2/packageB/bar.uml")),
+ is(singleton(uri("referencing.uml"))));
+
+ // This API is *not* generalized for the Papyrus one-file
+ assertThat(xrefs.get(uri("package2/packageB/bar.di")), is(emptySet()));
+ }
+}
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/ShardResourceHelperTest.java b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/ShardResourceHelperTest.java
new file mode 100644
index 00000000000..d2a8ef84841
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/ShardResourceHelperTest.java
@@ -0,0 +1,241 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.resource;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.ecore.EAnnotation;
+import org.eclipse.emf.ecore.EModelElement;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.InternalEObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.edit.command.AddCommand;
+import org.eclipse.emf.edit.domain.EditingDomain;
+import org.eclipse.papyrus.infra.emf.internal.resource.AbstractCrossReferenceIndex;
+import org.eclipse.papyrus.junit.utils.rules.AnnotationRule;
+import org.eclipse.uml2.uml.util.UMLUtil;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests for the {@link ShardResourceHelper} class.
+ */
+@RunWith(Parameterized.class)
+public class ShardResourceHelperTest extends AbstractCrossReferenceIndexTest {
+
+ @Rule
+ public final AnnotationRule<String> shardElementQualifiedName = AnnotationRule.create(ShardElement.class);
+
+ private final boolean useCommands;
+ private EditingDomain domain; // In case we are using commands
+
+ private ShardResourceHelper helper;
+ private EObject shardElement;
+ private Resource shardResource;
+
+ /**
+ * Initializes me.
+ *
+ * @param useCommands
+ * whether to use the shard helper's commands for resource manipulation
+ * (ensures test coverage)
+ */
+ public ShardResourceHelperTest(boolean useCommands) {
+ super();
+
+ this.useCommands = useCommands;
+ }
+
+ @Test
+ @ProjectResource("referencing.uml")
+ @ShardElement("referencing::package2::packageB")
+ public void makeShard() {
+ setShard(true);
+ assertAdapter();
+ assertAnnotation();
+
+ undo();
+ assertAdapter();
+ assertNoAnnotation();
+
+ redo();
+ assertAdapter();
+ assertAnnotation();
+ }
+
+ @Test
+ @ProjectResource("root.uml")
+ @ShardElement("root::package2::packageB")
+ public void makeNotShard() {
+ setShard(false);
+ assertAdapter();
+ assertNoAnnotation();
+
+ undo();
+ assertAdapter();
+ assertAnnotation();
+
+ redo();
+ assertAdapter();
+ assertNoAnnotation();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @ProjectResource("referencing.uml")
+ @ShardElement("referencing::package2::packageB")
+ public void attemptToUseClosedHelper() {
+ helper.close();
+
+ setShard(true);
+ }
+
+ //
+ // Test framework
+ //
+
+ @Parameters(name = "{index}: useCommands={0}")
+ public static Iterable<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ { false },
+ { true },
+ });
+ }
+
+ @Before
+ public void createShardHelper() {
+ if (useCommands) {
+ // Need an editing domain for command execution
+ domain = housekeeper.createSimpleEditingDomain(fixture);
+ }
+
+ // Find the shard element
+ shardElement = initialResources.stream()
+ .flatMap(res -> UMLUtil.findNamedElements(res, shardElementQualifiedName.get()).stream())
+ .findAny()
+ .orElseThrow(AssertionError::new);
+
+ // Create or find the shard resource
+ if (((InternalEObject) shardElement).eDirectResource() != null) {
+ shardResource = shardElement.eResource();
+ } else {
+ shardResource = fixture.createResource(project.getURI("the-shard.uml"));
+ if (useCommands) {
+ domain.getCommandStack().execute(new AddCommand(domain, shardResource.getContents(), shardElement));
+ } else {
+ shardResource.getContents().add(shardElement);
+ }
+ }
+ helper = new ShardResourceHelper(shardElement);
+ }
+
+ @After
+ public void destroyShardHelper() {
+ helper.close();
+ }
+
+ void setShard(boolean shard) {
+ if (useCommands) {
+ Command command = helper.getSetShardCommand(shard);
+ domain.getCommandStack().execute(command);
+ } else {
+ helper.setShard(shard);
+ }
+
+ assertThat("Changing shard status failed", helper.isShard(), is(shard));
+ }
+
+ void undo() {
+ if (useCommands) {
+ assertThat("Cannot undo", domain.getCommandStack().canUndo(), is(true));
+ domain.getCommandStack().undo();
+ } else {
+ helper.setShard(!helper.isShard());
+ }
+ }
+
+ void redo() {
+ if (useCommands) {
+ assertThat("Cannot redo", domain.getCommandStack().canRedo(), is(true));
+ domain.getCommandStack().redo();
+ } else {
+ helper.setShard(!helper.isShard());
+ }
+ }
+
+ /** Assert that our model element has the shard adapter attached. */
+ void assertAdapter() {
+ assertThat("Shard adapter not found", findAdapter().isPresent(), is(true));
+ }
+
+ /** Assert that our model element does not have the shard adapter attached. */
+ void assertNoAdapter() {
+ assertThat("Shard adapter found", findAdapter().isPresent(), is(false));
+ }
+
+ private Optional<Adapter> findAdapter() {
+ return shardElement.eAdapters().stream()
+ .filter(a -> a.getClass().getName().contains("ShardResourceHelper$"))
+ .findAny();
+ }
+
+ /** Assert that our model element has the shard annotation. */
+ void assertAnnotation() {
+ Optional<EAnnotation> annotation = findAnnotation();
+ assertThat("Shard annotation not found", annotation.isPresent(), is(true));
+ assertThat("Shard annotation missing back pointer",
+ annotation.map(EAnnotation::getReferences).get(),
+ hasItem(shardElement.eContainer()));
+ }
+
+ /** Assert that our model element does not have the shard annotation. */
+ void assertNoAnnotation() {
+ assertThat("Shard annotation found", findAnnotation().isPresent(), is(false));
+ }
+
+ private Optional<EAnnotation> findAnnotation() {
+ return Optional.of(shardElement)
+ .filter(EModelElement.class::isInstance).map(EModelElement.class::cast)
+ .map(EModelElement::getEAnnotations).map(Collection::stream)
+ .map(s -> s.filter(a -> AbstractCrossReferenceIndex.SHARD_ANNOTATION_SOURCE.equals(a.getSource())))
+ .flatMap(s -> s.findAny());
+ }
+
+ //
+ // Nested types
+ //
+
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ShardElement {
+ /** The qualified name of the element to make or unmake as a shard. */
+ String value();
+ }
+}
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/ShardResourceLocatorTest.java b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/ShardResourceLocatorTest.java
new file mode 100644
index 00000000000..6c602b90670
--- /dev/null
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/ShardResourceLocatorTest.java
@@ -0,0 +1,93 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus and others.
+ *
+ * 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.emf.resource;
+
+import java.util.Arrays;
+
+import org.eclipse.uml2.uml.Class;
+import org.eclipse.uml2.uml.Package;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests for the {@link ShardResourceLocator} class. These tests are run both
+ * with the full cross-reference index and with the partial on-demand index that
+ * only looks for shard parent relationships.
+ */
+@RunWith(Parameterized.class)
+public class ShardResourceLocatorTest extends AbstractCrossReferenceIndexTest {
+
+ /**
+ * Initializes me.
+ *
+ * @param onDemand
+ * whether to force the on-demand cross-reference index
+ * @param description
+ * the description of the test conditions
+ */
+ public ShardResourceLocatorTest(boolean onDemand, String description) {
+ super(onDemand);
+ }
+
+ @Test
+ @ProjectResource("package1/packageA/foo.uml")
+ public void shardLoadsContainersAndDoesntLoseStereotypes() {
+ Class foo = (Class) initialResources.get(0).getContents().get(0);
+
+ // The parent chain of Foo is loaded
+ requireLoaded("root.uml");
+ requireLoaded("package1.uml");
+ requireLoaded("package1/packageA.uml");
+
+ // Stereotype applications were not lost
+ requireEClass(foo);
+ requireEPackage(foo.getNearestPackage());
+ requireEPackage(foo.getNearestPackage().getNestingPackage());
+ }
+
+ @Test
+ @ProjectResource("referencing.uml")
+ public void proxyResolutionLoadsShardFromTheTop() {
+ Package referencing = (Package) initialResources.get(0).getContents().get(0);
+ Class subclass = (Class) referencing.getNestedPackage("package1")
+ .getNestedPackage("packageA").getOwnedType("Subclass");
+
+ // Get its general
+ Class superclass = (Class) subclass.getGeneral("Bar");
+
+ // The parent chain of Bar is loaded
+ requireLoaded("root.uml");
+ requireLoaded("package2.uml");
+ requireLoaded("package2/packageB.uml");
+
+ // Stereotype applications were not lost
+ requireEClass(superclass);
+ requireEPackage(superclass.getNearestPackage());
+ requireEPackage(superclass.getNearestPackage().getNestingPackage());
+ }
+
+ //
+ // Test framework
+ //
+
+ @Parameters(name = "{index}: {1}")
+ public static Iterable<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ { false, "full index" },
+ { true, "on-demand index" },
+ });
+ }
+}
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/index/WorkspaceModelIndexTest.java b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/index/WorkspaceModelIndexTest.java
index f94288ef453..1c31edb53d1 100644
--- a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/index/WorkspaceModelIndexTest.java
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/resource/index/WorkspaceModelIndexTest.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2014, 2015 Christian W. Damus and others.
+ * Copyright (c) 2014, 2016 Christian W. Damus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -20,6 +20,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileNotFoundException;
@@ -31,7 +32,12 @@ import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Supplier;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
@@ -41,8 +47,11 @@ import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
@@ -51,6 +60,8 @@ import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.papyrus.infra.emf.Activator;
+import org.eclipse.papyrus.infra.emf.internal.resource.index.IndexManager;
+import org.eclipse.papyrus.infra.emf.internal.resource.index.InternalModelIndex;
import org.eclipse.papyrus.infra.emf.utils.EMFHelper;
import org.eclipse.papyrus.junit.framework.classification.tests.AbstractPapyrusTest;
import org.eclipse.papyrus.junit.utils.LogTracker;
@@ -66,16 +77,17 @@ import org.junit.Test;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
-import com.google.common.util.concurrent.Uninterruptibles;
/**
* Test suite for the {@link WorkspaceModelIndex} class.
*/
public class WorkspaceModelIndexTest extends AbstractPapyrusTest {
- private static final CrossReferenceIndexer index = new CrossReferenceIndexer();
+ private static final SyncHolder syncHolder = new SyncHolder();
+ private static final CrossReferenceIndexer index = new CrossReferenceIndexer(syncHolder);
+
+ private static IndexManager manager;
private static WorkspaceModelIndex<CrossReferenceIndex> fixture;
- private static boolean delayIndexing;
@Rule
public final HouseKeeper houseKeeper = new HouseKeeper();
@@ -219,8 +231,8 @@ public class WorkspaceModelIndexTest extends AbstractPapyrusTest {
// Initial build
Map<IFile, CrossReferenceIndex> index = fixture.getIndex().get();
- // Ensure that indexing will take a bit of time
- delayIndexing = true;
+ // Interlock with the indexing for timing purposes
+ Semaphore sync = syncHolder.createStandardSync();
final String newFileName = "the_referencing_model.uml";
@@ -231,8 +243,11 @@ public class WorkspaceModelIndexTest extends AbstractPapyrusTest {
referencingFile = referencingProject.getFile(new Path(newFileName));
referencingURI = uri(referencingFile);
+ // Let the indexing start
+ sync.release();
+
// Cancel the index control job
- Job[] family = Job.getJobManager().find(fixture);
+ Job[] family = Job.getJobManager().find(manager);
Job controlJob = null;
for (Job job : family) {
if (job.getClass().getSimpleName().contains("JobWrangler")) {
@@ -241,16 +256,25 @@ public class WorkspaceModelIndexTest extends AbstractPapyrusTest {
}
}
assertThat("Control job not found", controlJob, notNullValue());
+
+ long cancellingAt = System.currentTimeMillis();
+
controlJob.cancel();
- long requestIndex = System.currentTimeMillis();
+ // Let the indexing finish
+ sync.release();
- // Check the index
- index = fixture.getIndex().get();
+ JobWaiter controlJobWaiter = JobWaiter.waitForStart(controlJob);
- long gotIndex = System.currentTimeMillis();
+ controlJobWaiter.waitForJob();
+
+ long restartedAt = System.currentTimeMillis();
- assertThat("Didn't have to wait for the index to recover", (gotIndex - requestIndex), greaterThan(1000L));
+ assertThat("Didn't have to wait for the index to recover",
+ (restartedAt - cancellingAt), greaterThan(1000L));
+
+ // Check the index
+ index = fixture.getIndex().get();
assertIndex(index);
}
@@ -311,12 +335,21 @@ public class WorkspaceModelIndexTest extends AbstractPapyrusTest {
@BeforeClass
public static void createFixture() {
- fixture = new WorkspaceModelIndex<CrossReferenceIndex>("test", UMLResource.UML_CONTENT_TYPE_IDENTIFIER, index, 2);
+ manager = new IndexManager() {
+ @Override
+ protected Map<QualifiedName, InternalModelIndex> loadIndices() {
+ fixture = new WorkspaceModelIndex<>("test", UMLResource.UML_CONTENT_TYPE_IDENTIFIER, index, 2);
+ return Collections.singletonMap(fixture.getIndexKey(), (InternalModelIndex) fixture);
+ }
+ };
+ manager.startManager();
}
@AfterClass
public static void destroyFixture() {
- fixture.dispose();
+ // This disposes the fixture, too
+ manager.dispose();
+ manager = null;
fixture = null;
}
@@ -346,7 +379,7 @@ public class WorkspaceModelIndexTest extends AbstractPapyrusTest {
@After
public void reset() {
- delayIndexing = false;
+ syncHolder.clear();
}
static URI uri(IFile file) {
@@ -393,9 +426,45 @@ public class WorkspaceModelIndexTest extends AbstractPapyrusTest {
// Nested types
//
+ static class SyncHolder implements Supplier<Semaphore> {
+ private volatile Semaphore sync;
+
+ @Override
+ public Semaphore get() {
+ return sync;
+ }
+
+ Semaphore createStandardSync() {
+ sync = new Semaphore(0);
+ return sync;
+ }
+
+ void clear() {
+ if (sync != null) {
+ // Make sure that any blocked threads are released to whatever fate
+ syncHolder.sync.release(100);
+ syncHolder.sync = null;
+ }
+ }
+ }
+
static class CrossReferenceIndexer implements WorkspaceModelIndex.IndexHandler<CrossReferenceIndex> {
private final Map<IFile, CrossReferenceIndex> index = Maps.newHashMap();
+ private final Supplier<? extends Semaphore> syncSupplier;
+
+ /**
+ * Initializes me.
+ *
+ * @param syncSupplier
+ * a supplier of an optional semaphore to acquire at start and end of indexing a file
+ */
+ public CrossReferenceIndexer(Supplier<? extends Semaphore> syncSupplier) {
+ super();
+
+ this.syncSupplier = syncSupplier;
+ }
+
private CrossReferenceIndex get(IFile file) {
CrossReferenceIndex result;
@@ -413,38 +482,48 @@ public class WorkspaceModelIndexTest extends AbstractPapyrusTest {
@Override
public CrossReferenceIndex index(IFile file) {
final CrossReferenceIndex result = get(file);
+ final Semaphore sync = syncSupplier.get();
- Set<URI> imports = result.imports;
-
- ResourceSet resourceSet = new IndexingResourceSet();
+ if (sync != null) {
+ // Wait for the test to let us proceed
+ sync.acquireUninterruptibly();
+ }
try {
- URI uri = uri(file);
-
- Resource resource = resourceSet.getResource(uri, true);
- for (Map.Entry<EObject, Collection<EStructuralFeature.Setting>> next : EcoreUtil.ProxyCrossReferencer.find(resource).entrySet()) {
- for (EStructuralFeature.Setting setting : next.getValue()) {
- Object references = setting.get(false);
-
- if (references instanceof EObject) {
- EObject ref = (EObject) references;
- if (ref.eIsProxy()) {
- URI href = EcoreUtil.getURI(ref).trimFragment();
- if (href.isPlatformResource() && imports.add(href)) {
- // add the corresponding export
- IFile other = file.getWorkspace().getRoot().getFile(new Path(href.toPlatformString(true)));
- get(other).exports.add(uri);
+ Set<URI> imports = result.imports;
+ imports.clear();
+
+ ResourceSet resourceSet = new IndexingResourceSet();
+
+ try {
+ URI uri = uri(file);
+
+ Resource resource = resourceSet.getResource(uri, true);
+ for (Map.Entry<EObject, Collection<EStructuralFeature.Setting>> next : EcoreUtil.ProxyCrossReferencer.find(resource).entrySet()) {
+ for (EStructuralFeature.Setting setting : next.getValue()) {
+ Object references = setting.get(false);
+
+ if (references instanceof EObject) {
+ EObject ref = (EObject) references;
+ if (ref.eIsProxy()) {
+ URI href = EcoreUtil.getURI(ref).trimFragment();
+ if (href.isPlatformResource() && imports.add(href)) {
+ // add the corresponding export
+ IFile other = file.getWorkspace().getRoot().getFile(new Path(href.toPlatformString(true)));
+ get(other).exports.add(uri);
+ }
}
}
}
}
+ } finally {
+ EMFHelper.unload(resourceSet);
}
} finally {
- EMFHelper.unload(resourceSet);
- }
-
- if (delayIndexing) {
- Uninterruptibles.sleepUninterruptibly(1L, TimeUnit.SECONDS);
+ if (sync != null) {
+ // Wait for the test to let us finish
+ sync.acquireUninterruptibly();
+ }
}
return result;
@@ -552,4 +631,74 @@ public class WorkspaceModelIndexTest extends AbstractPapyrusTest {
file.delete(true, monitor);
}
}
+
+ static final class JobWaiter extends JobChangeAdapter {
+ private final Job job;
+ private final boolean waitForStart;
+
+ private final Lock lock = new ReentrantLock();
+ private final Condition cond = lock.newCondition();
+ private volatile boolean gotIt;
+
+ private JobWaiter(Job job, boolean waitForStart) {
+ super();
+
+ this.job = job;
+ this.waitForStart = waitForStart;
+
+ Job.getJobManager().addJobChangeListener(this);
+ }
+
+ public static JobWaiter waitForStart(Job job) {
+ return new JobWaiter(job, true);
+ }
+
+ public static JobWaiter waitForEnd(Job job) {
+ return new JobWaiter(job, false);
+ }
+
+ public void waitForJob() {
+ lock.lock();
+
+ try {
+ while (!gotIt) {
+ try {
+ cond.await();
+ } catch (InterruptedException e) {
+ fail("Test was interrupted");
+ }
+ }
+ } finally {
+ lock.unlock();
+ Job.getJobManager().removeJobChangeListener(this);
+ }
+ }
+
+ @Override
+ public void aboutToRun(IJobChangeEvent event) {
+ if (waitForStart && (event.getJob() == job)) {
+ lock.lock();
+ try {
+ gotIt = true;
+ cond.signalAll();
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ @Override
+ public void done(IJobChangeEvent event) {
+ if (!waitForStart && (event.getJob() == job)) {
+ lock.lock();
+ try {
+ gotIt = true;
+ cond.signalAll();
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ }
}
diff --git a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/tests/AllTests.java b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/tests/AllTests.java
index 158a001365e..46aa80c3609 100644
--- a/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/tests/AllTests.java
+++ b/tests/junit/plugins/infra/emf/org.eclipse.papyrus.infra.emf.tests/tests/org/eclipse/papyrus/infra/emf/tests/AllTests.java
@@ -8,13 +8,16 @@
*
* Contributors:
* Christian W. Damus (CEA) - Initial API and implementation
- * Christian W. Damus - bugs 399859, 465416, 485220
+ * Christian W. Damus - bugs 399859, 465416, 485220, 496299
*
*/
package org.eclipse.papyrus.infra.emf.tests;
import org.eclipse.papyrus.infra.emf.advice.ReadOnlyObjectEditAdviceTest;
import org.eclipse.papyrus.infra.emf.edit.domain.PapyrusTransactionalEditingDomainTest;
+import org.eclipse.papyrus.infra.emf.resource.CrossReferenceIndexTest;
+import org.eclipse.papyrus.infra.emf.resource.ShardResourceHelperTest;
+import org.eclipse.papyrus.infra.emf.resource.ShardResourceLocatorTest;
import org.eclipse.papyrus.infra.emf.resource.index.WorkspaceModelIndexTest;
import org.eclipse.papyrus.infra.emf.utils.ServiceUtilsForResourceTest;
import org.eclipse.papyrus.infra.types.core.registries.ElementTypeSetConfigurationRegistry;
@@ -37,8 +40,10 @@ import org.junit.runners.Suite.SuiteClasses;
PapyrusTransactionalEditingDomainTest.class,
// oep.infra.emf.utils
ServiceUtilsForResourceTest.class,
+ // oep.infra.emf.resource
+ ShardResourceHelperTest.class, ShardResourceLocatorTest.class, CrossReferenceIndexTest.class,
// oep.infra.emf.resource.index
- WorkspaceModelIndexTest.class
+ WorkspaceModelIndexTest.class,
})
public class AllTests {
diff --git a/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/.classpath b/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/.classpath
index 8a8f1668cdc..eca7bdba8f0 100644
--- a/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/.classpath
+++ b/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/.classpath
@@ -1,7 +1,7 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
- <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/.settings/org.eclipse.jdt.core.prefs b/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/.settings/org.eclipse.jdt.core.prefs
index 410244d65a6..62a08f4494d 100644
--- a/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/.settings/org.eclipse.jdt.core.prefs
+++ b/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/.settings/org.eclipse.jdt.core.prefs
@@ -1,10 +1,10 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.compiler.source=1.8
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
diff --git a/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/META-INF/MANIFEST.MF b/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/META-INF/MANIFEST.MF
index be5e009ef29..bef0c0a73be 100644
--- a/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/META-INF/MANIFEST.MF
+++ b/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/META-INF/MANIFEST.MF
@@ -3,14 +3,14 @@ Require-Bundle: org.eclipse.ui;bundle-version="3.106.0",
org.junit;bundle-version="4.11.0",
org.eclipse.papyrus.junit.framework;bundle-version="1.2.0",
org.eclipse.papyrus.infra.core.log;bundle-version="1.2.0",
- org.eclipse.papyrus.infra.services.controlmode;bundle-version="1.2.0",
+ org.eclipse.papyrus.infra.services.controlmode;bundle-version="1.3.0",
org.eclipse.papyrus.infra.core;bundle-version="1.2.0",
org.eclipse.papyrus.views.modelexplorer;bundle-version="1.2.0",
org.eclipse.uml2.uml;bundle-version="5.0.0",
org.eclipse.emf.transaction,
org.eclipse.papyrus.infra.services.resourceloading;bundle-version="1.2.0",
org.eclipse.papyrus.junit.utils;bundle-version="1.2.0",
- org.eclipse.papyrus.infra.emf;bundle-version="1.2.0",
+ org.eclipse.papyrus.infra.emf;bundle-version="1.3.0",
org.eclipse.papyrus.infra.widgets;bundle-version="1.2.0",
org.eclipse.ui.navigator;bundle-version="3.5.500",
com.google.guava;bundle-version="11.0.0",
@@ -21,9 +21,9 @@ Export-Package: org.eclipse.papyrus.infra.services.controlmode.tests,
org.eclipse.papyrus.infra.services.controlmode.tests.uncontrol
Bundle-Vendor: %Bundle-Vendor
Bundle-ActivationPolicy: lazy
-Bundle-Version: 1.2.0.qualifier
+Bundle-Version: 1.4.0.qualifier
Bundle-Name: %Bundle-Name
Bundle-ManifestVersion: 2
Bundle-Activator: org.eclipse.papyrus.infra.services.controlmode.tests.control.Activator
Bundle-SymbolicName: org.eclipse.papyrus.infra.services.controlmode.tests
-Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
diff --git a/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/pom.xml b/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/pom.xml
index 5341ea1e2be..ac0785e925e 100644
--- a/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/pom.xml
+++ b/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/pom.xml
@@ -10,6 +10,6 @@
</parent>
<groupId>org.eclipse.papyrus</groupId>
<artifactId>org.eclipse.papyrus.infra.services.controlmode.tests</artifactId>
- <version>1.2.0-SNAPSHOT</version>
+ <version>1.4.0-SNAPSHOT</version>
<packaging>eclipse-test-plugin</packaging>
</project>
diff --git a/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/src/org/eclipse/papyrus/infra/services/controlmode/tests/uncontrol/UncontrolModelTest.java b/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/src/org/eclipse/papyrus/infra/services/controlmode/tests/uncontrol/UncontrolModelTest.java
index 47316c7bdc3..dac0bb79c44 100644
--- a/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/src/org/eclipse/papyrus/infra/services/controlmode/tests/uncontrol/UncontrolModelTest.java
+++ b/tests/junit/plugins/infra/services/org.eclipse.papyrus.infra.services.controlmode.tests/src/org/eclipse/papyrus/infra/services/controlmode/tests/uncontrol/UncontrolModelTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2014, 2015 CEA LIST, Christian W. Damus, and others.
+ * Copyright (c) 2014, 2016 CEA LIST, Christian W. Damus, and others.
* 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
@@ -8,27 +8,34 @@
* Contributors:
* Juan Cadavid (CEA) juan.cadavid@cea.fr - Implementation initial and API
* Gabriel Pascual (ALL4TEC) gabriel.pascual@all4tec.net - Bug 459427
- * Christian W. Damus - bug 480209
+ * Christian W. Damus - bugs 480209, 496299
******************************************************************************/
package org.eclipse.papyrus.infra.services.controlmode.tests.uncontrol;
+import static org.hamcrest.CoreMatchers.anything;
import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeThat;
import java.util.Collections;
import org.eclipse.core.resources.IFile;
+import org.eclipse.emf.ecore.EAnnotation;
+import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.papyrus.infra.core.services.ServiceException;
+import org.eclipse.papyrus.infra.emf.resource.ShardResourceHelper;
import org.eclipse.papyrus.infra.services.controlmode.tests.Messages;
import org.eclipse.papyrus.junit.utils.rules.PluginResource;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.PackageableElement;
+import org.hamcrest.CoreMatchers;
import org.junit.Test;
/**
@@ -392,4 +399,38 @@ public class UncontrolModelTest extends AbstractUncontrolModelTest {
};
uncontrolAssertion.assertUncontrol();
}
+
+ /**
+ * Test uncontrol of a "shard" resource sub-unit.
+ */
+ @Test
+ public void testUncontrolShardSubunit() throws ServiceException {
+ ShardResourceHelper[] helper = { null };
+ EModelElement[] element = { null };
+
+ UncontrolModeAssertion uncontrolAssertion = new UncontrolModeAssertion(Messages.UncontrolModelTest_4) {
+ @Override
+ protected void assertBeforeUncontrol() {
+ super.assertBeforeUncontrol();
+
+ element[0] = getElementToUnControl();
+ helper[0] = new ShardResourceHelper(element[0]);
+ houseKeeper.cleanUpLater(helper[0], ShardResourceHelper::close);
+
+ editorFixture.execute(helper[0].getSetShardCommand(true));
+ assumeThat("Element does not appear to be a shard", helper[0].isShard(), is(true));
+ }
+ };
+ uncontrolAssertion.assertUncontrol();
+
+ assertThat("Element still appears to be a shard", helper[0].isShard(), is(false));
+ assertThat("Element still has an annotation", element[0].getEAnnotations(),
+ not(CoreMatchers.<EAnnotation> hasItem(anything())));
+
+ undo();
+ assertNotSame(model.eResource(), selectedElements.get(0).eResource());
+ assertThat("Element does not appear to be a shard", helper[0].isShard(), is(true));
+ assertThat("Element does not have any annotation", element[0].getEAnnotations(),
+ CoreMatchers.<EAnnotation> hasItem(anything()));
+ }
}
diff --git a/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/META-INF/MANIFEST.MF b/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/META-INF/MANIFEST.MF
index 412c7b4ee21..0f6333765ef 100644
--- a/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/META-INF/MANIFEST.MF
+++ b/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/META-INF/MANIFEST.MF
@@ -16,7 +16,7 @@ Export-Package: org.eclipse.papyrus.junit.matchers,
org.eclipse.papyrus.junit.utils.tests
Bundle-Vendor: %Bundle-Vendor
Bundle-ActivationPolicy: lazy
-Bundle-Version: 2.0.0.qualifier
+Bundle-Version: 2.0.100.qualifier
Bundle-Name: %Bundle-Name
Bundle-ManifestVersion: 2
Bundle-Activator: org.eclipse.papyrus.junit.utils.Activator
diff --git a/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/pom.xml b/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/pom.xml
index d356497fa2e..d9cee29ea32 100644
--- a/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/pom.xml
+++ b/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/pom.xml
@@ -8,6 +8,6 @@
</parent>
<groupId>org.eclipse.papyrus</groupId>
<artifactId>org.eclipse.papyrus.junit.utils</artifactId>
- <version>2.0.0-SNAPSHOT</version>
+ <version>2.0.100-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project> \ No newline at end of file
diff --git a/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/rules/AbstractModelFixture.java b/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/rules/AbstractModelFixture.java
index c4bbc371846..f2d977d579c 100644
--- a/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/rules/AbstractModelFixture.java
+++ b/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/rules/AbstractModelFixture.java
@@ -8,7 +8,7 @@
*
* Contributors:
* Christian W. Damus (CEA) - Initial API and implementation
- * Christian W. Damus - bugs 399859, 451230, 458685, 469188, 485220
+ * Christian W. Damus - bugs 399859, 451230, 458685, 469188, 485220, 496299
*
*/
package org.eclipse.papyrus.junit.utils.rules;
@@ -55,10 +55,12 @@ import org.eclipse.emf.ecore.xml.type.AnyType;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.workspace.IWorkspaceCommandStack;
+import org.eclipse.papyrus.infra.core.resource.ModelMultiException;
import org.eclipse.papyrus.infra.core.resource.ModelSet;
import org.eclipse.papyrus.infra.core.resource.sasheditor.DiModel;
import org.eclipse.papyrus.infra.emf.utils.EMFHelper;
import org.eclipse.papyrus.infra.gmfdiag.common.model.NotationModel;
+import org.eclipse.papyrus.infra.tools.util.TypeUtils;
import org.eclipse.papyrus.junit.utils.JUnitUtils;
import org.eclipse.papyrus.uml.tools.model.UmlModel;
import org.eclipse.uml2.uml.Package;
@@ -262,6 +264,18 @@ public abstract class AbstractModelFixture<T extends EditingDomain> extends Test
uris.add(next.getURI());
}
initialResourceURIs = uris;
+
+ // Ensure that the ModelSet's IModels are started
+ ModelSet modelSet = TypeUtils.as(getResourceSet(), ModelSet.class);
+ if (modelSet != null) {
+ // It doesn't matter that the resource is already loaded
+ try {
+ modelSet.loadModels(result.get(0).getURI());
+ } catch (ModelMultiException e) {
+ // Continue with the test as well as we can
+ e.printStackTrace();
+ }
+ }
} else {
ResourceSet rset = getResourceSet();
boolean bootstrapResourceSet = rset == null;
@@ -345,8 +359,8 @@ public abstract class AbstractModelFixture<T extends EditingDomain> extends Test
}
// Look for any other dependencies (libraries, profiles, etc.) that also need to be copied
- Queue<Resource> dependents = new LinkedList<Resource>();
- Set<Resource> scanned = new HashSet<Resource>();
+ Queue<Resource> dependents = new LinkedList<>();
+ Set<Resource> scanned = new HashSet<>();
dependents.add(result);
boolean loadedProfiles = false;
for (Resource dependent = dependents.poll(); dependent != null; dependent = dependents.poll()) {

Back to the top