diff options
Diffstat (limited to 'plugins')
33 files changed, 2447 insertions, 301 deletions
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 850c6ec0719..44505d47c1a 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 @@ -8,6 +8,7 @@ Export-Package: org.eclipse.papyrus.infra.core, org.eclipse.papyrus.infra.core.extension, org.eclipse.papyrus.infra.core.extension.commands, org.eclipse.papyrus.infra.core.extension.diagrameditor, + org.eclipse.papyrus.infra.core.language, org.eclipse.papyrus.infra.core.lifecycleevents, org.eclipse.papyrus.infra.core.listenerservice, org.eclipse.papyrus.infra.core.markers, 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 1ec4a12a9ec..7a1ae21bd95 100644 --- a/plugins/infra/core/org.eclipse.papyrus.infra.core/plugin.xml +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/plugin.xml @@ -8,6 +8,7 @@ <extension-point id="model" name="plugin.xml.ModelName" schema="schema/model.exsd"/>
<extension-point id="transactionalEditingDomainProvider" name="transactionalEditingDomainProvider" schema="schema/transactionalEditingDomainProvider.exsd"/>
<extension-point id="sashModelProvider" name="Sash Model Providers" schema="schema/sashModelProvider.exsd"/>
+ <extension-point id="language" name="Modeling Language" schema="schema/language.exsd"/>
<extension
point="org.eclipse.ui.menus">
@@ -394,5 +395,14 @@ type="org.eclipse.papyrus.infra.core.services.IService">
</adapter>
</factory>
+ </extension>
+ <extension
+ point="org.eclipse.papyrus.infra.core.service">
+ <service
+ classname="org.eclipse.papyrus.infra.core.internal.language.LanguageService"
+ id="org.eclipse.papyrus.infra.core.language.ILanguageService"
+ priority="10"
+ startKind="startup">
+ </service>
</extension> </plugin>
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/schema/language.exsd b/plugins/infra/core/org.eclipse.papyrus.infra.core/schema/language.exsd new file mode 100644 index 00000000000..516be1d8310 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/schema/language.exsd @@ -0,0 +1,219 @@ +<?xml version='1.0' encoding='UTF-8'?> +<!-- Schema file written by PDE --> +<schema targetNamespace="org.eclipse.papyrus.infra.core" xmlns="http://www.w3.org/2001/XMLSchema"> +<annotation> + <appInfo> + <meta.schema plugin="org.eclipse.papyrus.infra.core" id="language" name="Modeling Language"/> + </appInfo> + <documentation> + Registration of Modeling Languages in the Papyrus Language Service. + </documentation> + </annotation> + + <element name="extension"> + <annotation> + <appInfo> + <meta.element /> + </appInfo> + </annotation> + <complexType> + <sequence> + <element ref="provider" 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="provider"> + <annotation> + <documentation> + Registration of a Papryus modeling language provider. + </documentation> + </annotation> + <complexType> + <sequence> + <element ref="class" minOccurs="0" maxOccurs="1"/> + <choice minOccurs="0" maxOccurs="unbounded"> + <element ref="content-type"/> + <element ref="language"/> + </choice> + </sequence> + <attribute name="class" type="string"> + <annotation> + <documentation> + The name of a language provider class. If not specified, then nested elements are required to specify a generic provider that matches URIs to languages. +The class may also be specified by the nested <tt>&lt;class&gt;</tt> element, especially if it is parameterized. + </documentation> + <appInfo> + <meta.attribute kind="java" basedOn=":org.eclipse.papyrus.infra.core.language.ILanguageProvider"/> + </appInfo> + </annotation> + </attribute> + </complexType> + </element> + + <element name="content-type"> + <annotation> + <documentation> + Matches languages by resource content type. +Ignored if the <tt>class</tt> attribute of the <tt>&lt;provider&gt;</tt> element is specified. +Required if the <tt>&lt;language&gt;</tt> element is specified. + </documentation> + </annotation> + <complexType> + <attribute name="id" type="string" use="required"> + <annotation> + <documentation> + References a resource content type for which to provider a language. + </documentation> + <appInfo> + <meta.attribute kind="identifier" basedOn="org.eclipse.core.contenttype.contentTypes/content-type/@id"/> + </appInfo> + </annotation> + </attribute> + </complexType> + </element> + + <element name="language"> + <annotation> + <documentation> + Specifies a language matched by resource content type. +Ignored if the <tt>class</tt> attribute or <tt>&lt;implementation&gt;</tt> element of the <tt>&lt;provider&gt;</tt> element is specified. +Required if the <tt>&lt;contentType&gt;</tt> element is specified. + </documentation> + </annotation> + <complexType> + <attribute name="class" type="string"> + <annotation> + <documentation> + The name of a language implementation class. If not specified, then a default implementation is supplied based on the other attributes that simply does no ModelSet configuration. + </documentation> + <appInfo> + <meta.attribute kind="java" basedOn=":org.eclipse.papyrus.infra.core.language.ILanguage"/> + </appInfo> + </annotation> + </attribute> + <attribute name="id" type="string" use="required"> + <annotation> + <documentation> + An unique identifier for the language. + </documentation> + </annotation> + </attribute> + <attribute name="version" type="string" use="required"> + <annotation> + <documentation> + The version number of the language. + </documentation> + </annotation> + </attribute> + <attribute name="name" type="string" use="required"> + <annotation> + <documentation> + The localized user-presentable name of the language. + </documentation> + <appInfo> + <meta.attribute translatable="true"/> + </appInfo> + </annotation> + </attribute> + </complexType> + </element> + + <element name="class"> + <annotation> + <documentation> + Specification of a parameterized executable extension. If specified, then the <tt>class</tt> attribute is ignored. + </documentation> + </annotation> + <complexType> + <sequence> + <element ref="parameter" minOccurs="0" maxOccurs="unbounded"/> + </sequence> + <attribute name="class" type="string"> + <annotation> + <documentation> + The name of a language provider class. Parameters may be specified to configure the instance as appropriate to its API specification. + </documentation> + <appInfo> + <meta.attribute kind="java" basedOn=":org.eclipse.papyrus.infra.core.language.ILanguageProvider"/> + </appInfo> + </annotation> + </attribute> + </complexType> + </element> + + <element name="parameter"> + <annotation> + <documentation> + A name/value pair to configure an implementation of the language provider. + </documentation> + </annotation> + <complexType> + <attribute name="name" type="string" use="required"> + <annotation> + <documentation> + An unique parameter name, as specified by the API of the implementation class. + </documentation> + </annotation> + </attribute> + <attribute name="value" type="string" use="required"> + <annotation> + <documentation> + + </documentation> + </annotation> + </attribute> + </complexType> + </element> + + <annotation> + <appInfo> + <meta.section type="since"/> + </appInfo> + <documentation> + 1.1.0 + </documentation> + </annotation> + + + + + <annotation> + <appInfo> + <meta.section type="copyright"/> + </appInfo> + <documentation> + Copyright (c) 2015 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/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/editor/ModelSetServiceFactory.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/editor/ModelSetServiceFactory.java index ae76099d3b4..185172c1f33 100644 --- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/editor/ModelSetServiceFactory.java +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/editor/ModelSetServiceFactory.java @@ -1,5 +1,5 @@ /*****************************************************************************
- * Copyright (c) 2011, 2014 CEA LIST and others.
+ * Copyright (c) 2011, 2015 CEA LIST, Christian W. Damus, and others.
*
*
* All rights reserved. This program and the accompanying materials
@@ -10,11 +10,13 @@ * Contributors:
* CEA LIST - Initial API and implementation
* Christian W. Damus (CEA) - bug 431953 (pre-requisite refactoring of ModelSet service start-up)
+ * Christian W. Damus - bug 468030
*
*****************************************************************************/
package org.eclipse.papyrus.infra.core.editor;
import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.papyrus.infra.core.language.ILanguageService;
import org.eclipse.papyrus.infra.core.resource.ModelSet;
import org.eclipse.papyrus.infra.core.services.IServiceFactory;
import org.eclipse.papyrus.infra.core.services.ModelSetServiceAdapter;
@@ -46,15 +48,14 @@ public class ModelSetServiceFactory implements IServiceFactory { this.serviceRegistry = servicesRegistry;
}
- /**
- *
- * @see org.eclipse.papyrus.infra.core.services.IService#startService()
- *
- * @throws ServiceException
- */
@Override
public void startService() throws ServiceException {
- // Pass
+ // If the language service is available (optional dependency), the ModelSet will want to use it, so start it
+ try {
+ serviceRegistry.startServicesByClassKeys(ILanguageService.class);
+ } catch (ServiceException e) {
+ // It's okay: the language service is optional
+ }
}
/**
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/DefaultLanguageProvider.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/DefaultLanguageProvider.java new file mode 100644 index 00000000000..11f745b52d1 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/DefaultLanguageProvider.java @@ -0,0 +1,203 @@ +/***************************************************************************** + * Copyright (c) 2015 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 java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.ContentHandler; +import org.eclipse.emf.ecore.resource.URIConverter; +import org.eclipse.papyrus.infra.core.Activator; +import org.eclipse.papyrus.infra.core.language.ILanguage; +import org.eclipse.papyrus.infra.core.language.ILanguageProvider; +import org.eclipse.papyrus.infra.core.language.ILanguageService; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +/** + * A default language provider for extensions that simply declare content-type matches for languages. + */ +public class DefaultLanguageProvider implements ILanguageProvider { + private final Set<IContentType> contentTypes = Sets.newHashSet(); + + private final List<ILanguage> languages = Lists.newArrayList(); + + private List<LanguageProxy> languageProxies; + + public DefaultLanguageProvider() { + super(); + } + + @Override + public synchronized Iterable<ILanguage> getLanguages(ILanguageService languageService, URI modelURI, boolean uriHasFileExtension) { + Set<URI> allURIs; + + try { + allURIs = resolveURIs(modelURI, uriHasFileExtension); + } catch (CoreException e) { + Activator.log.log(e.getStatus()); + + // Can't match our content types against anything + return Collections.emptyList(); + } + + if (!matchContentType(allURIs)) { + return Collections.emptyList(); + } else { + // If any of my content-types matches, then all of my languages are present + resolveProxies(); + return Collections.unmodifiableList(languages); + } + } + + void addContentType(String id) { + IContentType contentType = Platform.getContentTypeManager().getContentType(id); + if (contentType == null) { + Activator.log.warn("No such content-type in language provider extension: " + id); //$NON-NLS-1$ + } else { + contentTypes.add(contentType); + } + } + + synchronized void addLanguage(ILanguage language) { + this.languages.add(language); + } + + synchronized void addLanguage(IConfigurationElement config, String attribute) { + if (languageProxies == null) { + languageProxies = Lists.newArrayList(); + } + + languageProxies.add(new LanguageProxy(config, attribute)); + } + + private void resolveProxies() { + if (languageProxies != null) { + try { + for (LanguageProxy next : languageProxies) { + ILanguage resolved = next.resolve(); + if (resolved != null) { + addLanguage(resolved); + } + } + } finally { + languageProxies = null; + } + } + } + + private Set<URI> resolveURIs(URI modelURI, boolean uriHasFileExtension) throws CoreException { + // If given an extension, just take that + if (uriHasFileExtension) { + return Collections.singleton(modelURI); + } + + Set<URI> result = Sets.newHashSet(); + + if (modelURI.isPlatformResource()) { + String baseName = modelURI.lastSegment(); + IPath path = new Path(modelURI.trimSegments(1).toPlatformString(true)); + IContainer container = (path.segmentCount() > 1) ? ResourcesPlugin.getWorkspace().getRoot().getFolder(path) : ResourcesPlugin.getWorkspace().getRoot().getProject(path.lastSegment()); + for (IResource next : container.members()) { + if (next.getType() == IResource.FILE) { + IPath nextPath = next.getFullPath(); + String name = nextPath.removeFileExtension().lastSegment(); + if (name.equals(baseName)) { + result.add(URI.createPlatformResourceURI(nextPath.toString(), true)); + } + } + } + } else if (modelURI.isFile()) { + String baseName = modelURI.lastSegment(); + File directory = new File(modelURI.trimSegments(1).toFileString()); + for (File next : directory.listFiles()) { + if (next.isFile()) { + IPath nextPath = new Path(directory.getPath()).append(next.getName()); + String name = nextPath.removeFileExtension().lastSegment(); + if (name.equals(baseName)) { + result.add(URI.createPlatformResourceURI(nextPath.toString(), true)); + } + } + } + } + + return result; + } + + private boolean matchContentType(Set<URI> uris) { + boolean result = false; + + out: for (URI next : uris) { + try { + Map<String, ?> desc = URIConverter.INSTANCE.contentDescription(next, null); + String contentTypeID = (String) desc.get(ContentHandler.CONTENT_TYPE_PROPERTY); + if (contentTypeID != null) { + IContentType contentType = Platform.getContentTypeManager().getContentType(contentTypeID); + if (contentType != null) { + for (IContentType type : contentTypes) { + if (contentType.isKindOf(type)) { + result = true; + break out; + } + } + } + } + } catch (IOException e) { + // Not our content type, I guess + } + } + + return result; + } + + // + // Nested types + // + + private static class LanguageProxy { + private final IConfigurationElement config; + private final String attribute; + + LanguageProxy(IConfigurationElement config, String attribute) { + super(); + + this.config = config; + this.attribute = attribute; + } + + ILanguage resolve() { + try { + return (ILanguage) config.createExecutableExtension(attribute); + } catch (Exception e) { + Activator.log.error("Failed to instantiate language in provider extension from contributor " + config.getContributor().getName(), e); //$NON-NLS-1$ + return null; + } + } + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/LanguageProviderRegistry.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/LanguageProviderRegistry.java new file mode 100644 index 00000000000..ce61df60eb6 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/LanguageProviderRegistry.java @@ -0,0 +1,260 @@ +/***************************************************************************** + * Copyright (c) 2015 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 java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.Platform; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.plugin.RegistryReader; +import org.eclipse.papyrus.infra.core.Activator; +import org.eclipse.papyrus.infra.core.language.ILanguage; +import org.eclipse.papyrus.infra.core.language.ILanguageProvider; +import org.eclipse.papyrus.infra.core.language.ILanguageService; +import org.eclipse.papyrus.infra.core.language.Language; +import org.eclipse.papyrus.infra.core.language.Version; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; + +/** + * A registry of contributed {@link ILanguageProvider} implementations plugged in on my extension point. + */ +public class LanguageProviderRegistry implements ILanguageProvider.Registry { + + private static final String EXT_POINT = "language"; //$NON-NLS-1$ + + private static final ILanguageProvider NULL_PROVIDER = new ILanguageProvider() { + + @Override + public Iterable<ILanguage> getLanguages(ILanguageService languageService, URI modelURI, boolean uriHasFileExtension) { + return Collections.emptyList(); + } + }; + + private final List<ILanguageProvider> providers = Lists.newArrayListWithExpectedSize(2); + + private boolean needPrune; + + public LanguageProviderRegistry() { + super(); + + new MyRegistryReader().readRegistry(); + } + + /** + * Prune out any null providers (failed to initialize) and replace + * descriptors that have been instantiated by their instances, to avoid + * delegation. + */ + private void prune() { + if (needPrune) { + needPrune = false; + for (ListIterator<ILanguageProvider> iter = providers.listIterator(); iter.hasNext();) { + + ILanguageProvider next = iter.next(); + if (next == NULL_PROVIDER) { + iter.remove(); // Don't need this, any more + } else if (next instanceof MyRegistryReader.Descriptor) { + MyRegistryReader.Descriptor desc = (MyRegistryReader.Descriptor) next; + if (desc.instance != null) { + iter.set(desc.instance); + } + } + } + } + } + + @Override + public Collection<ILanguageProvider> getProviders() { + prune(); + return Collections.unmodifiableCollection(providers); + } + + private void removeProvider(String className) { + synchronized (providers) { + for (Iterator<ILanguageProvider> iter = providers.iterator(); iter.hasNext();) { + + ILanguageProvider next = iter.next(); + if (next instanceof MyRegistryReader.Descriptor) { + MyRegistryReader.Descriptor desc = (MyRegistryReader.Descriptor) next; + if (className.equals(desc.getClassName())) { + iter.remove(); + break; + } + } else if (className.equals(next.getClass().getName())) { + iter.remove(); + break; + } + } + } + } + + // + // Nested types + // + + private class MyRegistryReader extends RegistryReader { + + private static final String A_CLASS = "class"; //$NON-NLS-1$ + + private static final String E_PROVIDER = "provider"; //$NON-NLS-1$ + + private static final String E_CONTENT_TYPE = "content-type"; //$NON-NLS-1$ + private static final String E_LANGUAGE = "language"; //$NON-NLS-1$ + private static final String A_ID = "id"; //$NON-NLS-1$ + private static final String A_VERSION = "version"; //$NON-NLS-1$ + private static final String A_NAME = "name"; //$NON-NLS-1$ + + private Descriptor currentDescriptor; + + MyRegistryReader() { + super(Platform.getExtensionRegistry(), Activator.PLUGIN_ID, EXT_POINT); + } + + @Override + protected boolean readElement(IConfigurationElement element, boolean add) { + return add ? handleAdd(element) : handleRemove(element); + } + + private boolean handleAdd(IConfigurationElement element) { + boolean result = true; + + if (E_PROVIDER.equals(element.getName())) { + currentDescriptor = new Descriptor(element, A_CLASS); + providers.add(currentDescriptor); + } + + return result; + } + + private boolean handleRemove(IConfigurationElement element) { + boolean result = true; + + if (E_PROVIDER.equals(element.getName())) { + String className = getClassName(element, A_CLASS); + if (className != null) { + removeProvider(className); + } + } + + return result; + } + + String getClassName(IConfigurationElement provider, String attributeName) { + String result = provider.getAttribute(attributeName); + if (result == null) { + IConfigurationElement[] classes = provider.getChildren(attributeName); + if (classes.length > 0) { // Assume a single occurrence + result = classes[0].getAttribute("class"); //$NON-NLS-1$ It's always 'class' attribute of a nested element + } + } + return result; + } + + private class Descriptor extends PluginClassDescriptor implements ILanguageProvider { + + private ILanguageProvider instance; + + Descriptor(IConfigurationElement element, String attributeName) { + super(element, attributeName); + } + + String getClassName() { + return MyRegistryReader.this.getClassName(element, attributeName); + } + + ILanguageProvider getInstance() { + if (instance == null) { + try { + String className = getClassName(); + if (className == null) { + // It's the default implementation + instance = createDefaultProvider(); + } else { + instance = (ILanguageProvider) createInstance(); + } + } catch (Exception e) { + Activator.log.error("Failed to instantiate language provider extension.", e); + instance = NULL_PROVIDER; + } + + needPrune = true; + } + + return instance; + } + + @Override + public Iterable<ILanguage> getLanguages(ILanguageService languageService, URI modelURI, boolean uriHasFileExtension) { + return getInstance().getLanguages(languageService, modelURI, uriHasFileExtension); + } + + ILanguageProvider createDefaultProvider() throws CoreException { + DefaultLanguageProvider result = new DefaultLanguageProvider(); + + for (IConfigurationElement next : element.getChildren()) { + if (E_CONTENT_TYPE.equals(next.getName())) { + addContentType(result, next); + } else if (E_LANGUAGE.equals(next.getName())) { + addLanguage(result, next); + } + } + + return result; + } + + private void addContentType(DefaultLanguageProvider provider, IConfigurationElement contentType) { + String id = contentType.getAttribute(A_ID); + if (Strings.isNullOrEmpty(id)) { + logMissingAttribute(contentType, A_ID); + } else { + provider.addContentType(id); + } + } + + private void addLanguage(DefaultLanguageProvider provider, IConfigurationElement language) throws CoreException { + String className = language.getAttribute(A_CLASS); + if (Strings.isNullOrEmpty(className)) { + className = Language.class.getName(); + } + + // Maybe the plug-in specifies the default Language class explicitly + if (!className.equals(Language.class.getName())) { + provider.addLanguage(language, A_CLASS); + } else { + String id = language.getAttribute(A_ID); + if (Strings.isNullOrEmpty(id)) { + logMissingAttribute(language, A_ID); + } + String version = language.getAttribute(A_VERSION); + if (Strings.isNullOrEmpty(version)) { + logMissingAttribute(language, A_VERSION); + } + String name = language.getAttribute(A_NAME); + if (Strings.isNullOrEmpty(name)) { + logMissingAttribute(language, A_NAME); + } + provider.addLanguage(new Language(id, new Version(version), name)); + } + } + } + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/LanguageService.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/LanguageService.java new file mode 100644 index 00000000000..c8f91883549 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/internal/language/LanguageService.java @@ -0,0 +1,110 @@ +/***************************************************************************** + * Copyright (c) 2015 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 java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.core.runtime.PlatformObject; +import org.eclipse.emf.common.util.URI; +import org.eclipse.papyrus.infra.core.language.ILanguage; +import org.eclipse.papyrus.infra.core.language.ILanguageProvider; +import org.eclipse.papyrus.infra.core.language.ILanguageService; +import org.eclipse.papyrus.infra.core.services.IService; +import org.eclipse.papyrus.infra.core.services.ServiceException; +import org.eclipse.papyrus.infra.core.services.ServicesRegistry; +import org.eclipse.papyrus.infra.core.utils.ServiceUtils; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +/** + * The implementation of the Papyrus Modeling Language Service. + */ +public class LanguageService extends PlatformObject implements ILanguageService, IService { + + private final CopyOnWriteArrayList<ILanguageProvider> languageProviders = Lists.newCopyOnWriteArrayList(); + + private ServicesRegistry registry; + + public LanguageService() { + super(); + } + + @Override + public Set<ILanguage> getLanguages(URI modelURI, boolean uriHasFileExtension) { + Set<ILanguage> result = Sets.newHashSet(); + + for (ILanguageProvider next : languageProviders) { + Iterables.addAll(result, next.getLanguages(this, modelURI, uriHasFileExtension)); + } + + return result; + } + + @Override + public void addLanguageProvider(ILanguageProvider provider) { + languageProviders.addIfAbsent(provider); + } + + @Override + public void removeLanguageProvider(ILanguageProvider provider) { + if (provider != null) { + languageProviders.remove(provider); + } + } + + // + // Adaptable API + // + + + /** + * Adaptation to other services in the registry takes precedence over registered adapter factories. + */ + @Override + public <T> T getAdapter(Class<T> adapter) { + T result; + + if (adapter.isInstance(registry)) { + result = adapter.cast(registry); + } else { + result = (registry == null) ? null : ServiceUtils.getInstance().getService(adapter, registry, null); + } + + return (result != null) ? result : super.getAdapter(adapter); + } + + // + // Service lifecycle API + // + + @Override + public void init(ServicesRegistry servicesRegistry) throws ServiceException { + this.registry = servicesRegistry; + } + + @Override + public void startService() throws ServiceException { + languageProviders.addAllAbsent(ILanguageProvider.Registry.INSTANCE.getProviders()); + } + + @Override + public void disposeService() throws ServiceException { + languageProviders.clear(); + registry = null; + } + +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/ILanguage.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/ILanguage.java new file mode 100644 index 00000000000..86ce58cfa22 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/ILanguage.java @@ -0,0 +1,83 @@ +/***************************************************************************** + * Copyright (c) 2015 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.language; + +import org.eclipse.papyrus.infra.core.resource.ModelSet; + +/** + * <p> + * Protocol for the definition of a <em>Modeling Language</em> as it is instantiated + * in a Papyrus model. Although one may distinguish between primary or core languages + * such as UML, Ecore, MOF, etc. as implemented by OMG metamodels and derivative + * languages such as UML profiles, specializations, or package merges, this API does + * not make any such distinction. + * </p> + * <p> + * Any number of languages may be instantiated in a Papyrus model, but as long as the + * {@linkplain ILanguageService Language Service} is present, there is always at least + * one language. + * </p> + */ +public interface ILanguage { + + /** + * Obtains an unique identifier of the language, irrespective of version. + * + * @return + */ + String getID(); + + /** + * Obtains the version of the language. All versions of a language have the same {@linkplain #getID() identifier}. + * + * @return the version. Never {@code null} + */ + Version getVersion(); + + /** + * Obtains the user-presentable (translated for the current locale) name of the language, not including any {@linkplain #getVersion() version number}. + * + * @return my name. Never {@code null} or empty + */ + String getName(); + + /** + * Installs me on a model-set. This performs whatever registrations, listener attachments, etc. that may be necessary + * for the proper functioning of my language in the Papyrus context. + * + * @param modelSet + * a model-set to configure + */ + void install(ModelSet modelSet); + + /** + * Uninstalls me from a model-set. This undoes whatever the {@linkplain #install(ModelSet) installation} did. + * + * @param modelSet + * a model-set to unconfigure + */ + void uninstall(ModelSet modelSet); + + /** + * Two languages are equal if they have the same {@linkplain #getID() identifier} and {@linkplain #getVersion() version}. + */ + @Override + boolean equals(Object obj); + + /** + * Languages are hashed by {@linkplain #getID() identifier} and {@linkplain #getVersion() version}. + */ + @Override + int hashCode(); +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/ILanguageProvider.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/ILanguageProvider.java new file mode 100644 index 00000000000..f1e2b3eacdd --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/ILanguageProvider.java @@ -0,0 +1,53 @@ +/***************************************************************************** + * Copyright (c) 2015 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.language; + +import java.util.Collection; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.papyrus.infra.core.internal.language.LanguageProviderRegistry; +import org.eclipse.papyrus.infra.core.resource.ModelSet; + +/** + * A provider of {@link ILanguage}s to the {@linkplain ILanguageService service}. + * Providers may be registered dynamically using the {@linkplain ILanguageService#addLanguageProvider(ILanguageProvider) service API} + * or may be registered statically on the <tt>org.eclipse.papyrus.infra.core.language</tt> extension point. + */ +public interface ILanguageProvider { + /** + * Queries the languages that are instantiated in the specified model resource. + * + * @param languageService + * the language service that is requesting languages + * @param modelURI + * the URI of a model resource for which the service wants to determine languages + * @param uriHasFileExtension + * whether the {@code modelURI} has a file extension. For example, if the {@link ModelSet} + * is requesting languages, then the URI typically does not have an extension because a model-set comprising several + * resources that all have the same base file name + * + * @return the languages instantiated in the specified resource. May be empty if this provider does not recognize any languages in the resource + */ + Iterable<ILanguage> getLanguages(ILanguageService languageService, URI modelURI, boolean uriHasFileExtension); + + // + // Nested types + // + + interface Registry { + Registry INSTANCE = new LanguageProviderRegistry(); + + Collection<ILanguageProvider> getProviders(); + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/ILanguageService.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/ILanguageService.java new file mode 100644 index 00000000000..fbf34748f84 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/ILanguageService.java @@ -0,0 +1,64 @@ +/***************************************************************************** + * Copyright (c) 2015 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.language; + +import java.util.Set; + +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.emf.common.util.URI; +import org.eclipse.papyrus.infra.core.resource.ModelSet; + +/** + * <p> + * The Language Service provides information the {@linkplain ILanguage Modeling Languages} that are + * instantiated in a Papyrus model. + * </p> + * <p> + * In my capacity as an {@link IAdaptable}, I provide other services from the registry in which I + * am provided as adapters, in addition to whatever other adapters may be contributed for me. + * </p> + * + * @see ILanguage + */ +public interface ILanguageService extends IAdaptable { + /** + * Queries the languages that are instantiated in the specified model resource. + * + * @param modelURI + * the URI of a model resource, such as might be used to load a {@link ModelSet} + * @param uriHasFileExtension + * whether the {@code modelURI} has a file extension. For example, if the {@link ModelSet} + * is requesting languages, then the URI typically does not have an extension because a model-set comprising several + * resources that all have the same base file name + * + * @return the languages instantiated in the specified resource + */ + Set<ILanguage> getLanguages(URI modelURI, boolean uriHasFileExtension); + + /** + * Registers a language {@code provider}. Has no effect if the given {@code provider} is already registered. + * + * @param provider + * a language provider to add (may not be {@code null}) + */ + void addLanguageProvider(ILanguageProvider provider); + + /** + * Unregisters a language {@code provider}. Has no effect if the given {@code provider} is not currently registered. + * + * @param provider + * a language provider to remove (may be {@code null}, which has no effect) + */ + void removeLanguageProvider(ILanguageProvider provider); +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/Language.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/Language.java new file mode 100644 index 00000000000..280ba37eaee --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/Language.java @@ -0,0 +1,101 @@ +/***************************************************************************** + * Copyright (c) 2015 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.language; + +import org.eclipse.papyrus.infra.core.resource.ModelSet; + +/** + * Default implementation of a language. This implementation does nothing when {@linkplain #install(ModelSet) installed} on a model-set. + */ +public class Language implements ILanguage { + private final String id; + private final Version version; + private final String name; + + public Language(String id, Version version, String name) { + super(); + + this.id = id; + this.version = version; + this.name = name; + } + + @Override + public String getID() { + return id; + } + + @Override + public Version getVersion() { + return version; + } + + @Override + public String getName() { + return name; + } + + @Override + public void install(ModelSet modelSet) { + // Pass + } + + @Override + public void uninstall(ModelSet modelSet) { + // Pass + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((version == null) ? 0 : version.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ILanguage)) { + return false; + } + ILanguage other = (ILanguage) obj; + if (id == null) { + if (other.getID() != null) { + return false; + } + } else if (!id.equals(other.getID())) { + return false; + } + if (version == null) { + if (other.getVersion() != null) { + return false; + } + } else if (!version.equals(other.getVersion())) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format("%s version %s", getName(), getVersion()); //$NON-NLS-1$ + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/Version.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/Version.java new file mode 100644 index 00000000000..9c232e30512 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/language/Version.java @@ -0,0 +1,221 @@ +/***************************************************************************** + * Copyright (c) 2015 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.language; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.base.Strings; + +/** + * A {@linkplain ILanguage language} version. + */ +public final class Version implements Comparable<Version> { + private final Pattern VERSION_PATTERN = Pattern.compile("^(-?\\d+)(\\.\\d+)?(\\.\\d+)?(\\.[A-Za-z0-9_+\\-]+)?$"); //$NON-NLS-1$ + + public static final Version ZERO = new Version(0, 0, 0, null); + public static final Version ONE = new Version(1, 0, 0, null); + + private final int major; + private final int minor; + private final int service; + private final String qualifier; + + /** + * Initializes me. + * + * @param major + * my major increment. Others are zero + */ + public Version(int major) { + this(major, 0, 0, null); + } + + /** + * Initializes me. + * + * @param major + * my major increment + * @param minor + * my minor increment. Must be a whole number + * + * @throws IllegalArgumentException + * if the {@code minor} segment is negative + */ + public Version(int major, int minor) { + this(major, minor, 0, null); + } + + /** + * Initializes me. + * + * @param major + * my major increment + * @param minor + * my minor increment. Must be a whole number + * @param service + * my service increment. Must be a whole number + * + * @throws IllegalArgumentException + * if any {@code minor}/{@code service} segment is negative + */ + public Version(int major, int minor, int service) { + this(major, minor, service, null); + } + + /** + * Initializes me. + * + * @param major + * my major increment + * @param minor + * my minor increment. Must be a whole number + * @param service + * my service increment. Must be a whole number + * @param qualifier + * my qualifier (may be {@code null} if not needed, but not empty) + * + * @throws IllegalArgumentException + * if any {@code minor}/{@code service} segment is negative or if the qualifier is the empty string + */ + public Version(int major, int minor, int service, String qualifier) { + super(); + + if (minor < 0) { + throw new IllegalArgumentException("Negative minor segment"); //$NON-NLS-1$ + } + if (service < 0) { + throw new IllegalArgumentException("Negative service segment"); //$NON-NLS-1$ + } + if ("".equals(qualifier)) { //$NON-NLS-1$ + throw new IllegalArgumentException("Empty qualifier; should be null"); //$NON-NLS-1$ + } + + this.major = major; + this.minor = minor; + this.service = service; + this.qualifier = qualifier; + } + + /** + * Initializes me from my string representation. + * + * @param versionSpec + * my string representation + * + * @throws IllegalArgumentException + * if the string representation has errors in any segment + */ + public Version(String versionSpec) { + super(); + + Matcher m = VERSION_PATTERN.matcher(versionSpec); + if (!m.matches()) { + throw new IllegalArgumentException("Invalid version specification: " + versionSpec); //$NON-NLS-1$ + } + + this.major = Integer.parseInt(m.group(1)); + this.minor = m.group(2) == null ? 0 : Integer.parseInt(m.group(2).substring(1)); + this.service = m.group(3) == null ? 0 : Integer.parseInt(m.group(3).substring(1)); + this.qualifier = m.group(3) == null ? null : m.group(3).substring(1); + } + + public int major() { + return major; + } + + public int minor() { + return minor; + } + + public int service() { + return service; + } + + public String qualifier() { + return qualifier; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + major; + result = prime * result + minor; + result = prime * result + ((qualifier == null) ? 0 : qualifier.hashCode()); + result = prime * result + service; + return result; + } + + @Override + public String toString() { + return (qualifier == null) + ? String.format("%s.%s.%s", major, minor, service) //$NON-NLS-1$ + : String.format("%s.%s.%s.%s", major, minor, service, qualifier); //$NON-NLS-1$ + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Version)) { + return false; + } + Version other = (Version) obj; + if (major != other.major) { + return false; + } + if (minor != other.minor) { + return false; + } + if (qualifier == null) { + if (other.qualifier != null) { + return false; + } + } else if (!qualifier.equals(other.qualifier)) { + return false; + } + if (service != other.service) { + return false; + } + return true; + } + + @Override + public int compareTo(Version o) { + int result = o.major - this.major; + if (result == 0) { + result = o.minor - this.minor; + if (result == 0) { + result = o.service - this.service; + if (result == 0) { + result = Strings.nullToEmpty(o.qualifier).compareTo(Strings.nullToEmpty(this.qualifier)); + } + } + } + return result; + } + + public static org.osgi.framework.Version toOSGI(Version version) { + return new org.osgi.framework.Version(version.major, version.minor, version.service, version.qualifier); + } + + public static Version fromOSGI(org.osgi.framework.Version version) { + return new Version(version.getMajor(), version.getMinor(), version.getMicro(), Strings.emptyToNull(version.getQualifier())); + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/ModelSet.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/ModelSet.java index a69a10ddc4a..50d98d220ea 100644 --- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/ModelSet.java +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/resource/ModelSet.java @@ -20,6 +20,7 @@ * Gabriel Pascual (ALL4TEC) gabriel.pascual@all4tec.net - Bug 436952 * Gabriel Pascual (ALL4TEC) gabriel.pascual@all4tec.net - Bug 436998 * Christian W. Damus - bug 436998 + * Christian W. Damus - bug 468030 * *****************************************************************************/ package org.eclipse.papyrus.infra.core.resource; @@ -61,10 +62,15 @@ import org.eclipse.emf.edit.domain.EditingDomain; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.emf.transaction.impl.EditingDomainManager; import org.eclipse.papyrus.infra.core.Activator; +import org.eclipse.papyrus.infra.core.editor.ModelSetServiceFactory; +import org.eclipse.papyrus.infra.core.language.ILanguage; +import org.eclipse.papyrus.infra.core.language.ILanguageService; import org.eclipse.papyrus.infra.core.resource.additional.AdditionalResourcesModel; +import org.eclipse.papyrus.infra.core.utils.ServiceUtils; import org.eclipse.papyrus.infra.tools.util.PlatformHelper; import com.google.common.base.Optional; +import com.google.common.collect.Lists; /** * This class is used to manage a set of {@link IModel}. @@ -128,7 +134,7 @@ public class ModelSet extends ResourceSetImpl { /** map of resource loaded in the resource set, with resource as the key and a boolean indicating if the resource is loaded or not has the valuer */ protected Map<Resource, Boolean> resourcesToLoadState = new HashMap<Resource, Boolean>(); - + private List<ILanguage> languages; /** * @@ -565,6 +571,8 @@ public class ModelSet extends ResourceSetImpl { // Get the file name, without extension. uriWithoutExtension = uri.trimFileExtension(); + installLanguages(); + ModelMultiException exceptions = null; List<IModel> orderedModelsForLoading = getOrderedModelsForLoading(); @@ -995,6 +1003,8 @@ public class ModelSet extends ResourceSetImpl { iter.remove(); } + uninstallLanguages(); + // Clear the package registry (it may contain dynamic profile EPackages that we don't // want to leak in BasicExtendedMetaData instances attached to static EPackages) // Works around EMF bug 433108 @@ -1258,5 +1268,32 @@ public class ModelSet extends ResourceSetImpl { return null; } + protected void installLanguages() { + ILanguageService languageService = ServiceUtils.getInstance().getService(ILanguageService.class, ModelSetServiceFactory.getServiceRegistry(this), null); + if (languageService != null) { + languages = Lists.newArrayList(languageService.getLanguages(getURIWithoutExtension(), false)); + for (ILanguage next : languages) { + try { + next.install(this); + } catch (Exception e) { + Activator.log.error("Uncaught exception in installation of language " + next, e); //$NON-NLS-1$ + } + } + } + } + + protected void uninstallLanguages() { + if (languages != null) { + for (ILanguage next : Lists.reverse(languages)) { + try { + next.uninstall(this); + } catch (Exception e) { + Activator.log.error("Uncaught exception in uninstallation of language " + next, e); //$NON-NLS-1$ + } + } + + languages.clear(); + } + } } diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/BadStateException.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/BadStateException.java index 1a7aa023923..fbcfe0b4402 100644 --- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/BadStateException.java +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/BadStateException.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2011, 2014 LIFL and others. + * Copyright (c) 2011, 2015 LIFL, Christian W. Damus, and others. * * * All rights reserved. This program and the accompanying materials @@ -9,6 +9,7 @@ * * Contributors: * LIFL - Initial API and implementation + * Christian W. Damus - bug 468030 * *****************************************************************************/ package org.eclipse.papyrus.infra.core.services; @@ -41,7 +42,7 @@ public class BadStateException extends ServiceException { * @param serviceDescriptor */ public BadStateException(String text, ServiceState state, ServiceDescriptor descriptor) { - super(text + " (Service= '" + descriptor.getKey() + ", state= " + state + ")"); + super(text + " (Service= '" + descriptor.getKey() + "', state= " + state + ")"); } } diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/ServiceException.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/ServiceException.java index f8c749a8f13..1ec8d111ec0 100644 --- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/ServiceException.java +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/ServiceException.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2011, 2014 LIFL and others. + * Copyright (c) 2011, 2015 LIFL, Christian W. Damus, and others. * * * All rights reserved. This program and the accompanying materials @@ -9,6 +9,7 @@ * * Contributors: * LIFL - Initial API and implementation + * Christian W. Damus - bug 468030 * *****************************************************************************/ package org.eclipse.papyrus.infra.core.services; @@ -30,7 +31,7 @@ public class ServiceException extends Exception { * Constructor. */ public ServiceException() { - // TODO Auto-generated constructor stub + super(); } /** @@ -40,7 +41,6 @@ public class ServiceException extends Exception { */ public ServiceException(String message) { super(message); - // TODO Auto-generated constructor stub } /** @@ -50,7 +50,6 @@ public class ServiceException extends Exception { */ public ServiceException(Throwable cause) { super(cause); - // TODO Auto-generated constructor stub } /** @@ -61,7 +60,35 @@ public class ServiceException extends Exception { */ public ServiceException(String message, Throwable cause) { super(message, cause); - // TODO Auto-generated constructor stub } + public ServiceException chain(Throwable next) { + return (next == null) ? this : new ServiceMultiException().chain(next); + } + + public ServiceException chain(String identifier, Throwable next) { + return (next == null) ? this : new ServiceMultiException().chain(identifier, next); + } + + public static ServiceException chain(ServiceException serviceException, Throwable next) { + if (serviceException != null) { + return serviceException.chain(next); + } else if (next instanceof ServiceException) { + return (ServiceException) next; + } else if (next != null) { + return new ServiceMultiException().chain(next); + } else { + return serviceException; + } + } + + public static ServiceException chain(ServiceException serviceException, String identifier, Throwable next) { + if (serviceException != null) { + return serviceException.chain(identifier, next); + } else if (next != null) { + return new ServiceMultiException().chain(identifier, next); + } else { + return serviceException; + } + } } diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/ServiceMultiException.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/ServiceMultiException.java index 3484f6d98d5..3a6f7ae2369 100644 --- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/ServiceMultiException.java +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/ServiceMultiException.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2011, 2014 LIFL and others. + * Copyright (c) 2011, 2015 LIFL, Christian W. Damus, and others. * * * All rights reserved. This program and the accompanying materials @@ -9,6 +9,7 @@ * * Contributors: * LIFL - Initial API and implementation + * Christian W. Damus - bug 468030 * *****************************************************************************/ package org.eclipse.papyrus.infra.core.services; @@ -149,4 +150,21 @@ public class ServiceMultiException extends ServiceException { serviceIdentifiers.addAll(serviceIdentifiers); } + @Override + public ServiceException chain(Throwable next) { + if (next instanceof ServiceMultiException) { + addAll((ServiceMultiException) next); + } else if (next != null) { + addException(next); + } + return this; + } + + @Override + public ServiceException chain(String identifier, Throwable next) { + if (next != null) { + addException(identifier, next); + } + return this; + } } diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/ServicesRegistry.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/ServicesRegistry.java index 695d89c579a..13601f8708c 100644 --- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/ServicesRegistry.java +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/ServicesRegistry.java @@ -1,5 +1,5 @@ /*****************************************************************************
- * Copyright (c) 2011, 2014 LIFL, CEA, and others.
+ * Copyright (c) 2011, 2015 LIFL, CEA, 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,6 +9,7 @@ * Contributors:
* LIFL - Initial API and implementation
* Christian W. Damus (CEA) - bug 431953 (fix start-up of selective services to require only their dependencies)
+ * Christian W. Damus - bug 468030
*/
package org.eclipse.papyrus.infra.core.services;
@@ -628,14 +629,13 @@ public class ServicesRegistry { }
// Detect cycles
checkCycle(roots, map);
- // Retain only services with startupkind == START and state ==
- // REGISTERED
- roots = retainsToStartServices(roots);
+ // Retain only services with state == REGISTERED
+ roots = retainUnstartedServices(roots);
//
List<ServiceStartupEntry> toStart = buildTopologicalListOfServicesToStart(roots, map);
// Remove already started services
- toStart = retainsToStartServices(toStart);
+ toStart = retainUnstartedServices(toStart);
// if( log.isLoggable(Level.FINE))
// {
@@ -769,6 +769,24 @@ public class ServicesRegistry { }
/**
+ * Retains the services that are not yet started. Retains only services with state == REGISTERED
+ *
+ * @param services
+ * Collection to filter
+ * @return a new Collection containing the registered services
+ */
+ private List<ServiceStartupEntry> retainUnstartedServices(Collection<ServiceStartupEntry> services) {
+ List<ServiceStartupEntry> result = new ArrayList<ServiceStartupEntry>(services.size());
+ for (ServiceStartupEntry service : services) {
+ if (service.getState() == ServiceState.registered) {
+ result.add(service);
+ }
+ }
+
+ return result;
+ }
+
+ /**
* Check for cycles. Throws an exception if a cycle is discovered. Each root
* is checked to see if it contains a cycle.
*
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/internal/LazyStartupEntry.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/internal/LazyStartupEntry.java index a8df83e7159..708cd8b5dc8 100644 --- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/internal/LazyStartupEntry.java +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/internal/LazyStartupEntry.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) CEA LIST. + * Copyright (c) CEA LIST, Christian W. Damus, and others. * * * All rights reserved. This program and the accompanying materials @@ -9,10 +9,12 @@ * * Contributors: * Cedric Dumoulin Cedric.dumoulin@lifl.fr - Initial API and implementation + * Christian W. Damus - bug 468030 * *****************************************************************************/ package org.eclipse.papyrus.infra.core.services.internal; +import org.eclipse.papyrus.infra.core.Activator; import org.eclipse.papyrus.infra.core.services.IService; import org.eclipse.papyrus.infra.core.services.ServiceException; import org.eclipse.papyrus.infra.core.services.ServiceState; @@ -57,6 +59,7 @@ public class LazyStartupEntry extends ServiceStartupEntry { serviceEntry.startService(); } catch (Exception e) { // There was an error. The service is in error + Activator.log.error("Failed to start lazy service: " + getDescriptor().getKey(), e); //$NON-NLS-1$ serviceEntry = new ErrorServiceTypeEntry(serviceEntry.getDescriptor()); } } diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/AbstractServiceUtils.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/AbstractServiceUtils.java index 6390ec7d546..2ce85f89fc6 100644 --- a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/AbstractServiceUtils.java +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/utils/AbstractServiceUtils.java @@ -1,5 +1,5 @@ /*****************************************************************************
- * Copyright (c) 2010 LIFL & CEA LIST.
+ * Copyright (c) 2010, 2015 LIFL & CEA LIST, Christian W. Damus, and others.
*
*
* All rights reserved. This program and the accompanying materials
@@ -9,6 +9,7 @@ *
* Contributors:
* Cedric Dumoulin (LIFL) cedric.dumoulin@lifl.fr - Initial API and implementation
+ * Christian W. Damus - bug 468030
*
*****************************************************************************/
@@ -143,7 +144,7 @@ public abstract class AbstractServiceUtils<T> { * @param from
* The context from which the service should be retrieved
* @return
- * The implementation of the requested service
+ * The implementation of the requested service
* @throws ServiceException
* If an error occurs (e.g. cannot find the ServicesRegistry or the Service)
*
@@ -160,7 +161,7 @@ public abstract class AbstractServiceUtils<T> { * @param from
* The context from which the service should be retrieved
* @return
- * The implementation of the requested service
+ * The implementation of the requested service
* @throws ServiceException
* If an error occurs (e.g. cannot find the ServicesRegistry or the Service)
*
@@ -168,4 +169,29 @@ public abstract class AbstractServiceUtils<T> { public Object getService(Object service, T from) throws ServiceException {
return getServiceRegistry(from).getService(service);
}
+
+ /**
+ * Returns an implementation of the requested <em>optional</em> service, from the specified context, if it is available.
+ *
+ * @param service
+ * The service for which an implementation is requested
+ * @param from
+ * The context from which the service should be retrieved
+ * @param defaultImpl
+ * A default implementation of the requested service API to return if none is available in the registry
+ * or if the registered implementation could not be properly initialized. May be {@code null} if the
+ * service is <em>optional</em>
+ *
+ * @return
+ * The implementation of the requested service, or the {@code defaultImpl}
+ */
+ public <S> S getService(Class<S> service, T from, S defaultImpl) {
+ try {
+ // Don't even attempt to get a registry from a null context
+ return (from == null) ? defaultImpl : getServiceRegistry(from).getService(service);
+ } catch (ServiceException e) {
+ // That's OK. It's optional and we have a default
+ return defaultImpl;
+ }
+ }
}
diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/.classpath b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/.classpath index 0c22b5d7e6d..6a42377b56a 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.6"/> + <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="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 94d61f00da6..f08be2b06c4 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.6 -org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 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.7 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 021d637083c..6c1f1462a0e 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 @@ -24,7 +24,7 @@ Require-Bundle: org.eclipse.core.runtime, org.eclipse.core.expressions;bundle-version="3.4.600", org.eclipse.papyrus.infra.emf.readonly;bundle-version="1.1.0", org.eclipse.papyrus.infra.tools;bundle-version="1.1.0" -Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-ActivationPolicy: lazy;exclude:=org.eclipse.papyrus.uml.decoratormodel.internal.expressions Export-Package: org.eclipse.papyrus.uml.decoratormodel, org.eclipse.papyrus.uml.decoratormodel.helper, 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 a4b704e740d..52618d1c682 100644 --- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/plugin.xml +++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/plugin.xml @@ -80,5 +80,15 @@ class="org.eclipse.papyrus.uml.decoratormodel.profileExternalization.ProfileExternalizationPackage" genModel="model/ProfileExternalization.profile.genmodel"/> </extension> + <extension + point="org.eclipse.papyrus.infra.core.service"> + <service + classname="org.eclipse.papyrus.uml.decoratormodel.internal.resource.index.ProfileIndex" + id="org.eclipse.papyrus.uml.tools.profile.index.IProfileIndex" + priority="10" + startKind="lazy" + description="The profile application index for workspace resources"> + </service> + </extension> </plugin> diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/helper/DecoratorModelUtils.java b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/helper/DecoratorModelUtils.java index 6b0e3cc04d4..c558fee75a2 100644 --- a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/helper/DecoratorModelUtils.java +++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/helper/DecoratorModelUtils.java @@ -11,6 +11,7 @@ * Christian W. Damus - bug 399859 * Christian W. Damus - bug 458197 * Christian W. Damus - bug 459613 + * Christian W. Damus - bug 468030 * *****************************************************************************/ package org.eclipse.papyrus.uml.decoratormodel.helper; @@ -778,6 +779,32 @@ public class DecoratorModelUtils { } /** + * Queries whether a given {@code file} is a decorator model resource. + * <p> + * This method does <em>not</em> access the decorator model index. + * + * @param file + * a workspace file + * + * @return whether the fileexists and is a decorator model + */ + public static boolean isDecoratorModel(IFile file) { + boolean result = false; + + if (file.isAccessible()) { + try { + IContentDescription desc = file.exists() ? file.getContentDescription() : null; + result = (desc != null) && (desc.getContentType() != null) && desc.getContentType().isKindOf(DECORATOR_MODEL_CONTENT_TYPE); + } catch (CoreException e) { + // Couldn't determine content description? Then it cannot be our type + Activator.getDefault().getLog().log(e.getStatus()); + } + } + + return result; + } + + /** * Whether the resource indicated by the given URI is a decorator model resource. * <p> * This method does <em>not</em> access the decorator model index. @@ -794,13 +821,7 @@ public class DecoratorModelUtils { // Let the workspace's content-type manager handle it (which can cache the information) IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(uri.toPlatformString(true))); - try { - IContentDescription desc = file.exists() ? file.getContentDescription() : null; - result = (desc != null) && (desc.getContentType() != null) && desc.getContentType().isKindOf(DECORATOR_MODEL_CONTENT_TYPE); - } catch (CoreException e) { - // Couldn't determine content description? Then it cannot be our type - Activator.getDefault().getLog().log(e.getStatus()); - } + result = isDecoratorModel(file); } else { // Work it out the hard way InputStream input = null; 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 e8c69c076f7..b4de5bc665b 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 @@ -13,8 +13,8 @@ package org.eclipse.papyrus.uml.decoratormodel.internal.resource; -import java.io.IOException; import java.io.InputStream; +import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; @@ -37,8 +37,12 @@ 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.ModelIndexHandler; import org.eclipse.papyrus.uml.decoratormodel.internal.resource.index.ProfileIndexHandler; import org.eclipse.uml2.common.util.CacheAdapter; +import org.eclipse.uml2.uml.Profile; +import org.eclipse.uml2.uml.UMLPackage; +import org.xml.sax.helpers.DefaultHandler; import com.google.common.base.Function; import com.google.common.collect.HashMultimap; @@ -48,6 +52,7 @@ import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.SetMultimap; +import com.google.common.collect.Sets; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -68,6 +73,8 @@ public class DecoratorModelIndex { private final Map<URI, Map<URI, Map<URI, URI>>> decoratorModelToPackageToProfileApplications = Maps.newHashMap(); private final Map<URI, String> decoratorModelNames = Maps.newHashMap(); + private final Map<URI, SetMultimap<URI, URI>> userModelToResourceToAppliedProfiles = Maps.newHashMap(); + private final WorkspaceModelIndex<DecoratorModelIndex> index; private final CopyOnWriteArrayList<IDecoratorModelIndexListener> listeners = Lists.newCopyOnWriteArrayList(); @@ -78,7 +85,7 @@ public class DecoratorModelIndex { private DecoratorModelIndex() { super(); - index = new WorkspaceModelIndex<DecoratorModelIndex>("decoratorModels", DecoratorModelUtils.DECORATOR_MODEL_CONTENT_TYPE.getId(), indexer(), MAX_INDEX_JOBS); //$NON-NLS-1$ + index = new WorkspaceModelIndex<DecoratorModelIndex>("papyrusUMLProfiles", UMLPackage.eCONTENT_TYPE, indexer(), MAX_INDEX_JOBS); //$NON-NLS-1$ index.addListener(new WorkspaceModelIndexAdapter() { @Override protected void indexAboutToCalculateOrRecalculate(WorkspaceModelIndexEvent event) { @@ -466,42 +473,73 @@ public class DecoratorModelIndex { }; } - <V> ListenableFuture<V> afterIndex(Callable<V> callable) { - return index.afterIndex(callable); + /** + * Asynchronously queries the set of URIs of {@link Profile}s applied internally and by decorators to {@link Package}s + * within the specified user model. + * + * @param userModelURI + * the URI of a user model + * @return a future result of the set of URIs of the profile elements applied to it + */ + public ListenableFuture<Set<URI>> getAllProfilesAppliedToPackagesAsync(URI userModelURI) { + return afterIndex(getAllProfilesAppliedToPackagesCallable(userModelURI)); } - private void index(IFile file) { - final URI decoratorURI = URI.createPlatformResourceURI(file.getFullPath().toString(), true); + /** + * Queries the set of URIs of {@link Profile}s applied internally and by decorators to {@link Package}s + * within the specified user model. + * + * @param userModelURI + * the URI of a user model + * @return the set of URIs of the profile elements applied to it + */ + public Set<URI> getAllProfilesAppliedToPackages(URI userModelURI) throws CoreException { + return sync(afterIndex(getAllProfilesAppliedToPackagesCallable(userModelURI))); + } - ProfileIndexHandler handler = new ProfileIndexHandler(decoratorURI); + Callable<Set<URI>> getAllProfilesAppliedToPackagesCallable(final URI userModelURI) { + return new SyncCallable<Set<URI>>() { + @Override + protected Set<URI> doCall() { + SetMultimap<URI, URI> resourceToAppliedProfiles = userModelToResourceToAppliedProfiles.get(userModelURI); + return (resourceToAppliedProfiles == null) ? Collections.<URI> emptySet() : ImmutableSet.copyOf(resourceToAppliedProfiles.values()); + } + }; + } - InputStream input = null; + <V> ListenableFuture<V> afterIndex(Callable<V> callable) { + return index.afterIndex(callable); + } - try { + 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(); - input = file.getContents(); - - parser.parse(input, handler, decoratorURI.toString()); + parser.parse(input, handler, resourceURI.toString()); } catch (Exception e) { // We intentionally bomb out early with an exception - } finally { - if (input != null) { - try { - input.close(); - } catch (IOException e) { - Activator.log.error("Could not close file after indexing.", e); //$NON-NLS-1$ - } - } } + } + + private void indexDecoratorModel(IFile file) { + final URI decoratorURI = URI.createPlatformResourceURI(file.getFullPath().toString(), true); + + ProfileIndexHandler handler = new ProfileIndexHandler(decoratorURI); + + runIndexHandler(file, decoratorURI, handler); synchronized (sync) { // first, remove all links to the decorator model for (URI next : decoratorToModels.get(decoratorURI)) { modelToDecorators.remove(next, decoratorURI); + + SetMultimap<URI, URI> decoratorMap = userModelToResourceToAppliedProfiles.get(next); + if (decoratorMap != null) { + decoratorMap.removeAll(decoratorURI); + } } // update the forward mapping @@ -530,16 +568,37 @@ public class DecoratorModelIndex { // and the externalization name index decoratorModelNames.put(decoratorURI, handler.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()) { + URI userModelURI = next.getKey().trimFragment(); + if (userModelsProcessed.add(userModelURI)) { + SetMultimap<URI, URI> resourceToAppliedProfiles = userModelToResourceToAppliedProfiles.get(userModelURI); + if (resourceToAppliedProfiles == null) { + resourceToAppliedProfiles = HashMultimap.create(); + userModelToResourceToAppliedProfiles.put(userModelURI, resourceToAppliedProfiles); + } + for (URI profileURI : next.getValue().keySet()) { + resourceToAppliedProfiles.put(decoratorURI, profileURI); + } + } + } } } - private void unindex(IFile file) { + private void unindexDecoratorModel(IFile file) { 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)) { modelToDecorators.remove(next, decoratorURI); + + SetMultimap<URI, URI> resourceToAppliedProfiles = userModelToResourceToAppliedProfiles.get(next); + if (resourceToAppliedProfiles != null) { + resourceToAppliedProfiles.removeAll(decoratorURI); + } } // remove the forward mapping @@ -561,17 +620,59 @@ public class DecoratorModelIndex { } } + private void indexUserModel(IFile file) { + final URI userModelURI = URI.createPlatformResourceURI(file.getFullPath().toString(), true); + + ModelIndexHandler handler = new ModelIndexHandler(userModelURI); + + runIndexHandler(file, userModelURI, handler); + + synchronized (sync) { + // remove all internal profile applications of this resource + SetMultimap<URI, URI> resourceToAppliedProfiles = userModelToResourceToAppliedProfiles.get(userModelURI); + if (resourceToAppliedProfiles != null) { + resourceToAppliedProfiles.removeAll(userModelURI); + } + + // then add the applied profiles by resource (external and internal) + for (Map<URI, URI> next : handler.getProfileApplicationsByPackage().values()) { + if (resourceToAppliedProfiles == null) { + resourceToAppliedProfiles = HashMultimap.create(); + userModelToResourceToAppliedProfiles.put(userModelURI, resourceToAppliedProfiles); + } + resourceToAppliedProfiles.putAll(userModelURI, next.keySet()); // This resource applies the profiles, itself + } + } + } + + private void unindexUserModel(IFile file) { + 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); + if (resourceToAppliedProfiles != null) { + resourceToAppliedProfiles.removeAll(userModelURI); + } + } + } + private IndexHandler<DecoratorModelIndex> indexer() { return new IndexHandler<DecoratorModelIndex>() { @Override public DecoratorModelIndex index(IFile file) { - DecoratorModelIndex.this.index(file); + if (DecoratorModelUtils.isDecoratorModel(file)) { + DecoratorModelIndex.this.indexDecoratorModel(file); + } else { + DecoratorModelIndex.this.indexUserModel(file); + } return DecoratorModelIndex.this; } @Override public void unindex(IFile file) { - DecoratorModelIndex.this.unindex(file); + DecoratorModelIndex.this.unindexDecoratorModel(file); + DecoratorModelIndex.this.unindexUserModel(file); } }; } 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 new file mode 100644 index 00000000000..1eb4b23d987 --- /dev/null +++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/AbstractUMLIndexHandler.java @@ -0,0 +1,314 @@ +/***************************************************************************** + * Copyright (c) 2014, 2015 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.uml.decoratormodel.internal.resource.index; + +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.xmi.XMIResource; +import org.eclipse.uml2.uml.UMLPackage; +import org.eclipse.uml2.uml.util.UMLUtil; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; + +/** + * Framework for SAX parsing handlers for indexing UML resources. + */ +public abstract class AbstractUMLIndexHandler extends DefaultHandler { + protected final URI fileURI; + private final Map<URI, Map<URI, URI>> packageToProfileApplications = Maps.newHashMap(); + + private String umlNamespace; + private String umlPrefix; + private String xmiType; + private String xmiID; + + private Set<String> packageTypes; + private String eAnnotations; + private String references; + + private UMLElement top; + private int ignore; + + protected UMLElement currentPackage; + + private Await await = new Await(); + + /** + * Initializes me. + * + * @param fileURI + * the URI of the UML file that I am parsing + */ + public AbstractUMLIndexHandler(final URI fileURI) { + this.fileURI = fileURI; + } + + public URI getFileURI() { + return fileURI; + } + + public Map<URI, Map<URI, URI>> getProfileApplicationsByPackage() { + return packageToProfileApplications; + } + + @Override + public void startPrefixMapping(String prefix, String uri) throws SAXException { + if (uri.startsWith(XMIResource.XMI_NAMESPACE_PREFIX) || uri.startsWith(XMIResource.XMI_2_4_NAMESPACE_PREFIX)) { + xmiType = qname(prefix, "type"); //$NON-NLS-1$ + xmiID = qname(prefix, "id"); //$NON-NLS-1$ + } else if (EPackage.Registry.INSTANCE.getEPackage(uri) == UMLPackage.eINSTANCE) { + umlNamespace = uri; + umlPrefix = prefix; + + initializeUMLElementNames(); + } + } + + protected void initializeUMLElementNames() { + packageTypes = ImmutableSet.of(umlElement("Package"), umlElement("Model"), umlElement("Profile")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + eAnnotations = "eAnnotations"; //$NON-NLS-1$ + references = "references"; //$NON-NLS-1$ + } + + protected final String umlElement(String name) { + return qname(umlPrefix, name); + } + + 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 { + if (umlNamespace.equals(uri) || (top != null)) { + // skip over annotations + if (!ignore(qName, attributes)) { + push(qName, attributes); + handleUMLElement(top, attributes); + } + } + } + + protected boolean ignore(String qName, Attributes attributes) { + boolean result = false; + + if (attributes != null) { // Starting an element + result = (ignore > 0) || (eAnnotations.equals(qName) && !UMLUtil.UML2_UML_PACKAGE_2_0_NS_URI.equals(attributes.getValue("source"))); //$NON-NLS-1$ + if (result) { + ignore++; + } + } else { // Ending an element + result = (ignore > 0); + if (result) { + ignore--; + } + } + + return result; + } + + protected final void push(String qName, Attributes attributes) { + top = new UMLElement(qName, attributes); + } + + protected final UMLElement pop() { + UMLElement result = top; + if (top != null) { + top = top.parent; + } + return result; + } + + protected void handleUMLElement(UMLElement element, Attributes attributes) throws SAXException { + if (element.isPackage() && (element.getHREF() == null)) { + // An href is a reference to a package, not a package in our element hierarchy + currentPackage = element; + enterPackage(currentPackage, attributes); + } + + if (!doHandleUMLElement(element, attributes)) { + if (element.isA(UMLUtil.UML2_UML_PACKAGE_2_0_NS_URI)) { + // It's the applied profile definition annotation + await.push(references); + } else if (await.isAwaiting(element)) { + if (element.isRole(references)) { + handleAnnotationReferences(element); + } else { + handleAwaitedElement(element); + } + + await.pop(); + } + } + } + + protected void enterPackage(UMLElement package_, Attributes attributes) { + // Pass + } + + protected void exitPackage(UMLElement package_) { + // Pass + } + + protected abstract boolean doHandleUMLElement(UMLElement element, Attributes attributes); + + protected final void await(String elementName) { + await.push(elementName); + } + + protected void handleAnnotationReferences(UMLElement references) { + // Pass + } + + protected abstract void handleAwaitedElement(UMLElement element); + + protected abstract void summarize(); + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (!ignore(qName, null)) { + if (await.stopAt(pop())) { + await.pop(); + } + + if (top != null) { + UMLElement newPackage = top.nearestPackage(); + if ((newPackage != currentPackage) && (currentPackage != null)) { + exitPackage(currentPackage); + } + currentPackage = newPackage; + } + + if (top == null) { + // We're done with UML content + summarize(); + throw new OperationCanceledException(); + } + } + } + + // + // Nested types + // + + protected final class UMLElement { + final UMLElement parent; + + final String role; + final String type; + final String id; + final String href; + + UMLElement(String qName, Attributes attributes) { + parent = top; + + String type; + if (qName.equals(eAnnotations)) { + // "type" of an annotation is its source + type = attributes.getValue("source"); //$NON-NLS-1$ + } else { + type = attributes.getValue(xmiType); + if (Strings.isNullOrEmpty(type)) { + type = qName; + } + } + + this.role = qName; + this.type = type; + this.id = attributes.getValue(xmiID); + this.href = attributes.getValue("href"); //$NON-NLS-1$ + } + + boolean isPackage() { + return packageTypes.contains(type); + } + + boolean isRole(String roleName) { + return roleName.equals(role); + } + + boolean isA(String xmiType) { + return xmiType.equals(type); + } + + URI getHREF() { + return Strings.isNullOrEmpty(href) ? null : URI.createURI(href).resolve(fileURI); + } + + UMLElement nearestPackage() { + for (UMLElement next = this; next != null; next = next.parent) { + if (next.isPackage()) { + return next; + } + } + return null; + } + } + + private final class Await { + final Await parent = await; + + final String awaiting; + final UMLElement limit; + + Await() { + this(null); + } + + private Await(String awaiting) { + this.awaiting = awaiting; + this.limit = top; + } + + boolean isRoot() { + return parent == null; + } + + boolean isAwaiting(UMLElement element) { + return !isRoot() && element.isRole(awaiting); + } + + boolean stopAt(UMLElement element) { + return !isRoot() && (limit == element); + } + + Await push(String elementName) { + Await result = new Await(elementName); + await = result; + return result; + } + + void pop() { + if (!isRoot()) { + await = parent; + } + } + } + + protected static final class URIPair { + public URI first; + public URI second; + } +} 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 new file mode 100644 index 00000000000..3fa5f3a2947 --- /dev/null +++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ModelIndexHandler.java @@ -0,0 +1,124 @@ +/***************************************************************************** + * Copyright (c) 2015 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.uml.decoratormodel.internal.resource.index; + +import java.util.Collection; +import java.util.Map; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.uml2.uml.Package; +import org.eclipse.uml2.uml.Profile; +import org.xml.sax.Attributes; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; + +/** + * SAX parsing handler for indexing a UML resource. It produces one output: + * <ul> + * <li>{@link #getProfileApplicationsByPackage()}: profile applications by {@link Package}, as mappings of {@link Profile} ==> {@link EPackage} object URIs (with fragments)</li> + * </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; + private String appliedProfile; + + private URIPair currentProfileApplication; + + /** + * Initializes me. + * + * @param fileURI + * the URI of the UML file that I am parsing + */ + public ModelIndexHandler(final URI fileURI) { + super(fileURI); + } + + @Override + public URI getFileURI() { + return fileURI; + } + + @Override + public Map<URI, Map<URI, URI>> getProfileApplicationsByPackage() { + return packageToProfileApplications; + } + + @Override + protected void initializeUMLElementNames() { + super.initializeUMLElementNames(); + + profileApplication = "profileApplication"; //$NON-NLS-1$ + appliedProfile = "appliedProfile"; //$NON-NLS-1$ + } + + @Override + protected boolean doHandleUMLElement(UMLElement element, Attributes attributes) { + boolean result = false; + + if (element.isRole(profileApplication)) { + currentProfileApplication = new URIPair(); + packageProfileApplications.put(currentPackage.id, currentProfileApplication); + await(appliedProfile); + result = true; + } + + return result; + } + + @Override + protected void handleAwaitedElement(UMLElement element) { + if (element.isRole(appliedProfile)) { + URI href = element.getHREF(); + if (href != null) { + currentProfileApplication.first = href; + } + } + } + + @Override + protected void handleAnnotationReferences(UMLElement references) { + URI href = references.getHREF(); + if (href != null) { + currentProfileApplication.second = href; + } + } + + @Override + protected void summarize() { + for (String packageID : packageProfileApplications.keySet()) { + URI applyingPackageURI = fileURI.appendFragment(packageID); + Collection<URIPair> profileApplications = packageProfileApplications.get(packageID); + if (!profileApplications.isEmpty()) { + Map<URI, URI> map = packageToProfileApplications.get(applyingPackageURI); + if (map == null) { + map = Maps.newHashMap(); + packageToProfileApplications.put(applyingPackageURI, 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); + } + } + } + } + } +} diff --git a/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ProfileIndex.java b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ProfileIndex.java new file mode 100644 index 00000000000..df36a707cee --- /dev/null +++ b/plugins/uml/decoratormodel/org.eclipse.papyrus.uml.decoratormodel/src/org/eclipse/papyrus/uml/decoratormodel/internal/resource/index/ProfileIndex.java @@ -0,0 +1,66 @@ +/***************************************************************************** + * Copyright (c) 2015 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.uml.decoratormodel.internal.resource.index; + +import java.util.Set; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.papyrus.infra.core.services.IService; +import org.eclipse.papyrus.infra.core.services.ServiceException; +import org.eclipse.papyrus.infra.core.services.ServicesRegistry; +import org.eclipse.papyrus.uml.decoratormodel.internal.resource.DecoratorModelIndex; +import org.eclipse.papyrus.uml.tools.profile.index.IProfileIndex; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Implementation of the profile index provider service API that uses our workspace model index. + */ +public class ProfileIndex implements IProfileIndex, IService { + + public ProfileIndex() { + super(); + } + + @Override + public boolean indexes(URI umlResource) { + return umlResource.isPlatformResource(); + } + + @Override + public ListenableFuture<Set<URI>> getAppliedProfiles(URI umlResource) { + return DecoratorModelIndex.getInstance().getAllProfilesAppliedToPackagesAsync(umlResource); + } + + // + // Service lifecycle API + // + + @Override + public void init(ServicesRegistry servicesRegistry) throws ServiceException { + // Pass + } + + @Override + public void startService() throws ServiceException { + // Ensure that the index is alive and kicking + DecoratorModelIndex.getInstance(); + } + + @Override + public void disposeService() throws ServiceException { + // Pass + } + +} 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 b234efe9904..f5b7e20387f 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 @@ -17,19 +17,10 @@ import java.util.Collection; import java.util.Map; import java.util.Set; -import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.emf.common.util.URI; -import org.eclipse.emf.ecore.EPackage; -import org.eclipse.emf.ecore.xmi.XMIResource; -import org.eclipse.uml2.uml.UMLPackage; -import org.eclipse.uml2.uml.util.UMLUtil; import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; -import com.google.common.base.Strings; import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; @@ -41,35 +32,20 @@ import com.google.common.collect.Sets; * <li>{@link #getAppliedProfileURIs()}: a mapping of applying package to applied profile, as object URIs (with fragments)</li> * </ul> */ -public class ProfileIndexHandler extends DefaultHandler { - private final URI fileURI; +public class ProfileIndexHandler extends AbstractUMLIndexHandler { private final Set<URI> referencedModelURIs = Sets.newHashSet(); private final Map<URI, Map<URI, URI>> packageToProfileApplications = Maps.newHashMap(); private String externalizationName; - private String umlNamespace; - private String umlPrefix; - private String xmiType; - private String xmiID; - private Set<String> packageTypes; private String dependencyType; private String client; private String profileApplication; private String appliedProfile; - private String eAnnotations; - private String references; - - private UMLElement top; - private int ignore; - - private UMLElement currentPackage; private Map<String, URI> packageClients = Maps.newHashMap(); private URIPair currentProfileApplication; private Multimap<String, URIPair> packageProfileApplications = ArrayListMultimap.create(); - private Await await = new Await(); - /** * Initializes me. * @@ -77,11 +53,7 @@ public class ProfileIndexHandler extends DefaultHandler { * the URI of the profile-application file that I am parsing */ public ProfileIndexHandler(final URI fileURI) { - this.fileURI = fileURI; - } - - public URI getFileURI() { - return fileURI; + super(fileURI); } public Set<URI> getReferencedModelURIs() { @@ -97,108 +69,48 @@ public class ProfileIndexHandler extends DefaultHandler { } @Override - public void startPrefixMapping(String prefix, String uri) throws SAXException { - if (uri.startsWith(XMIResource.XMI_NAMESPACE_PREFIX) || uri.startsWith(XMIResource.XMI_2_4_NAMESPACE_PREFIX)) { - xmiType = qname(prefix, "type"); //$NON-NLS-1$ - xmiID = qname(prefix, "id"); //$NON-NLS-1$ - } else if (EPackage.Registry.INSTANCE.getEPackage(uri) == UMLPackage.eINSTANCE) { - umlNamespace = uri; - umlPrefix = prefix; - - packageTypes = ImmutableSet.of(umlElement("Package"), umlElement("Model"), umlElement("Profile")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - dependencyType = umlElement("Dependency"); //$NON-NLS-1$ - client = "client"; //$NON-NLS-1$ + protected void initializeUMLElementNames() { + super.initializeUMLElementNames(); - profileApplication = "profileApplication"; //$NON-NLS-1$ - appliedProfile = "appliedProfile"; //$NON-NLS-1$ + dependencyType = umlElement("Dependency"); //$NON-NLS-1$ + client = "client"; //$NON-NLS-1$ - eAnnotations = "eAnnotations"; //$NON-NLS-1$ - references = "references"; //$NON-NLS-1$ - } - } - - protected final String umlElement(String name) { - return qname(umlPrefix, name); - } - - 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(); + profileApplication = "profileApplication"; //$NON-NLS-1$ + appliedProfile = "appliedProfile"; //$NON-NLS-1$ } @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (umlNamespace.equals(uri) || (top != null)) { - // skip over annotations - if (!ignore(qName, attributes)) { - push(qName, attributes); - handleUMLElement(top, attributes); - } + protected void enterPackage(UMLElement package_, Attributes attributes) { + if (package_.parent == null) { + externalizationName = attributes.getValue("name"); //$NON-NLS-1$ } } - boolean ignore(String qName, Attributes attributes) { + @Override + protected boolean doHandleUMLElement(UMLElement element, Attributes attributes) { boolean result = false; - if (attributes != null) { // Starting an element - result = (ignore > 0) || (eAnnotations.equals(qName) && !UMLUtil.UML2_UML_PACKAGE_2_0_NS_URI.equals(attributes.getValue("source"))); //$NON-NLS-1$ - if (result) { - ignore++; - } - } else { // Ending an element - result = (ignore > 0); - if (result) { - ignore--; - } - } - - return result; - } - - protected final void push(String qName, Attributes attributes) { - top = new UMLElement(qName, attributes); - } - - protected final UMLElement pop() { - UMLElement result = top; - if (top != null) { - top = top.parent; - } - return result; - } - - protected void handleUMLElement(UMLElement element, Attributes attributes) throws SAXException { - if (element.isPackage() && (element.getHREF() == null)) { - // An href is a reference to a package, not a package in our element hierarchy - currentPackage = element; - - // if it's the top package, get its name - if (currentPackage.parent == null) { - externalizationName = attributes.getValue("name"); //$NON-NLS-1$ - } - } - if (element.isA(dependencyType)) { // It's a dependency. We want its client - await.push(client); + await(client); + result = true; } else if (element.isRole(profileApplication)) { currentProfileApplication = new URIPair(); packageProfileApplications.put(currentPackage.id, currentProfileApplication); - await.push(appliedProfile); - } else if (element.isA(UMLUtil.UML2_UML_PACKAGE_2_0_NS_URI)) { - // It's the applied profile definition annotation - await.push(references); - } else if (await.isAwaiting(element)) { - if (element.isRole(client) && element.isPackage()) { - // Got a package dependency client - handleDependencyClient(element); - } else if (element.isRole(appliedProfile)) { - handleAppliedProfile(element); - } else if (element.isRole(references)) { - handleReferences(element); - } + await(appliedProfile); + result = true; + } + + return result; + } - await.pop(); + @Override + protected void handleAwaitedElement(UMLElement element) { + if (element.isRole(client) && element.isPackage()) { + // Got a package dependency client + handleDependencyClient(element); + } else if (element.isRole(appliedProfile)) { + handleAppliedProfile(element); } } @@ -210,7 +122,8 @@ public class ProfileIndexHandler extends DefaultHandler { } } - protected void handleReferences(UMLElement references) { + @Override + protected void handleAnnotationReferences(UMLElement references) { URI href = references.getHREF(); if (href != null) { currentProfileApplication.second = href; @@ -224,6 +137,7 @@ public class ProfileIndexHandler extends DefaultHandler { } } + @Override protected void summarize() { for (String packageID : packageProfileApplications.keySet()) { URI applyingPackageURI = packageClients.get(packageID); @@ -236,132 +150,13 @@ public class ProfileIndexHandler extends DefaultHandler { packageToProfileApplications.put(applyingPackageURI, map); } for (URIPair profileApplication : profileApplications) { - map.put(profileApplication.first, profileApplication.second); + // If we can't determine the Ecore definition, the profile is not properly applied + if (profileApplication.second != null) { + map.put(profileApplication.first, profileApplication.second); + } } } } } } - - @Override - public void endElement(String uri, String localName, String qName) throws SAXException { - if (!ignore(qName, null)) { - if (await.stopAt(pop())) { - await.pop(); - } - - if (top != null) { - currentPackage = top.nearestPackage(); - } - - if (top == null) { - // We're done with UML content - summarize(); - throw new OperationCanceledException(); - } - } - } - - // - // Nested types - // - - class UMLElement { - final UMLElement parent; - - final String role; - final String type; - final String id; - final String href; - - UMLElement(String qName, Attributes attributes) { - parent = top; - - String type; - if (qName.equals(eAnnotations)) { - // "type" of an annotation is its source - type = attributes.getValue("source"); //$NON-NLS-1$ - } else { - type = attributes.getValue(xmiType); - if (Strings.isNullOrEmpty(type)) { - type = qName; - } - } - - this.role = qName; - this.type = type; - this.id = attributes.getValue(xmiID); - this.href = attributes.getValue("href"); //$NON-NLS-1$ - } - - boolean isPackage() { - return packageTypes.contains(type); - } - - boolean isRole(String roleName) { - return roleName.equals(role); - } - - boolean isA(String xmiType) { - return xmiType.equals(type); - } - - URI getHREF() { - return Strings.isNullOrEmpty(href) ? null : URI.createURI(href).resolve(fileURI); - } - - UMLElement nearestPackage() { - for (UMLElement next = this; next != null; next = next.parent) { - if (next.isPackage()) { - return next; - } - } - return null; - } - } - - class Await { - final Await parent = await; - - final String awaiting; - final UMLElement limit; - - Await() { - this(null); - } - - private Await(String awaiting) { - this.awaiting = awaiting; - this.limit = top; - } - - boolean isRoot() { - return parent == null; - } - - boolean isAwaiting(UMLElement element) { - return !isRoot() && element.isRole(awaiting); - } - - boolean stopAt(UMLElement element) { - return !isRoot() && (limit == element); - } - - Await push(String elementName) { - Await result = new Await(elementName); - await = result; - return result; - } - - void pop() { - if (!isRoot()) { - await = parent; - } - } - } - - private static final class URIPair { - URI first; - URI second; - } } diff --git a/plugins/uml/tools/org.eclipse.papyrus.uml.tools/META-INF/MANIFEST.MF b/plugins/uml/tools/org.eclipse.papyrus.uml.tools/META-INF/MANIFEST.MF index 4ad466a277f..4b8669b29ce 100644 --- a/plugins/uml/tools/org.eclipse.papyrus.uml.tools/META-INF/MANIFEST.MF +++ b/plugins/uml/tools/org.eclipse.papyrus.uml.tools/META-INF/MANIFEST.MF @@ -11,6 +11,7 @@ Export-Package: org.eclipse.papyrus.uml.tools, org.eclipse.papyrus.uml.tools.model, org.eclipse.papyrus.uml.tools.namereferences, org.eclipse.papyrus.uml.tools.profile.definition, + org.eclipse.papyrus.uml.tools.profile.index, org.eclipse.papyrus.uml.tools.providers, org.eclipse.papyrus.uml.tools.service, org.eclipse.papyrus.uml.tools.util diff --git a/plugins/uml/tools/org.eclipse.papyrus.uml.tools/plugin.xml b/plugins/uml/tools/org.eclipse.papyrus.uml.tools/plugin.xml index 43ddfb3b9e6..06e8166d2f8 100644 --- a/plugins/uml/tools/org.eclipse.papyrus.uml.tools/plugin.xml +++ b/plugins/uml/tools/org.eclipse.papyrus.uml.tools/plugin.xml @@ -134,4 +134,17 @@ </dependsOn> </service> </extension> + <extension + point="org.eclipse.papyrus.infra.core.language"> + <provider> + <content-type + id="org.eclipse.uml2.uml"> + </content-type> + <language + id="org.eclipse.papyrus.uml.language" + version="2.5" + name="UML"> + </language> + </provider> + </extension> </plugin> diff --git a/plugins/uml/tools/org.eclipse.papyrus.uml.tools/src/org/eclipse/papyrus/uml/tools/profile/index/IProfileIndex.java b/plugins/uml/tools/org.eclipse.papyrus.uml.tools/src/org/eclipse/papyrus/uml/tools/profile/index/IProfileIndex.java new file mode 100644 index 00000000000..4bd4cb0af00 --- /dev/null +++ b/plugins/uml/tools/org.eclipse.papyrus.uml.tools/src/org/eclipse/papyrus/uml/tools/profile/index/IProfileIndex.java @@ -0,0 +1,59 @@ +/***************************************************************************** + * Copyright (c) 2015 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.uml.tools.profile.index; + +import java.util.Set; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.uml2.uml.Profile; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * <p> + * An index service that provides the profiles applied to resources in the workspace. + * It is expected that the index can provide the URIs of {@link Profile}s that are applied + * to some {@link org.eclipse.uml2.uml.Package Package} stored in a resource without the + * resource being loaded in the context of any particular resource set. The mechanism + * by which this is accomplished is not specified. + * </p> + * <p> + * A suitable implementation of this interface should be registered in the Papyrus Service Registry. + * </p> + */ +public interface IProfileIndex { + /** + * Queries whether the index covers the specified URI. For example, the index may cover + * only the workspace and not remote (e.g., HTTP) or filesystem (outside of the workspace) + * resources. + * + * @param umlResource + * the URI of a UML resource + * + * @return whether the index covers it + */ + boolean indexes(URI umlResource); + + /** + * Asynchronously obtains the profiles applied to packages in the specified UML resource. + * + * @param umlResource + * a resource storing UML model content + * + * @return a future set of URIs of the {@link Profile}s applied to packages within the UML resource. + * These are the URIs of the profile elements, themselves, not of the containing resource, because + * there may be any number of distinct profiles in a resource + */ + ListenableFuture<Set<URI>> getAppliedProfiles(URI umlResource); +} diff --git a/plugins/uml/tools/org.eclipse.papyrus.uml.tools/src/org/eclipse/papyrus/uml/tools/profile/index/ProfileLanguageProvider.java b/plugins/uml/tools/org.eclipse.papyrus.uml.tools/src/org/eclipse/papyrus/uml/tools/profile/index/ProfileLanguageProvider.java new file mode 100644 index 00000000000..5439ab70927 --- /dev/null +++ b/plugins/uml/tools/org.eclipse.papyrus.uml.tools/src/org/eclipse/papyrus/uml/tools/profile/index/ProfileLanguageProvider.java @@ -0,0 +1,186 @@ +/***************************************************************************** + * Copyright (c) 2015 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.uml.tools.profile.index; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExecutableExtension; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.emf.common.util.URI; +import org.eclipse.papyrus.infra.core.language.ILanguage; +import org.eclipse.papyrus.infra.core.language.ILanguageProvider; +import org.eclipse.papyrus.infra.core.language.ILanguageService; +import org.eclipse.papyrus.infra.core.services.BadStateException; +import org.eclipse.papyrus.infra.core.services.ServicesRegistry; +import org.eclipse.papyrus.uml.tools.Activator; +import org.eclipse.papyrus.uml.tools.model.UmlModel; +import org.eclipse.uml2.uml.Profile; +import org.osgi.framework.Bundle; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +/** + * <p> + * A language provider based on UML profiles. It uses the {@link IProfileIndex} to efficiently + * determine the profiles applied to UML resources and return languages based on the applied + * profiles that it finds. It must be configured using the parameterized + * <tt>{@literal <implementation>}</tt> element of the language extension point. Parameters + * are of the form: + * </p> + * <table> + * <tr> + * <th>Name</th> + * <th>Value</th> + * <th>Comments</th> + * </tr> + * <tr valign="top"> + * <td><tt>profile.<i>N</i></tt></td> + * <td>URI of a {@link Profile} element in a profile resource</td> + * <td>where <i>N</i> is an unique index 1, 2, etc. distinguishing the profile parameters</td> + * </tr> + * <tr valign="top"> + * <td><tt>language.<i>N</i></tt></td> + * <td>name of class implementing the {@link ILanguage} interface on the declaring plug-in's classpath</td> + * <td>where <i>N</i> matches the corresponding profile parameter</td> + * </tr> + * </table> + */ +public class ProfileLanguageProvider implements ILanguageProvider, IExecutableExtension { + private static final Pattern NAME_PATTERN = Pattern.compile("(?:(profile)|language)\\.(\\d+)"); //$NON-NLS-1$ + + private IConfigurationElement config; + private Map<URI, String> languageClasses; + + public ProfileLanguageProvider() { + super(); + } + + @Override + public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException { + this.config = config; + + if (!(data instanceof Map<?, ?>)) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Initialization parameter 'data' is not a Map in language provider from contributor " + config.getContributor().getName())); + } + + // Gather configuration parameters + @SuppressWarnings("unchecked") + Map<String, String> map = (Map<String, String>) data; + + Map<String, URI> profileURIs = Maps.newHashMap(); + Map<String, String> languages = Maps.newHashMap(); + for (Map.Entry<String, String> next : map.entrySet()) { + Matcher m = NAME_PATTERN.matcher(next.getKey()); + if (!m.find()) { + Activator.log.warn(String.format("Invalid profile language provider parameter name %s from contributor %s", next.getKey(), config.getContributor().getName())); //$NON-NLS-1$ + } else { + if (m.group(1) != null) { + // it's a profile + profileURIs.put(m.group(2), URI.createURI(next.getValue(), true)); + } else { + // it's a language + languages.put(m.group(2), next.getValue()); + } + } + } + + // Assemble the configuration parameters + languageClasses = Maps.newHashMap(); + for (Map.Entry<String, URI> next : profileURIs.entrySet()) { + String languageClass = languages.remove(next.getKey()); + if (languageClass == null) { + Activator.log.warn(String.format("Missing language class for profile %s from contributor %s", next.getValue(), config.getContributor().getName())); //$NON-NLS-1$ + } else { + languageClasses.put(next.getValue(), languageClass); + } + } + + // Any left over? + for (String next : languages.values()) { + Activator.log.warn(String.format("Missing profile URI for language class %s from contributor %s", next, config.getContributor().getName())); //$NON-NLS-1$ + } + } + + @Override + public Iterable<ILanguage> getLanguages(ILanguageService languageService, URI modelURI, boolean uriHasFileExtension) { + Set<ILanguage> result = Sets.newHashSet(); + + if (!uriHasFileExtension) { + modelURI = modelURI.appendFileExtension(UmlModel.UML_FILE_EXTENSION); + } + + try { + IProfileIndex index = null; + ServicesRegistry registry = languageService.getAdapter(ServicesRegistry.class); + if (registry != null) { + try { + index = registry.getService(IProfileIndex.class); + } catch (BadStateException e) { + // The ModelSet is started before the rest of the registry, and it doesn't know about the profile + // index service to start it. So, we must start the profile service explicitly + registry.startServicesByClassKeys(IProfileIndex.class); + index = registry.getService(IProfileIndex.class); + } + } + + if (index != null) { + Set<URI> profiles = index.getAppliedProfiles(modelURI).get(10L, TimeUnit.MINUTES); + for (URI next : profiles) { + ILanguage language = getLanguage(next); + if (language != null) { + result.add(language); + } + } + } + } catch (Exception e) { + Activator.log.error(String.format("Failed to access profile index for resource %s", modelURI), e); //$NON-NLS-1$ + } + + return result; + } + + protected synchronized ILanguage getLanguage(URI profileURI) { + ILanguage result = null; + + String className = languageClasses.get(profileURI); + if (className != null) { + Bundle bundle = Platform.getBundle(config.getContributor().getName()); + if (bundle != null) { + try { + Class<?> class_ = bundle.loadClass(className); + if ((class_ == null) || !ILanguage.class.isAssignableFrom(class_)) { + languageClasses.remove(profileURI); // Don't try this again + Activator.log.error(String.format("Not a language class for profile %s in contributor %s: %s", profileURI, config.getContributor().getName(), className), null); //$NON-NLS-1$ + } else { + result = class_.asSubclass(ILanguage.class).newInstance(); + } + } catch (Exception e) { + languageClasses.remove(profileURI); // Don't try this again + Activator.log.error(String.format("Failed to instantiate language class %s for profile %s in contributor %s", className, profileURI, config.getContributor().getName()), e); //$NON-NLS-1$ + } + } + } + + return result; + } +} |