package com.sap.runlet.interpreter.expressions;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.sap.ap.metamodel.utils.StringFormatter;
import com.sap.runlet.abstractinterpreter.StackFrame;
import com.sap.runlet.abstractinterpreter.objects.ClassTypedObject;
import com.sap.runlet.abstractinterpreter.objects.EmptyObject;
import com.sap.runlet.abstractinterpreter.objects.MultiValuedObject;
import com.sap.runlet.abstractinterpreter.objects.RunletObject;
import com.sap.runlet.interpreter.RunletInterpreter;
import com.sap.runlet.interpreter.objects.FunctionFromMethodObject;
import data.classes.AssociationEnd;
import data.classes.ClassTypeDefinition;
import data.classes.FunctionSignatureTypeDefinition;
import data.classes.MethodSignature;
import data.classes.SapClass;
import data.classes.SignatureImplementation;
import data.classes.TypeDefinition;
import dataaccess.expressions.MethodCallExpression;
public class MethodCallInterpreter extends SignatureCallInterpreter {
public MethodCallInterpreter(MethodCallExpression mce) {
super(mce);
}
protected MethodCallExpression getSignatureCallExpression() {
return (MethodCallExpression) sce;
}
/**
* This implementation computes and adds the <tt>this</tt> value to the stack frame
*/
protected RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> completeStackFrameAndExecute(final RunletInterpreter interpreter,
final List<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> parameterValues) throws NoSuchMethodException,
InstantiationException, IllegalAccessException,
InvocationTargetException {
RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> result;
// The cast to ClassTypedObject is okay because of constraint MethodCallExpression.ObjectMustSupportOperation
RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> on = getOnObject(interpreter);
if (on == null) {
// TODO clarify exception handling semantics in River
// TODO clarify: should invoking on null for 0..x cause an exception at all or just do nothing?
throw new NullPointerException("trying to invoke method "+
StringFormatter.toString(getSignatureCallExpression().getSignature()));
}
// multi-valued can't be recognized from the expression's multiplicity; it could itself have
// resulted from a multi-valued this invocation, leading to multiple individual results combined
// into one multi-valued object; do an ugly "instanceof" instead:
boolean isMultipleThis = on instanceof MultiValuedObject<?, ?, ?>;
if (isMultipleThis) {
// create result collection and wrap with MultiValuedObject
final List<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> resultCollection = new ArrayList<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>>();
int i = 0;
Set<Thread> threadsToWaitFor = new HashSet<Thread>();
// FIXME (bug #2173) on.flatten() contradicts the way the return type definition is computed; collect one by one recursively
for (RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> thizz : on.flatten()) {
final FunctionFromMethodObject evaluator = getEvaluator(interpreter,
(ClassTypedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>) thizz);
if (evaluator.getImplementation().getImplements_().isSideEffectFree()) {
final int j = i;
resultCollection.add(null); // ensure that the list holds j+1 elements
// background execution
Thread t = new Thread() {
public void run() {
try {
resultCollection.set(j, exec(interpreter.spawn(), parameterValues, evaluator));
} catch (Throwable e) {
e.printStackTrace();
resultCollection.set(j, new ExceptionObject(interpreter, e, interpreter.getDefaultSnapshot()));
}
}
};
t.start();
threadsToWaitFor.add(t);
} else {
resultCollection.add(exec(interpreter, parameterValues, evaluator));
}
i++;
}
for (Thread t:threadsToWaitFor) {
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// TODO if a signature has a null output/return type, shouldn't we be creating an EmptyObject instead?
TypeDefinition outputType = getSignatureCallExpression().getSignature().getOutput();
if (outputType == null) {
result = new EmptyObject<AssociationEnd, SapClass, TypeDefinition, ClassTypeDefinition>(
getSignatureCallExpression().getSignature().getOutput(), interpreter.getModelAdapter());
} else {
result = new MultiValuedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>(outputType, resultCollection,
outputType.isOrdered(), outputType.isUnique());
}
} else {
if (on instanceof EmptyObject<?, ?, ?, ?>) {
result = new EmptyObject<AssociationEnd, SapClass, TypeDefinition, ClassTypeDefinition>(
getSignatureCallExpression().getSignature().getOutput(), interpreter.getModelAdapter());
}
else {
result = exec(interpreter, parameterValues, getEvaluator(interpreter,
(ClassTypedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>) on));
}
}
return result;
}
/**
* Determines the object on which to invoke the method. This implementation uses the
* {@link MethodCallExpression#getObject()} expression for this purpose.
*/
protected RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> getOnObject(final RunletInterpreter interpreter) throws NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException {
return interpreter.evaluate(getSignatureCallExpression().getObject());
}
/**
* Polymorphically resolves the method to call, based on the object currently
* listed as the {@link StackFrame#getThis() this} pointer of the current stack
* frame.<p>
*
* @return a {@link FunctionFromMethodObject} whose type attribute is not filled but
* left as <tt>null</tt>. Otherwise, this method would have to create a new
* {@link FunctionSignatureTypeDefinition} which is really undesirable here. The
* dedicated use of the {@link FunctionMethodFromObject} just for passing the
* implementation to call and the this pointer back to the call by {@link #exec}
* seem to justify this.
*/
private FunctionFromMethodObject getEvaluator(RunletInterpreter interpreter, ClassTypedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> thiz)
throws SecurityException, IllegalArgumentException {
SignatureImplementation impl = interpreter.resolveMethodCallToImplementation(
(MethodSignature) getSignatureCallExpression().getSignature(),
thiz);
FunctionFromMethodObject result = new FunctionFromMethodObject(/*type*/null, impl, thiz);
return result;
}
}