/** * Copyright (c) 2002-2010 IBM Corporation 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: * IBM - Initial API and implementation */ package org.eclipse.emf.ecore.plugin; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.osgi.framework.BundleContext; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.Platform; import org.eclipse.emf.common.EMFPlugin; import org.eclipse.emf.common.util.ResourceLocator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.common.util.WrappedException; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.impl.EPackageRegistryImpl; /** * A collection of platform-neutral static utilities * as well as Eclipse support utilities. */ public class EcorePlugin extends EMFPlugin { /** * The singleton instance of the plugin. */ public static final EcorePlugin INSTANCE = new EcorePlugin(); /** * Creates the singleton instance. */ private EcorePlugin() { super(new ResourceLocator[] {}); } @Override public ResourceLocator getPluginResourceLocator() { return plugin; } /** * Returns the platform resource map. * <p> * This map is from {@link String} to {@link URI}. * It is the logical equivalent of the map implied by an {@link IWorkspaceRoot}: * I.e., each entry in the map corresponds to * an {@link org.eclipse.core.resources.IProject} * that has a {@link org.eclipse.core.resources.IResource#getName name} * and a location {@link org.eclipse.core.resources.IResource#getLocation location}; * the name is the key * and the location, interpreted as a {@link URI#createFileURI file URI}, is the value. * This map is used to {@link #resolvePlatformResourcePath resolve} a platform resource path, * and thereby supports relocatable projects in a manner that is transparently the same as an Eclipse workspace. * </p> * @return the platform resource map. * @see #resolvePlatformResourcePath */ public static Map<String, URI> getPlatformResourceMap() { if (platformResourceMap == null) { platformResourceMap = new HashMap<String, URI>(); } return platformResourceMap; } /** * Resolves a platform resource path of the form <code>"/project/path"</code> * against the platform resource map. * <p> * The first segment of the path, i.e., the <em>project name</em>, * is used to get a URI from the {@link #getPlatformResourceMap() map}. * If a URI results, the remaining segments are {@link URI#resolve(URI) resolved} against it * and that is the result. * Otherwise, the result is <code>null</code>. * For example, given this mapping *<pre> * EcoreUtil.getPlatformResourceMap().put * ("project", URI.createURI("file:///C:/location/")); *</pre> * the following transformation would result: *<pre> * /project/directory/file * -> * file:///C:/location/directory/file *</pre> * </p> * @return the resolved URI or <code>null</code>. */ public static URI resolvePlatformResourcePath(String platformResourcePath) { if (platformResourceMap != null) { int index = platformResourcePath.indexOf("/", 1); String rootContainerName = platformResourcePath.substring(1, index); String relativeName = platformResourcePath.substring(index + 1); URI rootContainerLocation = getPlatformResourceMap().get(rootContainerName); if (rootContainerLocation != null) { return URI.createURI(relativeName).resolve(rootContainerLocation); } } return null; } /** * Handles recognized platform resource arguments and returns the stripped result. * <p> * Recognized arguments are of this form: *<pre> * -platformResource ( <project-name> <file-or-URI> )+ *</pre> * E.g., This these arguments *<pre> * -platformResource project file:///C:/location/ *</pre> * will produce this effect: *<pre> * EcoreUtil.getPlatformResourceMap().put * ("project", URI.createURI("file:///C:/location/")); *</pre> * This mechanism supports relocatable projects outside of Eclipse. * </p> * @param arguments an array of "command line" options. * @return the arguments stripped of those recognized as platform resource options. */ public static String [] handlePlatformResourceOptions(String [] arguments) { getPlatformResourceMap(); for (int i = 0; i < arguments.length; ++i) { if (arguments[i].equalsIgnoreCase("-platformResource")) { int start = i; while (++i < arguments.length && !arguments[i].startsWith("-")) { String rootContainerName = arguments[i]; if (++i < arguments.length) { String rootContainerLocation = arguments[i]; // This let's us test whether the string exists as a file. // If not, we try as a URI. // URI uri; File file = new File(rootContainerLocation); if (file.isDirectory() || !file.exists() && file.getParent() != null && file.getParentFile().isDirectory()) { try { file = file.getCanonicalFile(); } catch (IOException exception) { throw new WrappedException(exception); } uri = URI.createFileURI(file.toString() + "/"); } else { uri = URI.createURI(rootContainerLocation); } platformResourceMap.put(rootContainerName, uri); } } String [] remainingArguments = new String [arguments.length - (i - start)]; System.arraycopy(arguments, 0, remainingArguments, 0, start); System.arraycopy(arguments, i, remainingArguments, start, arguments.length - i); return remainingArguments; } } return arguments; } /** * Returns a map from {@link EPackage#getNsURI() package namespace URI} (represented as a String) * to the location of the GenModel containing a GenPackage for the package (represented as a {@link URI URI}). * @return a map from package namespace to GenModel location. */ public static Map<String, URI> getEPackageNsURIToGenModelLocationMap() { if (ePackageNsURIToGenModelLocationMap == null) { ePackageNsURIToGenModelLocationMap = new HashMap<String, URI>(); } return ePackageNsURIToGenModelLocationMap; } /** * Computes a map from <code>platform:/resource/<plugin-location>/</code> {@link URI} to * <code>platform:/plugin/<plugin-id>/</code> URI * for each URI in the collection of the form <code>platform:/plugin/<plugin-id>/...</code>. * This allows each plugin to be {@link org.eclipse.emf.ecore.resource.URIConverter#getURIMap() treated} * as if it were a project in the workspace. * If the workspace already contains a project for the plugin location, no mapping is produced. * @param uris a collections of {@link URI}s. * @return a map from platform resource URI to platform plugin URI. */ public static Map<URI, URI> computePlatformResourceToPlatformPluginMap(Collection<URI> uris) { Map<URI, URI> result = new HashMap<URI, URI>(); IWorkspaceRoot root = getWorkspaceRoot(); if (root != null) { for (URI uri : uris) { if (uri.isPlatformPlugin()) { String pluginID = uri.segment(1); if (!root.getProject(pluginID).isOpen()) { result.put(URI.createPlatformResourceURI(pluginID + "/", false), URI.createPlatformPluginURI(pluginID + "/", false)); } } } } return result; } private static Pattern bundleSymbolNamePattern; private static byte [] NO_BYTES = new byte [0]; /** * Computes a map from <code>platform:/plugin/<plugin-id>/</code> {@link URI} to * <code>platform:/resource/<plugin-location>/</code> URI * for each plugin project in the workspace. * This allows each plugin from the runtime to be {@link org.eclipse.emf.ecore.resource.URIConverter#getURIMap() redirected} * to its active version in the workspace. * @return a map from plugin URIs to resource URIs. * @see org.eclipse.emf.ecore.resource.URIConverter#getURIMap() * @see URI */ public static Map<URI, URI> computePlatformPluginToPlatformResourceMap() { Map<URI, URI> result = new HashMap<URI, URI>(); IWorkspaceRoot root = getWorkspaceRoot(); if (root != null) { IProject [] projects = root.getProjects(); if (projects != null) { String pluginID = null; class Handler extends DefaultHandler { public String pluginID; @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if ("".equals(uri) && "plugin".equals(localName)) { pluginID = attributes.getValue("id"); } throw new SAXException("Done"); } } Handler handler = new Handler(); SAXParserFactory parserFactory= SAXParserFactory.newInstance(); parserFactory.setNamespaceAware(true); SAXParser parser = null; try { parser = parserFactory.newSAXParser(); } catch (Exception exception) { INSTANCE.log(exception); } if (bundleSymbolNamePattern == null) { bundleSymbolNamePattern = Pattern.compile("^\\s*Bundle-SymbolicName\\s*:\\s*([^\\s;]*)\\s*(;.*)?$", Pattern.MULTILINE); } byte [] bytes = NO_BYTES; for (int i = 0, size = projects.length; i < size; ++i) { IProject project = projects[i]; if (project.isOpen()) { pluginID = null; IFile manifest = project.getFile("META-INF/MANIFEST.MF"); if (manifest.exists()) { InputStream inputStream = null; try { inputStream = manifest.getContents(); int available = inputStream.available(); if (bytes.length < available) { bytes = new byte [available]; } inputStream.read(bytes); String contents = new String(bytes, "UTF-8"); Matcher matcher = bundleSymbolNamePattern.matcher(contents); if (matcher.find()) { pluginID = matcher.group(1); } } catch (Exception exception) { INSTANCE.log(exception); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException exception) { INSTANCE.log(exception); } } } } else if (parser != null) { final IFile plugin = project.getFile("plugin.xml"); if (plugin.exists()) { try { parser.parse(new InputSource(plugin.getContents()), handler); } catch (Exception exception) { if (handler.pluginID != null) { pluginID = handler.pluginID; } else { INSTANCE.log(exception); } } } } if (pluginID != null) { URI platformPluginURI = URI.createPlatformPluginURI(pluginID + "/", false); URI platformResourceURI = URI.createPlatformResourceURI(project.getName() + "/", true); result.put(platformPluginURI, platformResourceURI); } } } } } return result; } /** * Computes a map so that plugins in the workspace will override those in the environment * and so that plugins with Ecore and GenModels will look like projects in the workspace. * It's implemented like this: *<pre> * Map result = new HashMap(); * result.putAll(computePlatformPluginToPlatformResourceMap()); * result.putAll(computePlatformResourceToPlatformPluginMap(new HashSet(EcorePlugin.getEPackageNsURIToGenModelLocationMap().values()))); * return result; *</pre> * @return computes a map so that plugins in the workspace will override those in the environment * and so that plugins with Ecore and GenModels will look like projects in the workspace. * @see org.eclipse.emf.ecore.resource.URIConverter#getURIMap() * @see URI * @see #computePlatformPluginToPlatformResourceMap() * @see #computePlatformResourceToPlatformPluginMap(Collection) */ public static Map<URI, URI> computePlatformURIMap() { Map<URI, URI> result = new HashMap<URI, URI>(); result.putAll(computePlatformPluginToPlatformResourceMap()); result.putAll(computePlatformResourceToPlatformPluginMap(new HashSet<URI>(EcorePlugin.getEPackageNsURIToGenModelLocationMap().values()))); return result; } /** * The platform resource map. * @see #getPlatformResourceMap */ private static Map<String, URI> platformResourceMap; /** * The map from package namespace URIs to the location of the GenModel for that package. * @see #getPlatformResourceMap */ private static Map<String, URI> ePackageNsURIToGenModelLocationMap; /** * A plugin implementation that handles Ecore plugin registration. * @see #startup() */ static public class Implementation extends EclipsePlugin { /** * Creates the singleton instance. */ public Implementation() { super(); plugin = this; } /** * Starts up this plugin by reading some extensions and populating the relevant registries. * <p> * The {@link org.eclipse.emf.ecore.EPackage.Registry#INSTANCE global} package registry * is populated by plugin registration of the form: *<pre> * <extension point="org.eclipse.emf.ecore.generated_package" > * <package uri="http://www.example.org/abc/Abc.ecore" class="org.example.abc.AbcPackage"/> * <extension> *</pre> * </p> * The URI is arbitrary but an absolute URI is recommended. * Provision for access to the serialized model via <code>"http:"</code> is encouraged. * <p> * The {@link org.eclipse.emf.ecore.resource.Resource.Factory.Registry#INSTANCE global} resource factory registry's * {@link org.eclipse.emf.ecore.resource.Resource.Factory.Registry#getExtensionToFactoryMap() extension} map * is populated by plugin registration of the form: *<pre> * <extension point="org.eclipse.emf.ecore.extension_parser"> * <parser type="abc" class="org.example.abc.util.AbcResourceFactoryImpl"/> * <extension> *</pre> * </p> * <p> * The {@link org.eclipse.emf.ecore.resource.Resource.Factory.Registry#INSTANCE global} resource factory registry's * {@link org.eclipse.emf.ecore.resource.Resource.Factory.Registry#getProtocolToFactoryMap() protocol} map * is populated by plugin registration of the form: *<pre> * <extension point="org.eclipse.emf.ecore.protocol_parser" > * <parser protocolName="abc" class="org.example.abc.util.AbcResourceFactoryImpl"/> * <extension> *</pre> * </p> * <p> * The {@link org.eclipse.emf.ecore.resource.URIConverter#URI_MAP global} URI map * is populated by plugin registration of the form: *<pre> * <extension point="org.eclipse.emf.ecore.uri_mapping" > * <mapping source="//special/" target="special/"/> * <extension> *</pre> * If the target is relative, it is resolved against the plugin's installed location, * resulting in a URI of the form: *<pre> * platform:/plugin/plugin-name_1.2.3/... *</pre> * The above registration would map *<pre> * //special/a/b.c *</pre> * to *<pre> * platform:/plugin/plugin-name_1.2.3/special/a/b.c *</pre> * </p> * @throws Exception if there is a show stopping problem. */ @Override public void start(BundleContext context) throws Exception { super.start(context); new RegistryReader (Platform.getExtensionRegistry(), EcorePlugin.getPlugin().getBundle().getSymbolicName(), PACKAGE_REGISTRY_IMPLEMENTATION_PPID) { IConfigurationElement previous; @Override protected boolean readElement(IConfigurationElement element) { if (element.getName().equals("registry")) { String implementationClass = element.getAttribute("class"); if (implementationClass == null) { logMissingAttribute(element, "class"); } else { if (defaultRegistryImplementation != null) { if (previous != null) { log("Both '" + previous.getContributor().getName() + "' and '" + element.getContributor().getName() + "' register a package registry implementation"); } if (defaultRegistryImplementation instanceof EPackageRegistryImpl.Delegator) { return false; } } try { defaultRegistryImplementation = (EPackage.Registry)element.createExecutableExtension("class"); previous = element; } catch (CoreException exception) { log(exception); } return true; } } return false; } }.readRegistry(); new GeneratedPackageRegistryReader(getEPackageNsURIToGenModelLocationMap()).readRegistry(); new DynamicPackageRegistryReader().readRegistry(); new FactoryOverrideRegistryReader().readRegistry(); new ExtensionParserRegistryReader().readRegistry(); new ProtocolParserRegistryReader().readRegistry(); new ContentParserRegistryReader().readRegistry(); new ContentHandlerRegistryReader().readRegistry(); new URIMappingRegistryReader().readRegistry(); new ValidationDelegateRegistryReader().readRegistry(); new SettingDelegateFactoryRegistryReader().readRegistry(); new InvocationDelegateFactoryRegistryReader().readRegistry(); new QueryDelegateFactoryRegistryReader().readRegistry(); new ConversionDelegateFactoryRegistryReader().readRegistry(); } } /** * The default registry implementation singleton. */ private static EPackage.Registry defaultRegistryImplementation; /** * Returns the default registry implementation singleton. * @return the default registry implementation singleton. */ public static EPackage.Registry getDefaultRegistryImplementation() { return defaultRegistryImplementation; } /** * Returns the Eclipse plugin singleton. * @return the plugin singleton. */ public static Implementation getPlugin() { return plugin; } /** * The plugin singleton */ private static Implementation plugin; /** * The workspace root. * @see #getWorkspaceRoot */ private static IWorkspaceRoot workspaceRoot; /** * Returns the workspace root, or <code>null</code>, if the runtime environment is stand-alone. * @return the workspace root, or <code>null</code>. */ public static IWorkspaceRoot getWorkspaceRoot() { if (workspaceRoot == null && IS_RESOURCES_BUNDLE_AVAILABLE && System.getProperty("org.eclipse.emf.ecore.plugin.EcorePlugin.doNotLoadResourcesPlugin") == null) { workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); } return workspaceRoot; } public static final String DYNAMIC_PACKAGE_PPID = "dynamic_package"; public static final String GENERATED_PACKAGE_PPID = "generated_package"; public static final String FACTORY_OVERRIDE_PPID = "factory_override"; public static final String EXTENSION_PARSER_PPID = "extension_parser"; public static final String PROTOCOL_PARSER_PPID = "protocol_parser"; public static final String CONTENT_PARSER_PPID = "content_parser"; public static final String CONTENT_HANDLER_PPID = "content_handler"; public static final String SCHEME_PARSER_PPID = "scheme_parser"; public static final String URI_MAPPING_PPID = "uri_mapping"; public static final String PACKAGE_REGISTRY_IMPLEMENTATION_PPID = "package_registry_implementation"; public static final String VALIDATION_DELEGATE_PPID = "validation_delegate"; public static final String SETTING_DELEGATE_PPID = "setting_delegate"; public static final String INVOCATION_DELEGATE_PPID = "invocation_delegate"; public static final String QUERY_DELEGATE_PPID = "query_delegate"; public static final String CONVERSION_DELEGATE_PPID = "conversion_delegate"; }