| /******************************************************************************* |
| * 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.traceback; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| import org.eclipse.emf.common.notify.Notification; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EOperation; |
| import org.eclipse.ocl.ecore.CollectionItem; |
| import org.eclipse.ocl.ecore.CollectionLiteralExp; |
| import org.eclipse.ocl.ecore.CollectionRange; |
| import org.eclipse.ocl.ecore.CollectionType; |
| 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.PropertyCallExp; |
| import org.eclipse.ocl.ecore.TupleLiteralExp; |
| import org.eclipse.ocl.ecore.Variable; |
| import org.eclipse.ocl.ecore.VariableExp; |
| import org.eclipse.ocl.ecore.opposites.OppositeEndFinder; |
| import org.eclipse.ocl.examples.impactanalyzer.filterSynthesis.FilterSynthesisImpl; |
| import org.eclipse.ocl.examples.impactanalyzer.impl.OperationBodyToCallMapper; |
| import org.eclipse.ocl.examples.impactanalyzer.instanceScope.unusedEvaluation.UnusedEvaluationRequestFactory; |
| import org.eclipse.ocl.examples.impactanalyzer.instanceScope.unusedEvaluation.UnusedEvaluationRequestSet; |
| import org.eclipse.ocl.examples.impactanalyzer.instanceScope.unusedEvaluation.UnusedEvaluationRequestSet.UnusedEvaluationResult; |
| import org.eclipse.ocl.examples.impactanalyzer.util.AnnotatedEObject; |
| import org.eclipse.ocl.examples.impactanalyzer.util.OCLFactory; |
| import org.eclipse.ocl.examples.impactanalyzer.util.OclHelper; |
| import org.eclipse.ocl.examples.impactanalyzer.util.OperationCallExpKeyedSet; |
| import org.eclipse.ocl.utilities.PredefinedType; |
| |
| |
| |
| /** |
| * 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 traceback |
| * step learns about the value of the variable only in one particular dynamic scope. |
| * <p> |
| */ |
| public class VariableTracebackStep extends BranchingTracebackStep<VariableExp> { |
| /** |
| * Tells if this step, when executed, will return the <code>fromObject</code> unmodified. This is the case if |
| * the variable expression refers to a <code>self</code> variable outside of an operation body. If set to <code>true</code>, |
| * the {@link #steps} are ignored. |
| */ |
| private boolean identity = false; |
| |
| private final Variable variable; |
| private final OppositeEndFinder oppositeEndFinder; |
| private final boolean variableHasCollectionType; |
| |
| public VariableTracebackStep(VariableExp sourceExpression, EClass context, |
| OperationBodyToCallMapper operationBodyToCallMapper, Stack<String> tupleLiteralNamesToLookFor, |
| TracebackStepCache tracebackStepCache, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory, OCLFactory oclFactory) { |
| super(sourceExpression, tupleLiteralNamesToLookFor, tracebackStepCache.getOppositeEndFinder(), operationBodyToCallMapper, unusedEvaluationRequestFactory, oclFactory); |
| oppositeEndFinder = tracebackStepCache.getOppositeEndFinder(); |
| variable = (Variable) sourceExpression.getReferredVariable(); |
| variableHasCollectionType = variable.getType() instanceof CollectionType; |
| // enter step into cache already to let it be found during recursive lookups |
| tracebackStepCache.put(sourceExpression, tupleLiteralNamesToLookFor, this); |
| if (isSelf()) { |
| getSteps().addAll(tracebackSelf(sourceExpression, context, tracebackStepCache, operationBodyToCallMapper, tupleLiteralNamesToLookFor)); |
| } else if (isIteratorVariable()) { |
| getSteps().addAll(tracebackIteratorVariable(sourceExpression, context, tracebackStepCache, operationBodyToCallMapper, tupleLiteralNamesToLookFor)); |
| } else if (isIterateResultVariable()) { |
| getSteps().addAll(tracebackIterateResultVariable(sourceExpression, context, tracebackStepCache, operationBodyToCallMapper, tupleLiteralNamesToLookFor)); |
| } else if (isLetVariable()) { |
| getSteps().addAll(tracebackLetVariable(sourceExpression, context, tracebackStepCache, operationBodyToCallMapper, tupleLiteralNamesToLookFor)); |
| } else if (isOperationParameter()) { |
| getSteps().addAll(tracebackOperationParameter(sourceExpression, context, tracebackStepCache, operationBodyToCallMapper, tupleLiteralNamesToLookFor)); |
| } 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: " |
| + variable.getName()); |
| } |
| } |
| |
| @Override |
| protected OperationCallExpKeyedSet performSubsequentTraceback(AnnotatedEObject source, |
| UnusedEvaluationRequestSet pendingUnusedEvalRequests, |
| org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache tracebackCache, |
| Notification changeEvent) { |
| OperationCallExpKeyedSet result; |
| // don't assign collection-type variables because having inferred one value of the collection doesn't |
| // tell us the complete variable value; simple example that would fail otherwise: |
| // "v->size()" would evaluate to 1 instead of whatever the size of the full collection would have been. |
| // Don't need to check configuration here for unused-checks being active; pendingUnusedEvalRequests will be null |
| // if unused checks are not activated. |
| if (pendingUnusedEvalRequests != null && !variableHasCollectionType) { |
| UnusedEvaluationResult unusedResult = pendingUnusedEvalRequests.setVariable(variable, source.getAnnotatedObject(), |
| oppositeEndFinder, tracebackCache, oclFactory); |
| if (unusedResult.hasProvenUnused()) { |
| provenUnused++; |
| result = tracebackCache.getOperationCallExpKeyedSetFactory().emptySet(); |
| } else { |
| result = perform(source, unusedResult.getNewRequestSet(), tracebackCache, changeEvent); |
| } |
| } else { |
| result = perform(source, null, tracebackCache, changeEvent); |
| } |
| return result; |
| } |
| |
| private OperationCallExpKeyedSet perform(AnnotatedEObject source, |
| UnusedEvaluationRequestSet pendingUnusedEvalRequests, |
| org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache tracebackCache, Notification changeEvent) { |
| OperationCallExpKeyedSet result; |
| if (identity) { |
| result = tracebackCache.getOperationCallExpKeyedSetFactory().createOperationCallExpKeyedSet(source); |
| } else { |
| result = super.performSubsequentTraceback(source, pendingUnusedEvalRequests, tracebackCache, |
| changeEvent); |
| } |
| return result; |
| } |
| |
| private boolean isLetVariable() { |
| // let variables are contained in the letExp |
| return variable.eContainer() instanceof LetExp && ((LetExp) variable.eContainer()).getVariable() == variable; |
| } |
| |
| private boolean isOperationParameter() { |
| return variable.getRepresentedParameter() != null; |
| } |
| |
| private boolean isIterateResultVariable() { |
| // result variables are contained in iterateExp |
| return (variable.eContainer() instanceof IterateExp && ((IterateExp) variable.eContainer()).getResult() == variable); |
| } |
| |
| private boolean isIteratorVariable() { |
| return (variable.eContainer() instanceof LoopExp && ((LoopExp) variable.eContainer()).getIterator().contains(variable)); |
| } |
| |
| private boolean isSelf() { |
| return variable.getName().equals(EcoreEnvironment.SELF_VARIABLE_NAME); |
| } |
| |
| private Set<TracebackStepAndScopeChange> tracebackOperationParameter(VariableExp variableExp, EClass context, |
| TracebackStepCache tracebackStepCache, OperationBodyToCallMapper operationBodyToCallMapper, Stack<String> tupleLiteralNamesToLookFor) { |
| Set<TracebackStepAndScopeChange> result = new HashSet<TracebackStepAndScopeChange>(); |
| OCLExpression rootExpression = getRootExpression(variableExp); |
| // 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); |
| // 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); |
| // record in this step's getSteps() that the stepForCall belongs to call so that results are keyed |
| // by OperationCallExp objects and an OperationCallTracebackStep can extract the results specific to its call fast; |
| // this is done by passing "call" to createTracebackStepAndScopeChange which records it in the |
| // TracebackStepAndScopeChange object so that when it gets used, it'll store the call as key for the results |
| TracebackStepAndScopeChangeWithOperationCallExp stepWithScopeChange = createTracebackStepAndScopeChange(variableExp, |
| argumentExpression, call, context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache); |
| result.add(stepWithScopeChange); |
| } |
| return result; |
| } |
| |
| /** |
| * 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) { |
| return op.getEParameters().indexOf(variable.getRepresentedParameter()); |
| } |
| |
| private Set<TracebackStepAndScopeChange> tracebackLetVariable(VariableExp variableExpression, EClass context, |
| TracebackStepCache tracebackStepCache, OperationBodyToCallMapper operationBodyToCallMapper, Stack<String> tupleLiteralNamesToLookFor) { |
| OCLExpression initExpression = (OCLExpression) variable.getInitExpression(); |
| TracebackStepAndScopeChange step = createTracebackStepAndScopeChange(variableExpression, initExpression, context, |
| operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache); |
| return Collections.singleton(step); |
| } |
| |
| private Set<TracebackStepAndScopeChange> tracebackIterateResultVariable(VariableExp variableExp, EClass context, |
| TracebackStepCache tracebackStepCache, OperationBodyToCallMapper operationBodyToCallMapper, Stack<String> tupleLiteralNamesToLookFor) { |
| // the init expression can't reference the result variable, therefore no recursive reference may occur here: |
| TracebackStepAndScopeChange stepForInitExpression = createTracebackStepAndScopeChange(variableExp, (OCLExpression) variable |
| .getInitExpression(), context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache); |
| // 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: |
| TracebackStepAndScopeChange stepForBodyExpression = createTracebackStepAndScopeChange(variableExp, |
| (OCLExpression) ((IterateExp) variableExp.getReferredVariable().eContainer()).getBody(), context, |
| operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache); |
| HashSet<TracebackStepAndScopeChange> result = new HashSet<TracebackStepAndScopeChange>(); |
| result.add(stepForBodyExpression); |
| result.add(stepForInitExpression); |
| return result; |
| } |
| |
| private Set<TracebackStepAndScopeChange> tracebackIteratorVariable(VariableExp variableExp, EClass context, |
| TracebackStepCache tracebackStepCache, OperationBodyToCallMapper operationBodyToCallMapper, Stack<String> tupleLiteralNamesToLookFor) { |
| LoopExp loopExp = (LoopExp) variableExp.getReferredVariable().eContainer(); |
| OCLExpression source = (OCLExpression) loopExp.getSource(); |
| TracebackStepAndScopeChange stepForSource = createTracebackStepAndScopeChange( |
| variableExp, source, context, operationBodyToCallMapper, |
| tupleLiteralNamesToLookFor, tracebackStepCache); |
| Set<TracebackStepAndScopeChange> result; |
| if (PredefinedType.CLOSURE_NAME.equals(loopExp.getName())) { |
| // use a branching step, one branch pursuing the source expression, the other tracing |
| // the body expression |
| TracebackStepAndScopeChange stepForBody = createTracebackStepAndScopeChange( |
| variableExp, (OCLExpression) loopExp.getBody(), context, operationBodyToCallMapper, |
| tupleLiteralNamesToLookFor, tracebackStepCache); |
| result = new HashSet<TracebackStepAndScopeChange>(); |
| result.add(stepForSource); |
| result.add(stepForBody); |
| } else { |
| result = Collections.singleton(stepForSource); |
| } |
| return result; |
| } |
| |
| private Set<TracebackStepAndScopeChange> tracebackSelf(VariableExp variableExp, EClass context, |
| TracebackStepCache tracebackStepCache, OperationBodyToCallMapper operationBodyToCallMapper, Stack<String> tupleLiteralNamesToLookFor) { |
| Set<TracebackStepAndScopeChange> result; |
| OCLExpression rootExpression = getRootExpression(variableExp); |
| EOperation op = getOperationOfWhichRootExpressionIsTheBody(rootExpression, operationBodyToCallMapper); |
| Collection<PropertyCallExp> derivedPropertyCalls = ((FilterSynthesisImpl)operationBodyToCallMapper).getDerivedProperties().get(rootExpression); |
| if (op != null) { |
| result = new HashSet<TracebackStepAndScopeChange>(); |
| // in an operation, self needs to be traced back to all source expressions of |
| // calls to that operation |
| Collection<OperationCallExp> calls = operationBodyToCallMapper.getCallsOf(rootExpression); |
| for (OperationCallExp call : calls) { |
| OCLExpression callSource = (OCLExpression) call.getSource(); |
| // record in this step's getSteps() that the stepForCall belongs to call so that results are keyed |
| // by OperationCallExp objects and an OperationCallTracebackStep can extract the results specific to its call fast; |
| // this is done by passing "call" to createTracebackStepAndScopeChange which records it in the |
| // TracebackStepAndScopeChange object so that when it gets used, it'll store the call as key for the results |
| TracebackStepAndScopeChangeWithOperationCallExp stepForCall = createTracebackStepAndScopeChange(variableExp, |
| callSource, call, context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache); |
| result.add(stepForCall); |
| } |
| } |
| else if (derivedPropertyCalls != null) { |
| result = new HashSet<TracebackStepAndScopeChange>(); |
| for (PropertyCallExp call : derivedPropertyCalls) { |
| OCLExpression callSource = (OCLExpression) call.getSource(); |
| TracebackStepAndScopeChange stepForCall = createTracebackStepAndScopeChange(variableExp, |
| callSource, /*call, */context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache); |
| result.add(stepForCall); |
| } |
| } else { |
| // self occurred outside of an operation & derivation expression; it evaluates to s for s being the context |
| identity = true; |
| result = Collections.emptySet(); |
| } |
| return result; |
| } |
| |
| private EOperation getOperationOfWhichRootExpressionIsTheBody(OCLExpression potentialBody, |
| 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(potentialBody); |
| EOperation op = null; |
| if (!filterSynthesizerCallCache.isEmpty()) { |
| op = filterSynthesizerCallCache.iterator().next().getReferredOperation(); |
| } |
| return op; |
| } |
| |
| /** |
| * There are a few known idiosyncrasies in the OCL "composition" hierarchy. A {@link TupleLiteralExp} does not contain its |
| * {@link TupleLiteralExp#getPart() tuple parts} which are variable declarations, a {@link CollectionLiteralExp} does not |
| * contain its {@link CollectionLiteralExp#getPart() parts}, and of those parts, none of {@link CollectionRange} nor |
| * {@link CollectionItem} contains the expressions that it uses to describe itself. |
| * <p> |
| * |
| * We still need to be able to determine the scope of, e.g., <tt>self</tt> or operation parameters and therefore need to |
| * ascend what may be called the "logical composition hierarchy" of an OCL expression. Therefore, this operation ascends the |
| * real composition hierarchy until it finds a <tt>null</tt> parent or a parent of type constraint or EAnnotation. |
| * |
| * In this case, it tries the aforementioned "logical compositions" one after the other. If for one such association another |
| * element is found, ascending continues there. |
| */ |
| protected OCLExpression getRootExpression(OCLExpression e) { |
| return OclHelper.getRootExpression(e); |
| } |
| |
| } |