diff options
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 <tt>WorkspaceModelIndex</tt> 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()) { |