blob: d030eb10efcd716f3836b52e2d4ac491d8e2a3f8 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2008-2015 See4sys, BMW Car IT, itemis 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:
* See4sys - Initial API and implementation
* BMW Car IT - [374883] Improve handling of out-of-sync workspace files during descriptor initialization
* itemis - [409458] Enhance ScopingResourceSetImpl#getEObjectInScope() to enable cross-document references between model files with different metamodels
* itemis - [409510] Enable resource scope-sensitive proxy resolutions without forcing metamodel implementations to subclass EObjectImpl
* itemis - [420792] Sphinx is not able to load resources that are registered in the EMF package registry
* itemis - [421205] Model descriptor registry does not return correct model descriptor for (shared) plugin resources
* itemis - [442342] Sphinx doen't trim context information from proxy URIs when serializing proxyfied cross-document references
* itemis - [475954] Proxies with fragment-based proxy URIs may get resolved across model boundaries
*
* </copyright>
*/
package org.eclipse.sphinx.emf.resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor;
import org.eclipse.sphinx.emf.metamodel.MetaModelDescriptorRegistry;
import org.eclipse.sphinx.emf.model.IModelDescriptor;
import org.eclipse.sphinx.emf.model.ModelDescriptorRegistry;
import org.eclipse.sphinx.emf.scoping.DefaultResourceScope;
import org.eclipse.sphinx.emf.scoping.IResourceScope;
import org.eclipse.sphinx.emf.util.EcorePlatformUtil;
import org.eclipse.sphinx.emf.util.EcoreResourceUtil;
/**
* A default implementation of the {@link ScopingResourceSet} interface.
*/
public class ScopingResourceSetImpl extends ExtendedResourceSetImpl implements ScopingResourceSet {
private IResourceScope outsideWorkspaceScope;
/**
* Default constructor.
*/
public ScopingResourceSetImpl() {
}
/**
* Returns the {@link IResourceScope resource scope} used to encompass all resources that are located outside of the
* workspace.
*
* @return The scope used to encompass all resources that are located outside of the workspace.
*/
protected IResourceScope getOutsideWorkspaceScope() {
if (outsideWorkspaceScope == null) {
outsideWorkspaceScope = createOutsideWorkspaceScope();
}
return outsideWorkspaceScope;
}
/**
* Creates the {@link IResourceScope resource scope} used to encompass all resources that are located outside of the
* workspace.
*
* @return The scope used to encompass all resources that are located outside of the workspace.
*/
protected IResourceScope createOutsideWorkspaceScope() {
return new DefaultResourceScope() {
@Override
public boolean belongsTo(Resource resource, boolean includeReferencedScopes) {
Assert.isNotNull(resource);
return !resource.getURI().isPlatform() || isShared(resource);
}
@Override
public boolean isShared(Resource resource) {
Assert.isNotNull(resource);
return resource.getURI().isPlatformPlugin();
}
};
}
/*
* @see org.eclipse.sphinx.emf.resource.ScopingResourceSet#getResourcesInModel(java.lang.Object)
*/
@Override
public List<Resource> getResourcesInModel(Object contextObject) {
return getResourcesInScope(contextObject, true, false);
}
/*
* @see org.eclipse.sphinx.emf.resource.ScopingResourceSet#getResourcesInModel(java.lang.Object, boolean)
*/
@Override
public List<Resource> getResourcesInModel(Object contextObject, boolean includeReferencedScopes) {
return getResourcesInScope(contextObject, includeReferencedScopes, false);
}
/*
* @see org.eclipse.sphinx.emf.resource.ScopingResourceSet#getResourcesInScope(java.lang.Object)
*/
@Override
public List<Resource> getResourcesInScope(Object contextObject) {
return getResourcesInScope(contextObject, true, true);
}
/*
* @see org.eclipse.sphinx.emf.resource.ScopingResourceSet#getResourcesInScope(java.lang.Object, boolean)
*/
@Override
public List<Resource> getResourcesInScope(Object contextObject, boolean includeReferencedScopes) {
return getResourcesInScope(contextObject, includeReferencedScopes, true);
}
/**
* Retrieves the {@link Resource resource}s contained by this {@link ResourceSet resource set} that belong to the
* {@link IResourceScope resource scope}(s) behind provided <code>contextObject</code>.
*
* @param contextObject
* The context object the resource scope to refer to.
* @param includeReferencedScopes
* <code>true</code> if the resources of resource scopes that are referenced by the resource scope behind
* the context object are to be retrieved as well, <code>false</code> if only the resources of the
* resource scope behind the context object are to be retrieved.
* @param ignoreMetaModel
* <code>true</code> if the resources should be retrieved regardless whether their metamodel descriptor
* matches that behind the context object, <code>false</code> if the metamodel descriptors of the
* resources and the context object are required to match.
* @return The resources contained by this resource set that belong to the resource scope(s) behind provided
* <code>contextObject</code>.
*/
protected List<Resource> getResourcesInScope(Object contextObject, boolean includeReferencedScopes, boolean ignoreMetaModel) {
// Retrieve resource scope(s) along with the descriptor(s) of the metamodel(s) using it(them) behind given
// context object
Map<IResourceScope, Set<IMetaModelDescriptor>> contextResourceScopes = getContextResourceScopes(contextObject);
// Collect resources which belong to the same resource scope(s) and metamodel(s) (if required) as the context
// object does
/*
* !! Important Note !! A LinkedHashSet is used to preserve the ordering of the Resources. Using a simple
* HashSet will not preserve the ordering which leads to inconsistent results when implementing Resource merging
* based on the getResourcesInScope() method.
*/
Set<Resource> resourcesInScope = new LinkedHashSet<Resource>();
List<Resource> safeResources = new ArrayList<Resource>(getResources());
for (Resource resource : safeResources) {
for (IResourceScope contextResourceScope : contextResourceScopes.keySet()) {
if (contextResourceScope.belongsTo(resource, includeReferencedScopes)) {
if (ignoreMetaModel || hasMatchingMetaModel(contextResourceScopes.get(contextResourceScope), resource)) {
resourcesInScope.add(resource);
break;
}
}
}
}
return Collections.unmodifiableList(new ArrayList<Resource>(resourcesInScope));
}
/*
* @see
* org.eclipse.sphinx.emf.resource.ScopingResourceSet#isResourceInScope(org.eclipse.emf.ecore.resource.Resource,
* java.lang.Object)
*/
@Override
public boolean isResourceInScope(Resource resource, Object contextObject) {
return isResourceInScope(resource, contextObject, true, true);
}
/*
* @see
* org.eclipse.sphinx.emf.resource.ScopingResourceSet#isResourceInScope(org.eclipse.emf.ecore.resource.Resource,
* java.lang.Object, boolean)
*/
@Override
public boolean isResourceInScope(Resource resource, Object contextObject, boolean includeReferencedScopes) {
return isResourceInScope(resource, contextObject, includeReferencedScopes, true);
}
/**
* Tests if given {@link Resource resource} belongs to the {@link IResourceScope resource scope}(s) behind provided
* <code>contextObject</code>.
*
* @param resource
* The resource to be investigated.
* @param contextObject
* The context object identifying the resource scope to refer to.
* @param includeReferencedScopes
* <code>true</code> if the resource scopes that are referenced by the resource scope behind the context
* object are to be considered as well, <code>false</code> if only the resource scope behind the context
* object is to be considered.
* @param ignoreMetaModel
* <code>true</code> if the given resource should be considered to belong to the resource scope behind
* the context object regardless whether its metamodel descriptor matches that of the context object,
* <code>false</code> if the metamodel descriptors of the given resource and the context object are
* required to match.
* @return <code>true</code> if given resource belongs to the resource scope behind provided
* <code>contextObject</code>, or <code>false</code> otherwise.
*/
protected boolean isResourceInScope(Resource resource, Object contextObject, boolean includeReferencedScopes, boolean ignoreMetaModel) {
// Retrieve resource scope(s) along with the descriptor(s) of the metamodel(s) using it(them) behind given
// context object
Map<IResourceScope, Set<IMetaModelDescriptor>> contextResourceScopes = getContextResourceScopes(contextObject);
// Check if resource belongs to the same resource scope(s) and metamodel(s) (if required) as the context
// object does
for (IResourceScope contextResourceScope : contextResourceScopes.keySet()) {
if (contextResourceScope.belongsTo(resource, includeReferencedScopes)) {
if (ignoreMetaModel || hasMatchingMetaModel(contextResourceScopes.get(contextResourceScope), resource)) {
return true;
}
}
}
return false;
}
/**
* Retrieves a map that is keyed by the {@link IResourceScope resource scopes} behind given context object and
* yields the {@link IMetaModelDescriptor descriptors of the metamodel}s using them.
*
* @param contextObject
* The context object to retrieve the resource scopes for.
* @return A map containing the resource scopes behind given context object along with the descriptor of the
* metamodels using them.
*/
protected Map<IResourceScope, Set<IMetaModelDescriptor>> getContextResourceScopes(Object contextObject) {
Map<IResourceScope, Set<IMetaModelDescriptor>> resourceScopes = new HashMap<IResourceScope, Set<IMetaModelDescriptor>>(1);
// Try to retrieve resource behind given context object
Resource resource = EcoreResourceUtil.getResource(contextObject);
if (resource != null) {
contextObject = resource;
}
if (contextObject instanceof IFile) {
IFile contextFile = (IFile) contextObject;
// Retrieve URI behind context file
contextObject = EcorePlatformUtil.createURI(contextFile.getFullPath());
}
if (contextObject instanceof URI) {
URI contextURI = (URI) contextObject;
// Try to resolve context URI to resource contained by this resource set
resource = getResource(contextURI, false);
if (resource != null) {
contextObject = resource;
} else {
// Try to resolve context URI to workspace root, project or folder
if (contextURI.isPlatformResource()) {
IPath contextPath = new Path(contextURI.toPlatformString(true));
IResource member = ResourcesPlugin.getWorkspace().getRoot().findMember(contextPath);
if (member instanceof IContainer) {
contextObject = member;
}
}
}
}
if (contextObject instanceof Resource) {
Resource contextResource = (Resource) contextObject;
// Try to find descriptor of model that context resource belongs to
IModelDescriptor modelDescriptor = ModelDescriptorRegistry.INSTANCE.getModel(contextResource);
if (modelDescriptor != null) {
// Return the context model's scope along with the descriptor of its metamodel
resourceScopes.put(modelDescriptor.getScope(), Collections.singleton(modelDescriptor.getMetaModelDescriptor()));
} else {
// Context resource located outside of the workspace?
if (getOutsideWorkspaceScope().belongsTo(contextResource, false)) {
// Return common scope for resources located outside of the workspace along with the descriptor of
// the metamodel behind the context resource
IMetaModelDescriptor contextMMDescriptor = MetaModelDescriptorRegistry.INSTANCE.getDescriptor(contextResource);
resourceScopes.put(getOutsideWorkspaceScope(), Collections.singleton(contextMMDescriptor));
}
}
}
if (contextObject instanceof IContainer) {
IContainer contextContainer = (IContainer) contextObject;
for (IModelDescriptor modelDescriptor : ModelDescriptorRegistry.INSTANCE.getModels(contextContainer)) {
IResourceScope resourceScope = modelDescriptor.getScope();
Set<IMetaModelDescriptor> mmDescriptors = resourceScopes.get(resourceScope);
if (mmDescriptors == null) {
mmDescriptors = new HashSet<IMetaModelDescriptor>(1);
resourceScopes.put(resourceScope, mmDescriptors);
}
mmDescriptors.add(modelDescriptor.getMetaModelDescriptor());
}
}
if (contextObject instanceof IModelDescriptor) {
IModelDescriptor modelDescriptor = (IModelDescriptor) contextObject;
resourceScopes.put(modelDescriptor.getScope(), Collections.singleton(modelDescriptor.getMetaModelDescriptor()));
}
if (contextObject instanceof IResourceScope) {
resourceScopes.put((IResourceScope) contextObject, Collections.singleton(MetaModelDescriptorRegistry.ANY_MM));
}
return resourceScopes;
}
protected boolean hasMatchingMetaModel(Set<IMetaModelDescriptor> mmDescriptors, Resource resource) {
Assert.isNotNull(mmDescriptors);
IMetaModelDescriptor resourceMMDescriptor = MetaModelDescriptorRegistry.INSTANCE.getDescriptor(resource);
if (resourceMMDescriptor != null) {
for (IMetaModelDescriptor mmDescriptor : mmDescriptors) {
if (resourceMMDescriptor.equals(mmDescriptor) || MetaModelDescriptorRegistry.ANY_MM == mmDescriptor) {
return true;
}
}
}
return false;
}
/*
* @see org.eclipse.sphinx.emf.resource.ExtendedResourceSetImpl#getEObject(org.eclipse.emf.common.util.URI,
* org.eclipse.sphinx.emf.metamodel.IMetaModelDescriptor, java.lang.Object, boolean)
*/
@Override
protected EObject getEObject(URI uri, IMetaModelDescriptor targetMetaModelDescriptor, Object contextObject, boolean loadOnDemand) {
Assert.isNotNull(uri);
// Fragment-based URI not knowing its target resource?
if (uri.segmentCount() == 0) {
// Search for object behind given URI within relevant set of potential target resources in scope
List<Resource> resources = getResourcesToSearchIn(getResourcesInScope(contextObject), uri, targetMetaModelDescriptor);
return safeFindEObjectInResources(resources, uri, loadOnDemand);
} else {
// Target resource is known, so search for object behind given URI only in that resource
Resource resource = safeGetResource(uri, loadOnDemand);
if (resource != null) {
// Do we have any context information?
if (contextObject != null) {
// Retrieve EObject behind URI fragment only if target resource is in scope
if (isResourceInScope(resource, contextObject, true, true)) {
return safeGetEObjectFromResource(resource, uri.fragment());
}
} else {
// Apply default behavior and retrieve EObject behind URI fragment regardless of the
// resource's scope
return safeGetEObjectFromResource(resource, uri.fragment());
}
}
return null;
}
}
/**
* Determines the set of {@link Resource resource}s to be considered for resolving given {@link URI}. Only called
* when given URI is fragment-based (i.e., has no segments and doesn't reference any explicit target resource).
* <p>
* This implementation uses the provided {@link EObject context object}, if not <code>null</code>, to retrieve the
* {@link #getResourcesInScope(Object) resources in scope}, and returns {@link #getResources() all resources} in
* this {@link ScopingResourceSetImpl resource set} otherwise. Clients may override this method in order to tweak
* the set of {@link Resource resource}s used for resolving fragment-based {@link URI}s.
* </p>
*
* @param uri
* The fragment-based {@link URI} to resolve.
* @param loadOnDemand
* Whether to create and load the {@link Resource target resource}, if it isn't already present in this
* {@link ScopingResourceSetImpl resource set}.
* @param contextObject
* The {@link EObject context object} used to determine the set of {@link Resource resource}s to be
* considered for resolving given fragment-based {@link URI}.
* @return The set of {@link Resource resource}s to be considered for resolving given fragment-based {@link URI}.
* @see #getResourcesInScope(Object)
* @see #getResources()
* @deprecated Use {@link ExtendedResourceSetImpl#getResourcesToSearchIn(List, URI, IMetaModelDescriptor)} instead.
*/
@Deprecated
protected List<Resource> getResourcesToSearchIn(URI uri, boolean loadOnDemand, EObject contextObject) {
// Do we have any context information?
if (contextObject != null) {
// Only resources in scope are relevant
return getResourcesInScope(contextObject);
} else {
// All resources regardless of their scope need to be considered
return Collections.unmodifiableList(getResources());
}
}
}