| /******************************************************************************* |
| * Copyright (c) 2011, 2018 Willink Transformations and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * E.D.Willink - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ocl.pivot.internal.resource; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import javax.xml.parsers.SAXParser; |
| |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.resources.IResourceDeltaVisitor; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.emf.common.EMFPlugin; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.plugin.EcorePlugin; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.resource.URIConverter; |
| import org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.ocl.pivot.internal.compatibility.EMF_2_9; |
| import org.eclipse.ocl.pivot.resource.ProjectManager; |
| import org.eclipse.ocl.pivot.utilities.NameUtil; |
| |
| /** |
| * ProjectMap extends {@link ProjectManager} to support polymorphic access in either plugin or standalone environments |
| * to EMF resources and EPackages. |
| * |
| *<h4>Plugin Environment</h4> |
| * |
| * A resolvable location is perhaps <tt>platform:/plugin/org.antlr.runtime/</tt> for a bundle or |
| * <tt>platform:/resource/org.eclipse.ocl.domain/</tt> for an open project. |
| * <p> |
| * {@link #getProjectDescriptors()} returns a map of project names, but not bundle names, to resolvable location. |
| * <p> |
| * {@link #initializePackageRegistry(ResourceSet)} augments the default EMF startup in a plugin environment |
| * whereby the global package registry acquires a registration for each namespace URI |
| * (e.g. <tt>http://www.eclipse.org/emf/2002/Ecore</tt>) defined by the |
| * <tt>org.eclipse.emf.ecore.generated_package</tt> extension point in plugins. |
| * The standard reguistration is auugmented where appropriate, by two further registrations for |
| * the project URI (e.g. <tt>platform:/resource/org.eclipse.emf.ecore/model/Ecore.ecore</tt>) and the plugin URI |
| * (e.g. <tt>platform:/plugin/org.eclipse.emf.ecore/model/Ecore.ecore</tt>). These extra registrations |
| * derived from the <tt>genPackages.ecorePackage</tt> referenced by the <tt>genmodel</tt> |
| * <tt>org.eclipse.emf.ecore.generated_package</tt> declarations ensure that all three URIs resolve to |
| * the same Resource eliminating most opportunities for meta-model schizophrenia. |
| * <p> |
| * {@link #initializePlatformResourceMap(boolean)} does nothing since the standard EMF Platform URI Handler |
| * can open platform resources directly. |
| * <p> |
| * {@link #initializeGenModelLocationMap(boolean)} does nothing, since the standard EMF startup in a |
| * plugin environment populates the {@link EcorePlugin#getEPackageNsURIToGenModelLocationMap()}. |
| * <p> |
| * {@link #initializeURIMap(ResourceSet)} installs explicit URI mappings into the {@link URIConverter} |
| * so that for each project so that both <tt>platform:/resource/project</tt> and |
| * <tt>platform:/plugin/<i>project</i></tt> reference <tt>platform:/resource/<i>project</i></tt>. An additional |
| * backstop URI mapping redirects <tt>platform:/resource</tt> to <tt>platform:/plugin</tt>. |
| * <p> |
| * The explicit mapping ensures that projects are accessible as either |
| * <tt>platform:/resource/<i>project</i></tt> or <tt>platform:/plugin/<i>project</i></tt>. |
| * The backstop mapping ensures that plugins, that are not occluded by projects, are |
| * accessible as <tt>platform:/plugin/<i>project</i></tt> or |
| * <tt>platform:/resource/<i>project</i></tt>, without needing to create an |
| * explicit URI map entry for each of the many hundreds of bundles in typical use. |
| * |
| * A global ProjectMap tracks workpsace project changes to synchronize the known open projects and the list |
| * of IProjectDescriptor. This ensures that when the global ProjectMap initializes a ResourceSet it uses |
| * the prevailing open projects. It does not update previously initialized ResourceSets. |
| */ |
| public class ProjectMap extends StandaloneProjectMap implements IResourceChangeListener, IResourceDeltaVisitor |
| { |
| public static class ProjectDescriptor extends StandaloneProjectMap.ProjectDescriptor |
| { |
| public ProjectDescriptor(@NonNull ProjectMap projectMap, @NonNull String name, @NonNull URI locationURI) { |
| super(projectMap, name, locationURI); |
| } |
| |
| @Override |
| protected @NonNull URI getResolvedGenModelURI(@NonNull IResourceDescriptor resourceDescriptor) { |
| URI genModelURI = resourceDescriptor.getGenModelURI(); |
| return genModelURI.resolve(getLocationURI()); |
| } |
| |
| @Override |
| public void initializeURIMap(@NonNull Map<URI, URI> uriMap) { |
| if (!EMFPlugin.IS_ECLIPSE_RUNNING) { |
| super.initializeURIMap(uriMap); |
| } |
| else { |
| if (locationURI.isPlatformResource()) { |
| URI resourceURI = locationURI; |
| URI pluginURI = getPlatformPluginURI(); |
| uriMap.put(resourceURI, resourceURI); |
| uriMap.put(pluginURI, resourceURI); |
| if (PROJECT_MAP_ADD_URI_MAP.isActive()) { |
| PROJECT_MAP_ADD_URI_MAP.println(resourceURI + " => " + resourceURI); |
| PROJECT_MAP_ADD_URI_MAP.println(pluginURI + " => " + resourceURI); |
| } |
| } |
| } |
| } |
| } |
| |
| public static @Nullable StandaloneProjectMap findAdapter(@NonNull ResourceSet resourceSet) { |
| if (!EcorePlugin.IS_ECLIPSE_RUNNING) { |
| return StandaloneProjectMap.findAdapter(resourceSet); |
| } |
| return (StandaloneProjectMap) EcoreUtil.getAdapter(resourceSet.eAdapters(), ProjectMap.class); |
| } |
| |
| public static synchronized @NonNull StandaloneProjectMap getAdapter(@NonNull ResourceSet resourceSet) { |
| if (!EcorePlugin.IS_ECLIPSE_RUNNING) { |
| return StandaloneProjectMap.getAdapter(resourceSet); |
| } |
| StandaloneProjectMap adapter = findAdapter(resourceSet); |
| if (adapter == null) { |
| adapter = new ProjectMap(false); |
| adapter.initializeResourceSet(resourceSet); |
| } |
| return adapter; |
| } |
| |
| /** |
| * non-null visitor when this ProjectMap is listening to resource chnages in the workspace. |
| */ |
| private IResourceDeltaVisitor visitor = null; |
| |
| public ProjectMap(boolean isGlobal) { |
| super(isGlobal); |
| } |
| |
| @Override |
| protected @NonNull IProjectDescriptor createProjectDescriptor(@NonNull String projectName, @NonNull URI locationURI) { |
| return new ProjectDescriptor(this, projectName, locationURI); |
| } |
| |
| /** |
| * @since 1.7 |
| */ |
| public void dispose() { |
| // super.dispose() { |
| if (visitor != null) { |
| ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); |
| visitor = null; |
| } |
| } |
| |
| @Override |
| public @Nullable URI getLocation(@NonNull String projectName) { |
| URI uri = super.getLocation(projectName); |
| if ((uri == null) && EMFPlugin.IS_ECLIPSE_RUNNING) { |
| uri = URI.createPlatformPluginURI("/" + projectName + "/", true); |
| } |
| return uri; |
| } |
| |
| /* @Override |
| public synchronized void initializeGenModelLocationMap(boolean force) { |
| if (force || ((nsURI2package == null) && !EMFPlugin.IS_ECLIPSE_RUNNING)) { |
| super.initializeGenModelLocationMap(force); |
| } |
| } */ |
| |
| @Override |
| public synchronized void initializePlatformResourceMap(boolean force) { |
| if (force || (!initializedPlatformResourceMap && !EMFPlugin.IS_ECLIPSE_RUNNING)) { |
| super.initializePlatformResourceMap(force); |
| } |
| } |
| |
| @Override |
| public void initializeResourceSet(@Nullable ResourceSet resourceSet) { |
| assert (resourceSet != null) || !EMFPlugin.IS_ECLIPSE_RUNNING; // Diagnosing for Bug 544187. |
| super.initializeResourceSet(resourceSet); |
| } |
| |
| @Override |
| public synchronized void initializeURIMap(@Nullable ResourceSet resourceSet) { |
| super.initializeURIMap(resourceSet); |
| if (EMFPlugin.IS_ECLIPSE_RUNNING) { |
| Map<URI, URI> uriMap = getURIMap(resourceSet); |
| URI resourceURI = URI.createPlatformResourceURI("/", true); |
| URI pluginURI = URI.createPlatformPluginURI("/", true); |
| uriMap.put(resourceURI, pluginURI); |
| } |
| } |
| |
| @Override |
| public boolean isAdapterForType(Object type) { |
| return (type instanceof Class<?>) && ((Class<?>)type).isAssignableFrom(ProjectMap.class); |
| } |
| |
| /** |
| * Internal call-back for a resource change visits the delta to respond to chnaged open/closed projects. |
| * Changes are synchronized on this. |
| */ |
| @Override |
| public void resourceChanged(IResourceChangeEvent event) { |
| try { |
| synchronized (this) { |
| event.getDelta().accept(visitor); |
| } |
| } catch (CoreException e) { |
| // log.error(e.getMessage(), e); |
| } |
| } |
| |
| @Override |
| protected void scanClassPath(@NonNull Map<@NonNull String, @NonNull IProjectDescriptor> projectDescriptors, @NonNull SAXParser saxParser) { |
| if (!EMFPlugin.IS_ECLIPSE_RUNNING) { |
| super.scanClassPath(projectDescriptors, saxParser); |
| } |
| else { |
| // scanBundles(); -- no need to scan hundreds of bundles when a single URI map entry will handle them all. |
| scanProjects(projectDescriptors); |
| scanGenModels(saxParser); |
| } |
| } |
| |
| /* protected void scanBundles() { |
| for (IBundleGroupProvider bundleGroupProvider : Platform.getBundleGroupProviders()) { |
| for (IBundleGroup bundleGroup : bundleGroupProvider.getBundleGroups()) { |
| for (Bundle bundle : bundleGroup.getBundles()) { |
| String bundleName = bundle.getSymbolicName(); |
| String projectKey = "/" + bundleName + "/"; |
| project2location.put(bundleName, URI.createPlatformPluginURI(projectKey, true)); |
| } |
| } |
| } |
| } */ |
| |
| protected void scanGenModels(@NonNull SAXParser saxParser) { |
| URIConverter uriConverter = new ExtensibleURIConverterImpl(); |
| Map<String, URI> ePackageNsURIToGenModelLocationMap = EMF_2_9.EcorePlugin.getEPackageNsURIToGenModelLocationMap(false); |
| Map<@NonNull URI, @NonNull Map<@NonNull URI, @Nullable String>> genModel2nsURI2className = new HashMap<>(); |
| for (String ePackageNsURI : ePackageNsURIToGenModelLocationMap.keySet()) { |
| URI genModelURI = ePackageNsURIToGenModelLocationMap.get(ePackageNsURI); |
| if (genModelURI != null) { |
| Map<@NonNull URI, @Nullable String> nsURI2className = genModel2nsURI2className.get(genModelURI); |
| if (nsURI2className == null) { |
| nsURI2className = new HashMap<>(); |
| genModel2nsURI2className.put(genModelURI, nsURI2className); |
| } |
| nsURI2className.put(URI.createURI(ePackageNsURI), null); |
| } |
| } |
| for (@NonNull URI genModelURI : genModel2nsURI2className.keySet()) { |
| if (genModelURI.isPlatformPlugin()) { |
| IProjectDescriptor projectDescriptor = getProjectDescriptorInternal(genModelURI); |
| Map<@NonNull URI, @Nullable String> nsURI2className = genModel2nsURI2className.get(genModelURI); |
| assert nsURI2className != null; |
| @NonNull URI deresolvedGenModelURI = genModelURI.deresolve(projectDescriptor.getLocationURI(), true, true, true); |
| @NonNull String genModelString = String.valueOf(deresolvedGenModelURI); |
| IResourceDescriptor resourceDescriptor = projectDescriptor.createResourceDescriptor(genModelString, nsURI2className); |
| GenModelReader genModelReader = new GenModelReader(resourceDescriptor); |
| InputStream inputStream = null; |
| try { |
| inputStream = uriConverter.createInputStream(genModelURI); |
| saxParser.parse(inputStream, genModelReader); |
| } catch (Exception e) { |
| logException("Failed to parse '" + genModelURI + "'", e); |
| } finally { |
| try { |
| if (inputStream != null) { |
| inputStream.close(); |
| } |
| } catch (IOException e) {} |
| } |
| } |
| } |
| } |
| |
| private void refreshProject(@NonNull Map<String, IProjectDescriptor> projectDescriptors, @NonNull IProject project) { |
| // Map<String, IProjectDescriptor> projectDescriptors = getProjectDescriptors(); |
| // if (projectDescriptors != null) { |
| @SuppressWarnings("null")@NonNull String projectName = project.getName(); |
| boolean wasOpen = projectDescriptors.containsKey(projectName); |
| boolean isOpen = project.isOpen(); |
| if (wasOpen) { |
| if (!isOpen) { |
| @SuppressWarnings("unused") |
| IProjectDescriptor projectDescriptor = projectDescriptors.remove(projectName); |
| // projectDescriptor.dispose(); |
| System.out.println(NameUtil.debugSimpleName(this) + " closing " + projectName); |
| } |
| else { |
| System.out.println(NameUtil.debugSimpleName(this) + " still open " + projectName); |
| } |
| } |
| else { |
| if (isOpen) { |
| System.out.println(NameUtil.debugSimpleName(this) + " opening " + projectName); |
| String projectKey = "/" + projectName + "/"; |
| @NonNull URI platformResourceURI = URI.createPlatformResourceURI(projectKey, true); |
| IProjectDescriptor projectDescriptor = createProjectDescriptor(projectName, platformResourceURI); |
| projectDescriptors.put(projectName, projectDescriptor); |
| } |
| else { |
| System.out.println(NameUtil.debugSimpleName(this) + " still closed " + projectName); |
| } |
| } |
| } |
| |
| protected void scanProjects(@NonNull Map<String, IProjectDescriptor> projectDescriptors) { |
| IWorkspace workspace = ResourcesPlugin.getWorkspace(); |
| if (isGlobal && (visitor == null)) { // Lazily install listening for a/the global ProjectMap |
| visitor = this; |
| workspace.addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); |
| } |
| for (IProject project : workspace.getRoot().getProjects()) { |
| if (project != null) { |
| refreshProject(projectDescriptors, project); |
| } |
| } |
| } |
| |
| /** |
| * Internal cCall-back from a resoyrceChanged() add/s/removes IProjectDescriptor for newly opened/closed projects. |
| */ |
| @Override |
| public boolean visit(IResourceDelta delta) throws CoreException { |
| IResource resource = delta.getResource(); |
| if (resource instanceof IWorkspaceRoot) |
| return true; |
| if (resource instanceof IProject) { |
| Map<@NonNull String, @NonNull IProjectDescriptor> projectDescriptors2 = getProjectDescriptors(); |
| assert projectDescriptors2 != null; |
| refreshProject(projectDescriptors2, (IProject)resource); |
| } |
| return false; |
| } |
| } |