Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian W. Damus2016-07-13 15:05:54 -0400
committerChristian W. Damus2016-07-13 15:51:04 -0400
commitddfb7b0caefdd1be212db31bde24b8a9feb225de (patch)
tree5c350c81ea1a9fb2985bb62195c06ff50a7a5174
parentf68c766e5c5df1bb5c08fd65bc6f5464d3a58208 (diff)
downloadorg.eclipse.papyrus-ddfb7b0caefdd1be212db31bde24b8a9feb225de.tar.gz
org.eclipse.papyrus-ddfb7b0caefdd1be212db31bde24b8a9feb225de.tar.xz
org.eclipse.papyrus-ddfb7b0caefdd1be212db31bde24b8a9feb225de.zip
Bug 496299: Controlled Units as Integral Fragments
https://bugs.eclipse.org/bugs/show_bug.cgi?id=496299 Implement a new mode of controlled unit in Papyrus dubbed "shards". A shard is like any other sub-unit created up to and including the Neon release, except that it cannot be opened independently in the editor. The Papyrus editor, when asked to open a "shard", will instead open the root resource of the model. Likewise, the editor matcher normalizes editor inputs to the root resource of any shard. The graph of shard dependencies is inferred from a new workspace- wide index of cross-resource containment references, when it is available. Otherwise, the linkage of shards to their parent references is parsed on-the-fly from the shard annotation's reference (with a relatively efficient XML parsing that terminates after reading only a few lines of the XMI text). A new ResourceLocator is implemented to provide a pluggable hook for resource loading (including proxy resolution), to ensure when loading a shard resource that its parent resource chain is first loaded from the top down to ensure that all context of profile applications is available before loading the shard, itself, which may have stereotype applications that depend on those profile applications. The CoreMultiDiagramEditor installs this resource locator on the ModelSet; other applications (including in a non-Eclipse context) can make similar use of it. Some additional fixes are required in other core components to make the loading of referenced sharded models as in bug 458837 work: * the SemanticUMLContentProvider did not detect the final resolution of containment proxies that changes what looks look a model root object into just another intermediate element in the content tree. Besides that it would schedule a large number of redundant UI refreshes asynchronously (deferred) on the UI thread * the DiModel and NotationModel would load their adjuncts to the *.uml resource when that resource is created, not after it has been loaded. This is much too early and ends up causing the transactional editing domain to detect the attachment of a resource's contents at the end of loading as an attempt to edit the model during a read-only transaction, which logs an exception and bombs the UI action. Instead, these models now have snippets that load the *.di and *.notation resources after the semantic resource has been loaded. * the new model snippets required an additional fix in the loading of IModels to handle contributions of snippets and dependencies to models that are overridden by other IModels registered under the same ID, such as is the case with the NotationModel and the CSSNotationModel, which latter needs the snippet declared by the former * the IModels additionally need to ensure that they start snippets on loading of an existing model even when it is already found to be loaded in the ModelSet (as happens often in JUnit tests) * the AbstractModelFixture in the JUnit test framework is updated to ensure that the ModelSet is properly initialized, with its own snippets started and its IModels loaded and their snippets started * the basic uncontrol command now removes the shard annotation from the uncontrolled element/resource, if there was one. Because this bundle now supports a new feature (that being shards), it seems appropriate to bump its minor version number General-purpose changes in the core workspace model index framework that improve overall performance, of particular significance in large and highly fragmented models: Implement persistent storage of the workspace model index at workspace save to support quick start-up without parsing the entire workspace. Consolidation of indices: * run a single pool of indexing jobs and a single resource change listener to trigger (re)-indexing of files * all indices matching any given file process it * includes a new extension point from which all indices are loaded into the shared index manager to initialize them and do the work (cherry-picked from streams/2.0-maintenance) Change-Id: Ifd65a71c57134b69d873f17139f3cedbf11c5ba5
-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