package com.sap.runlet.interpreter.expressions; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import behavioral.actions.Block; import behavioral.actions.Statement; import com.sap.runlet.abstractinterpreter.Interpreter; 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.abstractinterpreter.repository.SnapshotIdentifier; import com.sap.runlet.interpreter.RunletInterpreter; import com.sap.runlet.interpreter.RunletStackFrame; import com.sap.runlet.interpreter.objects.FunctionFromMethodObject; import com.sap.runlet.interpreter.objects.FunctionObject; import com.sap.runlet.interpreter.objects.NativeObject; import data.classes.Association; import data.classes.AssociationEnd; import data.classes.ClassTypeDefinition; import data.classes.NativeImpl; import data.classes.SapClass; import data.classes.SignatureImplementation; import data.classes.TypeDefinition; import dataaccess.expressions.Expression; import dataaccess.expressions.FunctionCallExpression; import dataaccess.expressions.SignatureCallExpression; public class SignatureCallInterpreter implements Interpreter<SignatureCallExpression, SapClass, TypeDefinition, ClassTypeDefinition, Association, AssociationEnd, Statement, Expression, SignatureImplementation, RunletStackFrame, NativeImpl, RunletInterpreter> { protected SignatureCallExpression sce; static class ExceptionObject extends NativeObject { public ExceptionObject(RunletInterpreter interpreter, Throwable t, SnapshotIdentifier snapshot) { super(/* ClassTypeDefinition */ null, t, snapshot, interpreter); } } public SignatureCallInterpreter(SignatureCallExpression sce) { this.sce = sce; } protected SignatureCallExpression getSignatureCallExpression() { return sce; } protected List<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> getParameterValues( final RunletInterpreter interpreter) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { // Collect side effect-free subexpressions and compute in parallel final LinkedHashMap<Expression, RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> parameterValues = new LinkedHashMap<Expression, RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>>(); Map<Thread, Expression> spawnedThreads = new HashMap<Thread, Expression>(); List<Expression> effectiveParameters = new ArrayList<Expression>(sce.getParameters()); if (effectiveParameters.size() < sce.getSignature().getInput().size()) { // fill up with default values for (int i=effectiveParameters.size(); i<sce.getSignature().getInput().size(); i++) { effectiveParameters.add(sce.getSignature().getInput().get(i).getDefaultValue()); } } for (final Expression paramExp:effectiveParameters) { // FIXME execute non-side-effect-free args first and sequentially, then parallelize only on side effect-free ones if (effectiveParameters.size() > 1 && paramExp.isSideEffectFree()) { // background execution Thread t = new Thread("Parallel parameter evaluation") { public void run() { try { parameterValues.put(paramExp, interpreter.spawn().evaluate(paramExp)); } catch (Throwable e) { e.printStackTrace(); parameterValues.put(paramExp, new ExceptionObject(interpreter, e, interpreter.getDefaultSnapshot())); } } }; spawnedThreads.put(t, paramExp); t.start(); } else { // foreground execution parameterValues.put(paramExp, interpreter.evaluate(paramExp)); } } for (Thread t:spawnedThreads.keySet()) { try { t.join(); if (parameterValues.get(spawnedThreads.get(t)) instanceof ExceptionObject) { throw new RuntimeException((Throwable) ((ExceptionObject) parameterValues .get(spawnedThreads.get(t))).getNativeObject()); } } catch (InterruptedException e) { e.printStackTrace(); } } List<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> result = new ArrayList<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>>(); for (Expression paramExp:effectiveParameters) { result.add(parameterValues.get(paramExp)); } return result; } /** * Evaluation of a signature call expression works in the following phases: * <ul> * <li>compute actual parameter values if the call takes parameters</li> * <li>create a new stack frame with the parameter values bound to the parameters</li> * <li>give subclasses a chance to enrich the stack before executing the implementation, * e.g., as to set the <tt>this</tt> pointer for a method call expression and iterate * over its values in case it's a multi-valued object</li> * <li>push the stack frame onto the call stack</li> * <li>determine the implementation to execute, e.g., by evaluating the function expression * or by doing polymorphic resolution based on the value of <tt>this</tt>; this step * has to happen before the new stack frame has been activated because it may require * another evaluation, and that needs to take place in the current and not the new * stack frame context</li> * <li>execute the implementation by asking the interpreter to evaluate it</li> * </ul> */ @Override public RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> evaluate(final RunletInterpreter interpreter) throws SecurityException, IllegalArgumentException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { List<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> parameterValues = getParameterValues(interpreter); return completeStackFrameAndExecute(interpreter, parameterValues); // compute "on" object before pushing stack } /** * For a function signature call, a {@link FunctionObject} is returned that results from * evaluating the expression that yields the {@link Block} to execute. In particular the * subclass for method call execution needs to return a {@link FunctionFromMethodObject} * wrapping the method implementation to call. * * @return a single of multiple/nested {@link FunctionObject}. */ protected RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> getExecutableToCall(RunletInterpreter interpreter) throws SecurityException, IllegalArgumentException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { return interpreter.evaluate(((FunctionCallExpression) sce).getCalledBlock()); } /** * Subclasses can optionally override this to add more stuff to the stack frame than * just the arguments, such as the <tt>this</tt> value in case of method calls. */ protected RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> completeStackFrameAndExecute(final RunletInterpreter interpreter, List<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> parameterValues) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { // Note that there is no polymorphism for function calls return exec(interpreter, parameterValues, getExecutableToCall(interpreter)); } /** * Pushes a new stack frame on the <tt>interpreter</tt>'s stack and adds the parameters with * their values to that stack frame. The evaluators are expected to be {@link FunctionObject}s * whose {@link FunctionObject#evaluate(RunletInterpreter)} is used to evaluate the * signature implementation. When the evaluation has finished the stack frame is popped * again from the interpreter's stack. */ protected RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> exec(RunletInterpreter interpreter, List<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> parameterValues, RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> evaluators) throws SecurityException, IllegalArgumentException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> result = null; Collection<RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>> resultCollection = null; TypeDefinition sceType = sce.getType(); boolean pushed = false; try { // TODO check use of flatten() once NestedTypeDefinition use has been finalized // TODO parallelize if side effect-free for (RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> functionObject : evaluators.flatten()) { FunctionObject evaluator = (FunctionObject) functionObject; RunletStackFrame newStackFrame = interpreter.pushArgumentsToStackFrame(parameterValues, evaluator); pushed = true; interpreter.push(newStackFrame); // TODO this must not happen before function expression has been evaluated (errr... why again?) RunletObject<AssociationEnd, TypeDefinition, ClassTypeDefinition> evaluationResult = evaluator.evaluate(interpreter); if (result != null) { // already a result; this is a "many" case; put previous and this result into collection resultCollection = RunletObject.createCollection( sce.getType().isOrdered(), sce.getType().isUnique()); resultCollection.add(result); result = null; resultCollection.add(evaluationResult); } else { result = evaluationResult; } } } finally { if (pushed) { interpreter.pop(); } } if (sceType != null) { if (resultCollection != null) { result = new MultiValuedObject<AssociationEnd, TypeDefinition, ClassTypeDefinition>( sce.getType(), resultCollection, sce.getType().isOrdered(), sce.getType().isUnique()); } else { if (result == null) { // non-void return type; return valid EmptyObject result = new EmptyObject<AssociationEnd, SapClass, TypeDefinition, ClassTypeDefinition>(sce.getType(), interpreter.getModelAdapter()); } } } else { // return type is void; force a null result assert result == null; } return result; } }