blob: d63cb2df7c3470d47d40a56849650a81200da3fa [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2017 EclipseSource Services GmbH 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:
* Martin Fleck - initial API and implementation
* Christian W. Damus - bug 526932
* Christian W. Damus - bug 512529
*******************************************************************************/
package org.eclipse.papyrus.compare.uml2.internal.hook.migration;
import com.google.common.collect.Iterables;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EPackage.Registry;
import org.eclipse.emf.ecore.resource.ContentHandler;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.impl.TransactionalEditingDomainImpl;
import org.eclipse.papyrus.infra.core.resource.ModelSet;
import org.eclipse.papyrus.uml.tools.model.UmlModel;
/**
* This class wraps a resource set into a ModelSet with minimal changes to be compliant to the expected
* Profile Migration mechanism of Eclipse Luna.
*
* @author Martin Fleck <mfleck@eclipsesource.com>
*/
public class ModelSetWrapper extends ModelSet {
/**
* Mapping of resources to their read-only setting. Needed when a new editing domain is created.
*/
private Map<Resource, Boolean> resourceToReadOnlyMap = new HashMap<Resource, Boolean>();
/**
* The resource set being wrapped.
*/
private ResourceSet resourceSet;
/**
* Constructor.
*
* @param resourceSet
* resource set to be wrapped.
*/
public ModelSetWrapper(ResourceSet resourceSet) {
super();
this.resourceSet = resourceSet;
// We need to ensure that UML profiles are shared by myself and the
// resource set that I wrap, because a profile must be loaded exactly once
// to avoid confusing the identity of stereotypes. Otherwise, stereotype
// applications repaired in this model-set context would be deemed invalid
// in the wrapped resource-set context because there the UML API
// is seeing the wrong copy of the Stereotype as their definition
// cf. http://eclip.se/526932
this.resources = new ProxyingResourceList();
}
@Override
protected Resource demandCreateResource(URI uri) {
if (shouldProxy(uri)) {
// Create the real resource in the wrapped resource set and proxy it here
Resource delegate = resourceSet.createResource(uri, ContentHandler.UNSPECIFIED_CONTENT_TYPE);
if (delegate != null) {
// Get the proxy
return getResource(uri, false);
}
}
return super.demandCreateResource(uri);
}
/**
* Ensure that the given resource has the specified readOnly setting in the
* {@link #getTransactionalEditingDomain() editing domain} of this model set.
*
* @param resource
* resource within this resource set
* @param readOnly
* true if resource should be readOnly, false otherwise
*/
public void setReadOnly(Resource resource, Boolean readOnly) {
resourceToReadOnlyMap.put(resource, readOnly);
}
@Override
public synchronized TransactionalEditingDomain getTransactionalEditingDomain() {
final TransactionalEditingDomainImpl domain = new TransactionalEditingDomainImpl(
new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE));
domain.setResourceToReadOnlyMap(resourceToReadOnlyMap);
return domain;
}
@Override
public Registry getPackageRegistry() {
return resourceSet.getPackageRegistry(); // delegate to wrapped resourceSet
}
/**
* Queries whether a code resource should be proxied in the model set. This is usually required for UML
* library resources (profiles, metamodels, and such deployed in plug-ins) and not for other resources.
*
* @param resourceURI
* a resource URI
* @return whether it should be proxied
*/
protected boolean shouldProxy(URI resourceURI) {
return resourceSet.getURIConverter().normalize(resourceURI).isPlatformPlugin()
&& UmlModel.UML_FILE_EXTENSION.equals(resourceURI.fileExtension());
}
/**
* Detaches me from the resource-set that I wrap.
*/
public void detach() {
((ProxyingResourceList)resources).detach();
}
/**
* Directly set the model-set's URI without initializing the entire pluggable models and snippets
* infrastructure.
*
* @param uriWithoutExtension
* the URI without the file extension
*/
@Override
public void setURIWithoutExtension(URI uriWithoutExtension) {
super.setURIWithoutExtension(uriWithoutExtension);
}
//
// Nested types
//
/**
* A specialized resource list that shares the underlying resources (and hence their contents) of UML
* Profiles with the wrapped resource, to ensure a single in-memory copy of each profile definition for
* consistent identification of stereotype applications. Nonetheless, each resource set has to see these
* shared resources as "owned by" it, so in the {@code ModelSetWrapper} context this is achieved by
* dynamic proxies that pass through to the real resources for everything but the relationship to the
* owning resource-set.
*
* @author Christian W. Damus
*/
protected class ProxyingResourceList extends ResourcesEList<Resource> implements Adapter {
private static final long serialVersionUID = 1L;
private final Map<Resource, Resource> proxies = new HashMap<>();
/**
* Initializes me.
*/
protected ProxyingResourceList() {
super();
for (Resource next : resourceSet.getResources()) {
addProxy(next);
}
resourceSet.eAdapters().add(this);
}
/**
* Detaches me from my delegate list, removes all of my proxy resources, and forgets all of my proxy
* mappings.
*/
void detach() {
// Stop listening to my delegate for changes in its resources
resourceSet.eAdapters().remove(this);
// Remove all of my proxy resources that I don't own
removeAll(proxies.values());
proxies.clear();
}
//
// Proxy management
//
/**
* Queries whether a {@code resource} should be proxied in the model set. This is usually required for
* UML library resources (profiles, metamodels, and such deployed in plug-ins) and not for other
* resources.
*
* @param resource
* a resource
* @return whether it should be proxied
*/
protected boolean shouldProxy(Resource resource) {
return ModelSetWrapper.this.shouldProxy(resource.getURI());
}
/**
* Obtains the existing proxy, if any, that represents my share of a {@code resource}.
*
* @param resource
* a resource
* @return the existing proxy, or {@code null} if it has not been proxied
*/
Resource getProxy(Resource resource) {
return proxies.get(resource);
}
/**
* Creates the canonical proxy wrapper for a {@code resource} that represents my share of it.
*
* @param resource
* a resource to proxy
* @return the proxied resource
*/
Resource createProxy(Resource resource) {
Collection<Class<?>> interfaces = getResourceInterfaces(resource.getClass());
Resource result = (Resource)Proxy.newProxyInstance(getClass().getClassLoader(),
Iterables.toArray(interfaces, Class.class), new ResourceProxy(resource));
proxies.put(resource, result);
return result;
}
/**
* Computes the {@link Resource} interfaces that a proxy needs to implement to expose the full API of
* an instance of the given resource implementation class.
*
* @param resourceImpl
* the implementation class of a resource
* @return its interfaces
*/
private Collection<Class<?>> getResourceInterfaces(Class<?> resourceImpl) {
Collection<Class<?>> result = new LinkedHashSet<>();
for (Class<?> class_ = resourceImpl; class_ != null; class_ = class_.getSuperclass()) {
interfaces: for (Class<?> next : class_.getInterfaces()) {
if (Resource.class.isAssignableFrom(next)) {
for (Class<?> found : result) {
if (next.isAssignableFrom(found)) {
// Don't bother if it's a redundant superinterface
continue interfaces;
}
}
result.add(next);
}
}
}
// Remove redundant superinterfaces
return result;
}
/**
* Queries whether a {@code resource} is a proxy that represents my co-ownership of it.
*
* @param resource
* a resource
* @return whether it is a proxy of mine
*/
boolean isProxy(Resource resource) {
return resource != null && Proxy.isProxyClass(resource.getClass())
&& Proxy.getInvocationHandler(resource) instanceof ResourceProxy;
}
/**
* Obtains a proxy for the given {@code resource}, creating that proxy if necessary, to represent my
* share of it.
*
* @param resource
* a resource
* @return the {@code resource} itself if it already {@linkplain #isProxy(Resource) is a proxy} or a
* proxy wrapping it
*/
Resource proxy(Resource resource) {
if (isProxy(resource)) {
return resource;
}
Resource result = getProxy(resource);
if (result == null) {
result = createProxy(resource);
}
return result;
}
/**
* Obtains the real resource represented by the given {@code resource}, in case it
* {@linkplain #isProxy(Resource) is a proxy}.
*
* @param resource
* a resource
* @return the real resource if a proxy, otherwise just the {@code resource}, itself
*/
Resource unproxy(Resource resource) {
if (isProxy(resource)) {
return ((ResourceProxy)Proxy.getInvocationHandler(resource)).resource;
}
return resource;
}
/**
* Adds a proxy for me to share ownership of the given {@code resource} if I
* {@linkplain #shouldProxy(Resource) should have a proxy} for it.
*
* @param resource
* a resource to conditionally add to me as a proxy
*/
void addProxy(Resource resource) {
if (shouldProxy(resource)) {
add(proxy(resource));
}
}
/**
* Removes my proxy for the given {@code resource} if I have one
*
* @param resource
* a resource that I must no longer share
*/
void removeProxy(Resource resource) {
// Don't implicitly create a proxy just to remove it
remove(getProxy(resource));
}
/**
* Adds a proxy for me to share ownership of the given {@code newResource} in place of the old if I
* {@linkplain #shouldProxy(Resource) should have a proxy} for it. Otherwise, just ensures that I do
* not have a proxy for the {@code oldResource}.
*
* @param oldResource
* a resource for which I should not have a proxy
* @param newResource
* a resource to conditionally replace the old one with
*/
void replaceProxy(Resource oldResource, Resource newResource) {
// Don't implicitly create a proxy just to remove it
int index = indexOf(getProxy(oldResource));
if (index >= 0) {
if (shouldProxy(newResource)) {
set(index, proxy(newResource));
} else {
removeProxy(oldResource);
}
} else if (shouldProxy(newResource)) {
add(proxy(newResource));
}
}
//
// Adapter protocol to pick up changes in the delegate list
//
public void notifyChanged(Notification msg) {
if (msg.isTouch()) {
return;
}
if (msg.getNotifier() == resourceSet
&& msg.getFeatureID(ResourceSet.class) == RESOURCE_SET__RESOURCES) {
switch (msg.getEventType()) {
case Notification.ADD:
addProxy((Resource)msg.getNewValue());
break;
case Notification.ADD_MANY:
for (Object next : (Collection<?>)msg.getNewValue()) {
addProxy((Resource)next);
}
break;
case Notification.REMOVE:
removeProxy((Resource)msg.getOldValue());
break;
case Notification.REMOVE_MANY:
for (Object next : (Collection<?>)msg.getOldValue()) {
removeProxy((Resource)next);
}
break;
case Notification.SET:
replaceProxy((Resource)msg.getOldValue(), (Resource)msg.getNewValue());
break;
default:
// Pass. Not even move is interesting
break;
}
}
}
public Notifier getTarget() {
return null;
}
public void setTarget(Notifier newTarget) {
// Pass
}
public boolean isAdapterForType(Object type) {
return false;
}
}
/**
* Invocation handler for my proxy resources. It passes through all API to the wrapped resource, properly
* owned by my wrapped resource set, except for the API dealing with the relationship to the resource set,
* which in that case is myself.
*
* @author Christian W. Damus
*/
private class ResourceProxy implements InvocationHandler {
private final Resource resource;
ResourceProxy(Resource resource) {
super();
this.resource = resource;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
case "getResourceSet": //$NON-NLS-1$
if (method.getDeclaringClass() == Resource.class) {
return ModelSetWrapper.this;
}
break;
case "basicSetResourceSet": //$NON-NLS-1$
if (method.getDeclaringClass() == Resource.Internal.class) {
// Don't try to tell the real resource that the wrapper
// model-set is its owner
return null;
}
break;
}
return method.invoke(resource, args);
}
}
}