package com.sap.runlet.interpreter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.examples.eventmanager.EventManagerFactory;
import com.sap.ap.metamodel.utils.StringFormatter;
import com.sap.runlet.abstractinterpreter.util.Tuple.Pair;
import data.classes.MethodSignature;
import data.classes.SapClass;
import data.classes.SignatureImplementation;
import data.classes.SignatureOwner;
import data.classes.TypeAdapter;
public class MethodCallResolver {
/**
* Caches the {@link URI}s of the signature implementation model elements; only URIs
* because different ResourceSets may want to know.
*/
private Map<Pair<MethodSignature, SapClass>, URI> cache =
new HashMap<Pair<MethodSignature, SapClass>, URI>();
private final MethodCallResolverCacheInvalidationListener listener;
/**
* Registers the method call resolver as an event listener with an event manager for the
* resource set. Every time an event is received that may cause a change in method resolution,
* the cache is invalidated.
*/
public MethodCallResolver(ResourceSet resourceSet) {
listener = new MethodCallResolverCacheInvalidationListener(this);
EventManagerFactory.eINSTANCE.getEventManagerFor(resourceSet).subscribe(listener.getFilter(), listener);
}
public SignatureImplementation getImplementation(MethodSignature staticSignature, SapClass dynamicClass, ResourceSet resourceSet) {
Pair<MethodSignature, SapClass> key = new Pair<MethodSignature, SapClass>(staticSignature, dynamicClass);
SignatureImplementation result;
URI implURI = cache.get(key);
if (implURI == null) {
result = resolveMethodCallToImplementation(staticSignature, dynamicClass);
cache.put(key, EcoreUtil.getURI(result));
} else {
result = (SignatureImplementation) resourceSet.getEObject(implURI, /* loadOnDemand */ true);
}
return result;
}
public void invalidateCache() {
cache.clear();
}
/**
* Implements the polymorphism strategy. When a method is to be invoked on
* an object, the object's runtime type needs to be considered in order to
* determine the right implementation for the method signature invoked.
* <p>
*
* The algorithm checks if there is a signature on the runtime type of the
* <tt>thiz</tt> object that conforms to <tt>calledSignature</tt>. If one is
* found, it is returned. If none is found, the set of adapters known for
* <tt>thiz</tt>'s runtime class are transitively checked for matching
* implementations. If none is found, this means there is a conformance
* problem where <tt>thiz</tt> does not conform to the class by which
* <tt>calledSignature</tt> is defined which points to a problem in the
* design-time's type checks. A {@link RuntimeException} is thrown in this
* case. Otherwise, the signature implementation found in an adapter will be
* returned.
*
* TODO what about delegation?
*/
public SignatureImplementation resolveMethodCallToImplementation(MethodSignature calledSignature, SapClass thizClass) {
SapClass staticClass = (SapClass) calledSignature.getOwner();
SignatureImplementation result = findConformingSignature(calledSignature, thizClass);
if (result == null) {
// No conforming signature found on thizClass; search along adapters
List<TypeAdapter> adapterChain = getAdapterChain(thizClass, staticClass);
if (adapterChain == null) {
throw new RuntimeException("Internal error: couldn't resolve method " + StringFormatter.toString(calledSignature)
+ " on type " + thizClass.getName());
}
for (TypeAdapter ta : getAdapterChain(thizClass, staticClass)) {
result = findConformingSignature(calledSignature, ta);
if (result != null) {
break;
}
}
}
return result;
}
/**
* Tries to locate an owned signature on the <tt>signatureOwner</tt> that
* conforms to <tt>calledSignature</tt>. If no such signature can be found,
* <tt>null</tt> is returned. <em>No</em> adapter lookups are performed by
* this method.
*/
private SignatureImplementation findConformingSignature(MethodSignature calledSignature, SignatureOwner signatureOwner) {
for (MethodSignature thizClassSignature : signatureOwner.getOwnedSignatures()) {
if (thizClassSignature.conformsTo(calledSignature)) {
return thizClassSignature.getImplementation();
}
}
return null;
}
/**
* Attempts to find a chain of {@link TypeAdapter}s where the first's
* {@link TypeAdapter#getAdapted()} equals <tt>from</tt> and the last
* adapter's {@link TypeAdapter#getTo()} equals <tt>to</tt> and for each
* consecutive pair of adapters <tt>a1</tt> and <tt>a2</tt> in the chain,
*
* <tt>a1.{@link TypeAdapter#getTo() getTo}.equals(a2.{@link TypeAdapter#getAdapted() getAdapted})</tt>
* .
*/
private List<TypeAdapter> getAdapterChain(SapClass from, SapClass to) {
// TODO consider caching the adapter chain results
return getAdapterChain(from, to, new HashSet<SapClass>());
}
/**
* Attempts to find a chain of {@link TypeAdapter}s where the first's
* {@link TypeAdapter#getAdapted()} equals <tt>from</tt> and the last
* adapter's {@link TypeAdapter#getTo()} equals <tt>to</tt> and for each
* consecutive pair of adapters <tt>a1</tt> and <tt>a2</tt> in the chain,
*
* <tt>a1.{@link TypeAdapter#getTo() getTo}.equals(a2.{@link TypeAdapter#getAdapted() getAdapted})</tt>
* .
* <p>
*
* <tt>visitedClasses</tt> is the set of classes to which adaptation of
* <tt>from</tt> has already been achieved. Paths leading this direction
* therefore can be cut off, also avoiding endless recursions in case of
* cyclic adapter definitions.
* <p>
*
* If no such adapter chain can be found, <tt>null</tt> is returned.
*/
private List<TypeAdapter> getAdapterChain(SapClass from, SapClass to, Set<SapClass> visitedClasses) {
List<TypeAdapter> result = null;
if (from.equals(to)) {
result = new ArrayList<TypeAdapter>();
} else {
visitedClasses.add(from);
Collection<TypeAdapter> candidates = from.getAdapters();
for (TypeAdapter candidate : candidates) {
if (!visitedClasses.contains(candidate.getTo())) {
result = getAdapterChain(candidate.getTo(), to, visitedClasses);
if (result != null) {
result.add(0, candidate);
break;
}
}
}
}
return result;
}
}