/******************************************************************************* * Copyright (c) 2009, 2012 SAP AG 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: * SAP AG - initial API and implementation ******************************************************************************/ package org.eclipse.ocl.examples.impactanalyzer.instanceScope; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.Stack; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EOperation; import org.eclipse.emf.ecore.EParameter; import org.eclipse.ocl.ecore.EcoreEnvironment; import org.eclipse.ocl.ecore.IterateExp; import org.eclipse.ocl.ecore.LetExp; import org.eclipse.ocl.ecore.LoopExp; import org.eclipse.ocl.ecore.OCLExpression; import org.eclipse.ocl.ecore.OperationCallExp; import org.eclipse.ocl.ecore.Variable; import org.eclipse.ocl.ecore.VariableExp; import org.eclipse.ocl.examples.impactanalyzer.impl.OperationBodyToCallMapper; import org.eclipse.ocl.examples.impactanalyzer.util.OCLFactory; import org.eclipse.ocl.utilities.PredefinedType; /** * Computes a {@link NavigationStep} for a {@link VariableExp} which, given an element constituting a value the variable * shall assume, infers a set of elements which, when used as the value of the outermost expression's <code>self</code> variable, * may lead the variable considered here to assume the expected value.<p> * * OCL knows occurrences of different "stereotypes" of variables, depending on where/how the variable is defined: * <ul> * <li>a <code>self</code> variable occurring within the body of an operation</li> * <li>a <code>self</code> variable occurring outside the body of an operation</li> * <li>an operation's formal parameter</li> * <li>an iterator variable</li> * <li>the result variable of an {@link IterateExp}</li> * <li>the variable assigned by a {@link LetExp}</li> * </ul> * * In all cases, traceback continues with the expression defining the possible values that the variable may assume. In all * cases, this leaves the scope in which the variable is visible. This becomes important when considering how variable values * travel through the computation of the <code>unused</code> function, trying to prove for each expression visited by traceback, * whether or not it is used, given what is known about variable values. * * @author Axel Uhl (D043530) * */ // TODO refactor into several subclasses, one for each is... case, and let tracer factory in InstanceScopeAnalysis.createTracer choose the appropriate one public class VariableExpTracer extends AbstractTracer<VariableExp> { public VariableExpTracer(VariableExp expression, Stack<String> tuplePartNames, OCLFactory oclFactory) { super(expression, tuplePartNames, oclFactory); } private Variable getVariableDeclaration() { return (Variable) getExpression().getReferredVariable(); } private boolean isLetVariable() { // let variables are contained in the letExp Variable exp = getVariableDeclaration(); return exp.eContainer() instanceof LetExp && ((LetExp) exp.eContainer()).getVariable() == exp; } private boolean isOperationParameter() { return getVariableDeclaration().getRepresentedParameter() != null; } private boolean isIterateResultVariable() { // result variables are contained in iterateExp Variable exp = getVariableDeclaration(); return (exp.eContainer() instanceof IterateExp && ((IterateExp) exp.eContainer()).getResult() == exp); } private boolean isIteratorVariable() { Variable exp = getVariableDeclaration(); return (exp.eContainer() instanceof LoopExp && ((LoopExp) exp.eContainer()).getIterator().contains(exp)); } private boolean isSelf() { return getVariableDeclaration().getName().equals(EcoreEnvironment.SELF_VARIABLE_NAME); } /** * The step produced will be invoked with the value for the variable. This knowledge can be helpful when trying to perform * partial evaluations. Variables have an {@link OCLExpression} as their scope. For example, a let-variable has the * {@link LetExp#getIn() in} expression as its static scope. Additionally, scopes are dynamically instantiated during * expression evaluation. For example, during evaluation of an {@link IterateExp}, the body expression forms the static scope * for the {@link IterateExp#getResult() result variable}. During each iteration, a new dynamic scope is created and the same * static result variable may have a different value in each dynamic scope. It is important to understand that the navigation * step learns about the value of the variable only in one particular dynamic scope. * <p> * * Dynamic scopes are identified by the {@link OCLExpression} object forming the static scope, combined with a unique * identifier, implemented as a simple counter. * <p> * * It is also important to understand that variables of a collection type cannot have their value fully inferred by the * traceback process as only single elements are visited during the trace. */ @Override public NavigationStep traceback(EClass context, PathCache pathCache, OperationBodyToCallMapper operationBodyToCallMapper) { NavigationStep result; if (isSelf()) { result = tracebackSelf(context, pathCache, operationBodyToCallMapper); } else if (isIteratorVariable()) { result = tracebackIteratorVariable(context, pathCache, operationBodyToCallMapper); } else if (isIterateResultVariable()) { result = tracebackIterateResultVariable(context, pathCache, operationBodyToCallMapper); } else if (isLetVariable()) { result = tracebackLetVariable(context, pathCache, operationBodyToCallMapper); } else if (isOperationParameter()) { result = tracebackOperationParameter(context, pathCache, operationBodyToCallMapper); } else { throw new RuntimeException("Unknown variable expression that is neither an iterator variable " + "nor an iterate result variable nor an operation parameter nor a let variable nor self: " + getExpression().getReferredVariable().getName()); } applyScopesOnNavigationStep(result, operationBodyToCallMapper); return result; } private NavigationStep tracebackOperationParameter(EClass context, PathCache pathCache, OperationBodyToCallMapper operationBodyToCallMapper) { OCLExpression rootExpression = getRootExpression(); // all operation bodies must have been reached through at least one call; all calls are // recorded in the filter synthesizer's cache. Therefore, we can determine the relationship // between body and EOperation. EOperation op = operationBodyToCallMapper.getCallsOf(rootExpression).iterator().next().getReferredOperation(); int pos = getParameterPosition(op); List<NavigationStep> stepsPerCall = new ArrayList<NavigationStep>(); IndirectingStep indirectingStep = pathCache.createIndirectingStepFor(getExpression(), getTupleLiteralPartNamesToLookFor()); // As new operation calls change the set of OperationCallExp returned here for existing operations, // the PathCache cannot trivially be re-used across expression registrations. We would have to // invalidate all cache entries that depend on this step. Or we add steps produced for new calls to this // step as the calls get added; but that may require a re-assessment of the isAlwaysEmpty() calls. // This may not pay off. for (OperationCallExp call : operationBodyToCallMapper.getCallsOf(rootExpression)) { OCLExpression argumentExpression = (OCLExpression) call.getArgument().get(pos); NavigationStep stepForCall = pathCache.getOrCreateNavigationPath(argumentExpression, context, operationBodyToCallMapper, getTupleLiteralPartNamesToLookFor(), oclFactory); // leaves all variables currently in scope because it'll jump into a new expression context; // the step enters into all scopes in which the argument expression in the operation call is nested, // starting from the root expression for the argument expression stepForCall.addLeavingScopes(getAllVariablesInScope(getExpression(), operationBodyToCallMapper)); stepForCall.addEnteringScopes(getAllVariablesInScope(argumentExpression, operationBodyToCallMapper)); stepsPerCall.add(stepForCall); } indirectingStep.setActualStep(pathCache.navigationStepForBranch(getInnermostElementType(getExpression().getType()), context, getExpression(), getTupleLiteralPartNamesToLookFor(), /* requireExactMatchForSourceType */ false, stepsPerCall.toArray(new NavigationStep[0]))); return indirectingStep; } /** * Determines the position of the parameter of operation <tt>op</tt> that is named like the variable referred to by this * tracer's {@link #getExpression() variable expression). */ private int getParameterPosition(EOperation op) { String variableName = getVariableDeclaration().getName(); // determine position of formal IN_DIR parameter named variableName int pos = 0; EList<EObject> pList = op.eContents(); for (EObject p : pList) { if (p instanceof EParameter) { if (variableName.equals(((EParameter) p).getName())) { break; } else { pos++; } } } return pos; } private NavigationStep tracebackLetVariable(EClass context, PathCache pathCache, OperationBodyToCallMapper operationBodyToCallMapper) { NavigationStep result = pathCache.getOrCreateNavigationPath((OCLExpression) getVariableDeclaration().getInitExpression(), context, operationBodyToCallMapper, getTupleLiteralPartNamesToLookFor(), oclFactory); return result; } private NavigationStep tracebackIterateResultVariable(EClass context, PathCache pathCache, OperationBodyToCallMapper operationBodyToCallMapper) { // the init expression can't reference the result variable, therefore no recursive reference may occur here: NavigationStep stepForInitExpression = pathCache.getOrCreateNavigationPath((OCLExpression) getVariableDeclaration() .getInitExpression(), context, operationBodyToCallMapper, getTupleLiteralPartNamesToLookFor(), oclFactory); // the body expression, however, may reference the result variable; computing the body's navigation step graph // may therefore recursively look up the navigation step graph for the result variable. We therefore need to // enter a placeholder into the cache before we start computing the navigation step graph for the body expression: IndirectingStep indirectingStep = pathCache.createIndirectingStepFor( getExpression(), getTupleLiteralPartNamesToLookFor()); NavigationStep stepForBodyExpression = pathCache.getOrCreateNavigationPath( (OCLExpression) ((IterateExp) getVariableDeclaration().eContainer()).getBody(), context, operationBodyToCallMapper, getTupleLiteralPartNamesToLookFor(), oclFactory); NavigationStep actualStepForIterateResultVariableExp = pathCache.navigationStepForBranch( getInnermostElementType(getExpression().getType()), context, getExpression(), getTupleLiteralPartNamesToLookFor(), /* requireExactMatchForSourceType */ false, stepForInitExpression, stepForBodyExpression); indirectingStep.setActualStep(actualStepForIterateResultVariableExp); return indirectingStep; } private NavigationStep tracebackIteratorVariable(EClass context, PathCache pathCache, OperationBodyToCallMapper operationBodyToCallMapper) { LoopExp loopExp = (LoopExp) getVariableDeclaration().eContainer(); // the source expression can't reference the iterator variable, therefore no recursive reference may occur here: NavigationStep stepForSource = pathCache.getOrCreateNavigationPath( (OCLExpression) loopExp.getSource(), context, operationBodyToCallMapper, getTupleLiteralPartNamesToLookFor(), oclFactory); NavigationStep result; if (PredefinedType.CLOSURE_NAME.equals(loopExp.getName())) { // the body expression, however, may reference the iterator variable; computing the body's navigation step graph // may therefore recursively look up the navigation step graph for the iterator variable. We therefore need to // enter a placeholder into the cache before we start computing the navigation step graph for the body expression: IndirectingStep indirectingStep = pathCache.createIndirectingStepFor( getExpression(), getTupleLiteralPartNamesToLookFor()); NavigationStep stepForBodyExpression = pathCache.getOrCreateNavigationPath( (OCLExpression) loopExp.getBody(), context, operationBodyToCallMapper, getTupleLiteralPartNamesToLookFor(), oclFactory); NavigationStep actualStepForIterateResultVariableExp = pathCache.navigationStepForBranch( getInnermostElementType(getExpression().getType()), context, getExpression(), getTupleLiteralPartNamesToLookFor(), /* requireExactMatchForSourceType */ false, stepForSource, stepForBodyExpression); indirectingStep.setActualStep(actualStepForIterateResultVariableExp); result = indirectingStep; } else { result = stepForSource; } return result; } private NavigationStep tracebackSelf(EClass context, PathCache pathCache, OperationBodyToCallMapper operationBodyToCallMapper) { IndirectingStep result; EOperation op = getOperationOfWhichRootExpressionIsTheBody(operationBodyToCallMapper); if (op != null) { // in an operation, self needs to be traced back to all source expressions of // calls to that operation Collection<OperationCallExp> calls = operationBodyToCallMapper.getCallsOf(getRootExpression()); IndirectingStep indirectingStep = pathCache.createIndirectingStepFor( getExpression(), getTupleLiteralPartNamesToLookFor()); List<NavigationStep> stepsForCalls = new ArrayList<NavigationStep>(); for (OperationCallExp call : calls) { OCLExpression callSource = (OCLExpression) call.getSource(); NavigationStep stepForCall = pathCache.getOrCreateNavigationPath(callSource, context, operationBodyToCallMapper, getTupleLiteralPartNamesToLookFor(), oclFactory); // leaves all variables currently in scope because it'll jump into a new expression context; // the step enters into all scopes in which the source expression in the operation call is nested, // starting from the root expression for the source expression stepForCall.addLeavingScopes(getAllVariablesInScope(getExpression(), operationBodyToCallMapper)); stepForCall.addEnteringScopes(getAllVariablesInScope(callSource, operationBodyToCallMapper)); stepsForCalls.add(stepForCall); } // the branching navigation step must not be cached or looked up in a cache because its // associated expression is the same as the one used for the IndirectingStep created and cached // above. indirectingStep.setActualStep(pathCache.navigationStepForBranch(getInnermostElementType(getExpression().getType()), context, getExpression(), getTupleLiteralPartNamesToLookFor(), /* requireExactMatchForSourceType */ false, stepsForCalls.toArray(new NavigationStep[0]))); result = indirectingStep; } else { // self occurred outside of an operation; it evaluates to s for s being the context result = pathCache.createIndirectingStepFor(getExpression(), getTupleLiteralPartNamesToLookFor()); result.setActualStep(new IdentityNavigationStep((EClass) getExpression().getType(), (EClass) getExpression() .getType(), getExpression())); } return result; } private EOperation getOperationOfWhichRootExpressionIsTheBody(OperationBodyToCallMapper operationBodyToCallMapper) { // all operation bodies must have been reached through at least one call; all calls are // recorded in the filter synthesizer's cache. Therefore, we can determine the relationship // between body and EOperation. Set<OperationCallExp> filterSynthesizerCallCache = operationBodyToCallMapper.getCallsOf(getRootExpression()); EOperation op = null; if (!filterSynthesizerCallCache.isEmpty()) { op = filterSynthesizerCallCache.iterator().next().getReferredOperation(); } return op; } @Override protected Set<Variable> calculateLeavingScopes(OperationBodyToCallMapper operationBodyToCallMapper) { // when tracing back a VariableExp, a number of scopes may be left when navigating to the variable definition // leaving scopes are calculated by finding all scope creating expressions between the traced VariableExp // and the common composition parent of the VariableExp and its definition Set<Variable> result; if (isLetVariable()) { result = getVariablesIntroducedBetweenHereAnd((OCLExpression) ((LetExp) getVariableDeclaration().eContainer()).getIn(), operationBodyToCallMapper); } else if (isIteratorVariable() || isIterateResultVariable()) { result = getVariablesIntroducedBetweenHereAnd((OCLExpression) ((LoopExp) getVariableDeclaration().eContainer()).getBody(), operationBodyToCallMapper); } else if (isOperationParameter() || (isSelf() && getOperationOfWhichRootExpressionIsTheBody(operationBodyToCallMapper) != null)) { // for operation parameters or self inside an operation body, traceback continues with the call expression, // leaving all scopes result = getAllVariablesInScope(getExpression(), operationBodyToCallMapper); } else { result = Collections.emptySet(); } return result; } }