blob: e7313d4cb668ccd2857522ee2a9cac0a27f15144 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2018 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* SAP AG - initial API and implementation
******************************************************************************/
package org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback;
import java.util.ArrayList;
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.EClassifier;
import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.ocl.ecore.CollectionType;
import org.eclipse.ocl.ecore.EcorePackage;
import org.eclipse.ocl.ecore.IfExp;
import org.eclipse.ocl.ecore.IterateExp;
import org.eclipse.ocl.ecore.LetExp;
import org.eclipse.ocl.ecore.LoopExp;
import org.eclipse.ocl.ecore.OCL;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.ecore.OperationCallExp;
import org.eclipse.ocl.ecore.TupleType;
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.configuration.OptimizationActivation;
import org.eclipse.ocl.examples.impactanalyzer.deltaPropagation.PartialEvaluatorImpl;
import org.eclipse.ocl.examples.impactanalyzer.impl.OperationBodyToCallMapper;
import org.eclipse.ocl.examples.impactanalyzer.instanceScope.unusedEvaluation.UnusedEvaluationRequest;
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.HighlightingToStringVisitor;
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.examples.impactanalyzer.util.Tuple.Pair;
public abstract class AbstractTracebackStep<E extends OCLExpression> implements TracebackStep {
/**
* If set to a non-<code>null</code> class, this step asserts that if the source objects passed to its
* {@link #traceback(AnnotatedEObject, UnusedEvaluationRequestSet, TracebackCache, Notification)}
* operation are not compatible to that
* type, then the result set will be empty.
*/
protected EClass requiredType;
/**
* The OCL expression for which this is the traceback step
*/
private final E expression;
/**
* Descriptions of the partial evaluations to perform when executing this step, in order to try to prove that the
* {@link #expression} is unused by the particular change currently being traced back.
*
* @see #determineUnusedEvaluationRequests(OCLExpression, OperationBodyToCallMapper, UnusedEvaluationRequestFactory)
*/
private final Set<UnusedEvaluationRequest> unusedEvaluationRequests;
/**
* To avoid endless recursions, a step remembers for which combinations of <code>source</code> objects and
* {@link UnusedEvaluationRequestSet}s it is currently executing. If any of those combinations is to be evaluated
* again while already being evaluated by the current thread, an empty set is returned. It uses the two attributes
* {@link #currentlyEvaluatingTracebackFor} and {@link #listOfKeysCurrentlyEvaluatingTracebackFor} for this purpose to
* be memory-economic. If not evaluating for any key, both are <code>null</code>. If evaluating for a single key,
* that key is stored in {@link #currentlyEvaluatingTracebackFor}. If evaluating for multiple objects,
* {@link #currentlyEvaluatingTracebackFor} is <code>null</code>, and {@link #listOfKeysCurrentlyEvaluatingTracebackFor}
* holds a collection of keys for which the step is currently evaluating.<p>
*
* @see #startEvaluationFor(Pair)
* @see #finishedEvaluationFor(Pair)
* @see #isCurrentlyEvaluatingFor(Pair)
*/
private Pair<AnnotatedEObject, UnusedEvaluationRequestSet> currentlyEvaluatingTracebackFor;
private Collection<Pair<AnnotatedEObject, UnusedEvaluationRequestSet>> listOfKeysCurrentlyEvaluatingTracebackFor;
private final OppositeEndFinder oppositeEndFinder;
/**
* Used in "debug" mode, storing this step's annotation string to be used in {@link AnnotatedEObject}s to explain how
* a result was inferred.
*/
private final String annotation;
protected final OCLFactory oclFactory;
public static int tracebackExecutions = 0;
public static int provenUnused = 0;
/**
* Encapsulates the scope change that has to happen before invoking a subsequent traceback step.
* @author Axel Uhl (D043530)
*/
public static class TracebackStepAndScopeChange implements TracebackStep {
private final TracebackStep step;
private final Set<Variable> variablesThatLeaveOrEnterScopeWhenCallingStep;
public TracebackStepAndScopeChange(TracebackStep step, Set<Variable> variablesThatLeaveOrEnterScopeWhenCallingStep) {
this.step = step;
this.variablesThatLeaveOrEnterScopeWhenCallingStep = variablesThatLeaveOrEnterScopeWhenCallingStep;
}
public OperationCallExpKeyedSet traceback(AnnotatedEObject source, UnusedEvaluationRequestSet pendingUnusedEvalRequests,
org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache tracebackCache, Notification changeEvent) {
UnusedEvaluationRequestSet reducedUnusedEvaluationRequestSet;
if (tracebackCache.getConfiguration().isUnusedDetectionActive()) {
reducedUnusedEvaluationRequestSet = pendingUnusedEvalRequests == null ? null
: pendingUnusedEvalRequests.createReducedSet(variablesThatLeaveOrEnterScopeWhenCallingStep,
tracebackCache.getUnusedEvaluationRequestFactory());
} else {
reducedUnusedEvaluationRequestSet = null;
}
return step.traceback(source, reducedUnusedEvaluationRequestSet, tracebackCache, changeEvent);
}
public String toString() {
StringBuilder result = new StringBuilder("Unscope ");
for (Variable v : variablesThatLeaveOrEnterScopeWhenCallingStep) {
result.append(v.getName());
result.append(", ");
}
result.append("then perform ");
result.append(step);
return result.toString();
}
}
/**
* When executed using the {@link #traceback(AnnotatedEObject, UnusedEvaluationRequestSet, TracebackCache, Notification)} method,
* results will be keyed with {@link #callToWhichResultsAreSpecific} because the {@link TracebackStep} encapsulated by this object
* navigates back to the specified call expression, and there either to the source or an argument expression.
*
* @see OperationCallExpKeyedSet
* @author Axel Uhl (D043530)
*
*/
protected static class TracebackStepAndScopeChangeWithOperationCallExp extends TracebackStepAndScopeChange {
private final OperationCallExp callToWhichResultsAreSpecific;
/**
* Remembers <code>call</code> as the only operation call through which
* @param step
* @param variablesChangingScope
* @param callToWhichResultsAreSpecific
*/
public TracebackStepAndScopeChangeWithOperationCallExp(TracebackStep step, Set<Variable> variablesChangingScope,
OperationCallExp callToWhichResultsAreSpecific) {
super(step, variablesChangingScope);
this.callToWhichResultsAreSpecific = callToWhichResultsAreSpecific;
}
public OperationCallExpKeyedSet traceback(AnnotatedEObject source, UnusedEvaluationRequestSet pendingUnusedEvalRequests,
org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache tracebackCache,
Notification changeEvent) {
OperationCallExpKeyedSet resultsForSourceOrArgument = super.traceback(source, pendingUnusedEvalRequests,
tracebackCache, changeEvent);
OperationCallExpKeyedSet result = tracebackCache.getOperationCallExpKeyedSetFactory().createOperationCallExpKeyedSet(
callToWhichResultsAreSpecific, resultsForSourceOrArgument);
return result;
}
public String toString() {
StringBuilder result = new StringBuilder(super.toString());
result.append(", then filter for results specific to OperationCallExp ");
result.append(callToWhichResultsAreSpecific);
return result.toString();
}
}
/**
* If the expression's type for which this traceback step is constructed is class-like, {@link #requiredType} is set to the
* expression's type.
* @param tupleLiteralNamesToLookFor
* if a tuple part is being sought, the expression type will be a tuple type; in this case, extract the sought
* part's type as the {@link #requiredType}.
*/
protected AbstractTracebackStep(E sourceExpression, Stack<String> tupleLiteralNamesToLookFor,
OppositeEndFinder oppositeEndFinder, OperationBodyToCallMapper operationBodyToCallMapper,
UnusedEvaluationRequestFactory unusedEvaluationRequestFactory, OCLFactory oclFactory) {
this.expression = sourceExpression;
this.oppositeEndFinder = oppositeEndFinder;
this.oclFactory = oclFactory;
EClassifier type = sourceExpression.getType();
requiredType = getInnermostTypeConsideringTupleLiteralsLookedFor(tupleLiteralNamesToLookFor, type);
if (OptimizationActivation.getOption().isUnusedDetectionActive()) {
unusedEvaluationRequests = determineUnusedEvaluationRequests(getExpression(), operationBodyToCallMapper,
unusedEvaluationRequestFactory);
} else {
unusedEvaluationRequests = null;
}
annotation = getVerboseDebugInfo(operationBodyToCallMapper);
}
/**
* There are a few rules for determining, whether a sub-expression is unused under a given set of variable values.
* <ul>
* <li><b>Unused branch in {@link IfExp}</b>: The {@link IfExp#getThenExpression() then}-expression of an {@link IfExp} is
* unused if the {@link IfExp#getCondition() condition} of that {@link IfExp} evaluates to <code>false</code>. Analogously,
* the {@link IfExp#getElseExpression() else}-expression of an {@link IfExp} is unused if the {@link IfExp#getCondition()
* condition} of that {@link IfExp} evaluates to <code>true</code>.</li>
*
* <li><b>Unused {@link LetExp let} declaration</b>: the {@link Variable#getInitExpression() initialization expression} for a
* <code>let</code>-variable is unused if all {@link VariableExp} expressions referring to that variable are unused within the
* {@link LetExp#getIn() in}-expression.</li>
*
* <li><b>Unused {@link OperationCallExp#getArgument() argument} expression of an operation call</b>: the argument of an
* operation call is unused if all {@link VariableExp} expression in the operation body referring to the corresponding formal
* parameter are unused.</li>
*
* <li><b>Unused body of loop over empty collection</b>: The body expression of any {@link LoopExp} expression won't be used
* if the {@link LoopExp#getSource() source} of the loop expression evaluates to an empty collection.</li>
*
* <li><b>Unused element of ordered collection literal in conjunction with <code>-&gt;at(...)</code></b>: When the
* <code>-&gt;at(...)</code> standard library operation is applied to an ordered collection literal and it is possible to
* evaluate the argument of the <code>at</code> call, all literal items at other positions than the one identified by the
* argument are unused.</li>
*
* <li><b>Composition rule</b>: An expression is unused if its {@link EObject#eContainer() containing expression} is unused.</li>
* </ul>
*
* Eventually, unused checks perform one or more {@link PartialEvaluatorImpl (partial) evaluations} of expressions which are
* reached by navigation across the OCL expression's AST, starting from the expression whose "unusedness" is to be proven.
* Similar to the scope changes implied by the AST navigations during
* {@link #traceback(AnnotatedEObject, UnusedEvaluationRequestSet, TracebackCache, Notification)}, these navigations leave and
* enter dynamic variable scopes. Once a variable's scope is left or a new one entered, we currently don't assume that the
* {@link #traceback(AnnotatedEObject, UnusedEvaluationRequestSet, TracebackCache, Notification)} calculation will be able
* reliably perform dynamic scope matching. Therefore, we don't expect to be able to infer a variable's value anymore, once
* its dynamic scope has been left or (re-)entered.
* <p>
*
* Therefore, this method produces {@link UnusedEvaluationRequest}s which hold slots only for variables that remained in scope
* during the AST navigation from the <code>sourceExpression</code> to the expression to be evaluated during the "unused"
* check. They are stored in the {@link TracebackStep} whose expression's unusedness they shall prove. When a step is executed
* by invoking its {@link #traceback(AnnotatedEObject, UnusedEvaluationRequestSet, TracebackCache, Notification)} method,
* these requests are {@link UnusedEvaluationRequest#evaluate(org.eclipse.ocl.ecore.opposites.OppositeEndFinder, OCL) evaluated}.
* using {@link UnusedEvaluationRequestSet#evaluate(Set, org.eclipse.ocl.ecore.opposites.OppositeEndFinder, TracebackCache, OCL)}. If this proves
* that the expression is unused, an empty set can be returned right away. Otherwise, the follow-up
* {@link UnusedEvaluationRequestSet} delivered via {@link UnusedEvaluationResult#getNewRequestSet()} is merged with the one
* passed to {@link #traceback(AnnotatedEObject, UnusedEvaluationRequestSet, TracebackCache, Notification)}.
* <p>
*
* It may happen that a <code>let</code>-variable is unknown during a partial evaluation attempt but that its initialization
* expression can successfully be evaluated with the variable values inferred. In this case, the <code>let</code>-variable is
* <em>indirectly inferred</em>. Similarly, a formal operation parameter may be unknown while trying to prove that it or
* another formal parameter is unused. However, these types of unused checks for formal operation parameters may be performed
* based on the "Unused {@link OperationCallExp#getArgument() argument} expression of an operation call" rule. In this case,
* the argument expressions are known and an attempt can be made to evaluate them. If such an evaluation succeeds, the
* evaluation result represents the inferred value of the corresponding formal parameter.
* @return the set of evaluation requests with which to try to prove unusedness of the {@link #expression}
*/
private Set<UnusedEvaluationRequest> determineUnusedEvaluationRequests(OCLExpression e,
OperationBodyToCallMapper operationBodyToCallMapper, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory) {
Set<UnusedEvaluationRequest> result = new HashSet<UnusedEvaluationRequest>();
OCLExpression root = OclHelper.getRootExpression(e);
Set<Variable> variablesInScope = getAllVariablesInScope(e, operationBodyToCallMapper);
return determineUnusedEvaluationRequests(e, root, variablesInScope, operationBodyToCallMapper, result, unusedEvaluationRequestFactory);
}
/**
* @param upTo during following the composition rule, ascends up to this expression and no further
* @return <code>result</code>
*/
private Set<UnusedEvaluationRequest> determineUnusedEvaluationRequests(OCLExpression e, OCLExpression upTo,
Set<Variable> variablesInScope, OperationBodyToCallMapper operationBodyToCallMapper, Set<UnusedEvaluationRequest> result, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory) {
UnusedEvaluationRequest thenClauseRequest = getThenClauseUnusedCheckRequest(e, variablesInScope, operationBodyToCallMapper, unusedEvaluationRequestFactory);
if (thenClauseRequest != null) {
result.add(thenClauseRequest);
} else {
UnusedEvaluationRequest elseClauseRequest = getElseClauseUnusedCheckRequest(e, variablesInScope, operationBodyToCallMapper, unusedEvaluationRequestFactory);
if (elseClauseRequest != null) {
result.add(elseClauseRequest);
} else {
UnusedEvaluationRequest loopBodyRequest = getLoopBodyUnusedCheckRequest(e, variablesInScope, operationBodyToCallMapper, unusedEvaluationRequestFactory);
if (loopBodyRequest != null) {
result.add(loopBodyRequest);
} else {
Set<UnusedEvaluationRequest> letVariableInitRequests = getLetVariableInitUnusedCheckRequests(e, variablesInScope, operationBodyToCallMapper, oppositeEndFinder, unusedEvaluationRequestFactory);
if (letVariableInitRequests != null) {
result.addAll(letVariableInitRequests);
} else {
Set<UnusedEvaluationRequest> operationArgumentRequests = getOperationArgumentUnusedCheckRequests(e, variablesInScope, operationBodyToCallMapper);
if (operationArgumentRequests != null) {
result.addAll(operationArgumentRequests);
} else {
UnusedEvaluationRequest collectionLiteralWithAtRequest = getCollectionLiteralWithAtUnusedCheckRequest(e, variablesInScope, operationBodyToCallMapper);
if (collectionLiteralWithAtRequest != null) {
result.add(collectionLiteralWithAtRequest);
}
}
}
}
}
}
Set<UnusedEvaluationRequest> compositeParentRequests = getCompositeParentUnusedCheckRequests(e, upTo,
variablesInScope, new HashSet<UnusedEvaluationRequest>(), operationBodyToCallMapper, unusedEvaluationRequestFactory);
result.addAll(compositeParentRequests);
if (result.size() > 0) {
return result;
} else {
return null;
}
}
/**
* @param e the expression for which to investigate unusedness
* @param upTo during following the composition rule, ascends up to this expression and no further
* @param result a set to which to add the results and which to return
* @return <code>requestsToAddTo</code>
*/
private Set<UnusedEvaluationRequest> getCompositeParentUnusedCheckRequests(OCLExpression e, OCLExpression upTo,
Set<Variable> variablesInScope, Set<UnusedEvaluationRequest> result, OperationBodyToCallMapper operationBodyToCallMapper, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory) {
EObject container = e.eContainer();
while (container != null && !(container instanceof OCLExpression)) {
container = container.eContainer();
}
if (container != null && container instanceof OCLExpression) {
Set<Variable> variablesLeavingScope = getVariablesScopedByExpression(e, operationBodyToCallMapper);
Set<Variable> remainingVariablesInScope = new HashSet<Variable>(variablesInScope);
remainingVariablesInScope.removeAll(variablesLeavingScope);
determineUnusedEvaluationRequests((OCLExpression) container, upTo, remainingVariablesInScope,
operationBodyToCallMapper, result, unusedEvaluationRequestFactory);
}
return result;
}
private UnusedEvaluationRequest getCollectionLiteralWithAtUnusedCheckRequest(OCLExpression e, Set<Variable> variablesInScope,
OperationBodyToCallMapper operationBodyToCallMapper) {
// TODO Implement AbstractTracebackStep.getCollectionLiteralWithAtUnusedCheckRequest(...)
return null;
}
private Set<UnusedEvaluationRequest> getOperationArgumentUnusedCheckRequests(OCLExpression e, Set<Variable> variablesInScope,
OperationBodyToCallMapper operationBodyToCallMapper) {
// TODO Implement AbstractTracebackStep.getOperationArgumentUnusedCheckRequests(...)
return null;
}
private Set<UnusedEvaluationRequest> getLetVariableInitUnusedCheckRequests(OCLExpression e, Set<Variable> variablesInScope,
OperationBodyToCallMapper operationBodyToCallMapper, OppositeEndFinder oppositeEndFinder, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory) {
Set<UnusedEvaluationRequest> result = new HashSet<UnusedEvaluationRequest>();
EObject container = e.eContainer();
if (container != null && container instanceof Variable && ((Variable) container).getInitExpression() == e) {
EObject letCandidate = container.eContainer();
if (letCandidate instanceof LetExp && ((LetExp) letCandidate).getIn() == e) {
Collection<EObject> variableExps = oppositeEndFinder.navigateOppositePropertyWithBackwardScope(
(EReference) EcorePackage.eINSTANCE.getVariableExp().getEStructuralFeature(
EcorePackage.VARIABLE_EXP__REFERRED_VARIABLE), container);
for (EObject ve : variableExps) {
VariableExp variableExp = (VariableExp) ve;
Set<Variable> variableScopeChange = getVariablesChangingScope(e, variableExp, operationBodyToCallMapper);
Set<Variable> newScope = new HashSet<Variable>(variablesInScope);
newScope.removeAll(variableScopeChange);
determineUnusedEvaluationRequests(variableExp,
/* up to */ (OCLExpression) ((LetExp) letCandidate).getIn(),
newScope, operationBodyToCallMapper, result, unusedEvaluationRequestFactory);
}
}
}
return result;
}
private UnusedEvaluationRequest getLoopBodyUnusedCheckRequest(OCLExpression e, Set<Variable> variablesInScope,
OperationBodyToCallMapper operationBodyToCallMapper, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory) {
UnusedEvaluationRequest result = null;
EObject container = e.eContainer();
if (container instanceof LoopExp && ((LoopExp) container).getBody() == e) {
Set<Variable> variablesInScopeReducedByIteratorsAndResult = new HashSet<Variable>(variablesInScope);
variablesInScopeReducedByIteratorsAndResult.removeAll(((LoopExp) container).getIterator());
if (container instanceof IterateExp) {
Variable resultVariable = (Variable) ((IterateExp) container).getResult();
if (resultVariable != null) {
variablesInScopeReducedByIteratorsAndResult.remove(resultVariable);
}
}
result = unusedEvaluationRequestFactory.getUnusedEvaluationRequest((OCLExpression) ((LoopExp) container).getSource(),
/* unused if */ null /* meaning empty */, null, variablesInScopeReducedByIteratorsAndResult);
}
return result;
}
private UnusedEvaluationRequest getElseClauseUnusedCheckRequest(OCLExpression e, Set<Variable> variablesInScope,
OperationBodyToCallMapper operationBodyToCallMapper, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory) {
UnusedEvaluationRequest result = null;
EObject container = e.eContainer();
if (container instanceof IfExp && ((IfExp) container).getElseExpression() == e) {
result = unusedEvaluationRequestFactory.getUnusedEvaluationRequest((OCLExpression) ((IfExp) container).getCondition(), /* unused if */ true, null,
variablesInScope);
}
return result;
}
private UnusedEvaluationRequest getThenClauseUnusedCheckRequest(OCLExpression e, Set<Variable> variablesInScope,
OperationBodyToCallMapper operationBodyToCallMapper, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory) {
UnusedEvaluationRequest result = null;
EObject container = e.eContainer();
if (container instanceof IfExp && ((IfExp) container).getThenExpression() == e) {
result = unusedEvaluationRequestFactory.getUnusedEvaluationRequest((OCLExpression) ((IfExp) container).getCondition(), /* unused if */ false, null,
variablesInScope);
}
return result;
}
/**
* @return the expression for which this step is responsible
*/
public E getExpression() {
return expression;
}
protected EClass getInnermostTypeConsideringTupleLiteralsLookedFor(Stack<String> tupleLiteralNamesToLookFor, EClassifier type) {
EClass result;
if (tupleLiteralNamesToLookFor == null || tupleLiteralNamesToLookFor.isEmpty()) {
result = getInnermostClass(type);
} else {
EClassifier currentType = getInnermostElementType(type);
int i=tupleLiteralNamesToLookFor.size()-1;
while (i>=0) {
TupleType tt = (TupleType) currentType;
EStructuralFeature part = tt.getEStructuralFeature(tupleLiteralNamesToLookFor.get(i));
currentType = getInnermostClass(part.getEType());
i--;
}
result = (EClass) currentType;
}
return result;
}
public OperationCallExpKeyedSet traceback(AnnotatedEObject source, UnusedEvaluationRequestSet pendingUnusedEvalRequests,
org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache tracebackCache, Notification changeEvent) {
tracebackExecutions++;
OperationCallExpKeyedSet result;
Pair<AnnotatedEObject, UnusedEvaluationRequestSet> key = new Pair<AnnotatedEObject, UnusedEvaluationRequestSet>(source, pendingUnusedEvalRequests);
if (isCurrentlyEvaluatingFor(key)) {
result = tracebackCache.getOperationCallExpKeyedSetFactory().emptySet();
} else {
try {
startEvaluationFor(key);
result = tracebackCache.get(this, source, pendingUnusedEvalRequests);
if (result == null) {
if (requiredType != null && !requiredType.isInstance(source.getAnnotatedObject())) {
result = tracebackCache.getOperationCallExpKeyedSetFactory().emptySet();
} else {
if (tracebackCache.getConfiguration().isUnusedDetectionActive()) {
// try to prove unusedness with the unused rules recorded for this step; if that fails,
// merge the unused evaluation requests that failed for unknown variables
// with the ones passed in pendingUnusedEvalRequests and carry on
UnusedEvaluationResult unusedEvaluationResult = UnusedEvaluationRequestSet.evaluate(
unusedEvaluationRequests, oppositeEndFinder, tracebackCache, oclFactory);
if (unusedEvaluationResult.hasProvenUnused()) {
provenUnused++;
result = tracebackCache.getOperationCallExpKeyedSetFactory().emptySet();
} else {
result = performSubsequentTraceback(source,
unusedEvaluationResult.getNewRequestSet() == null ? null : unusedEvaluationResult
.getNewRequestSet().merge(pendingUnusedEvalRequests), tracebackCache, changeEvent);
}
} else {
result = performSubsequentTraceback(source, null, tracebackCache, changeEvent);
}
}
tracebackCache.put(this, source, pendingUnusedEvalRequests, result);
}
} finally {
finishedEvaluationFor(key);
}
}
return result;
}
private boolean isCurrentlyEvaluatingFor(Pair<AnnotatedEObject, UnusedEvaluationRequestSet> key) {
return listOfKeysCurrentlyEvaluatingTracebackFor != null
? listOfKeysCurrentlyEvaluatingTracebackFor.contains(key)
: key.equals(currentlyEvaluatingTracebackFor);
}
private void finishedEvaluationFor(Pair<AnnotatedEObject, UnusedEvaluationRequestSet> key) {
if (listOfKeysCurrentlyEvaluatingTracebackFor != null) {
if (listOfKeysCurrentlyEvaluatingTracebackFor.size() == 1) {
listOfKeysCurrentlyEvaluatingTracebackFor = null;
} else {
listOfKeysCurrentlyEvaluatingTracebackFor.remove(key);
}
} else {
currentlyEvaluatingTracebackFor = null;
}
}
private void startEvaluationFor(Pair<AnnotatedEObject, UnusedEvaluationRequestSet> key) {
if (listOfKeysCurrentlyEvaluatingTracebackFor != null) {
listOfKeysCurrentlyEvaluatingTracebackFor.add(key);
} else {
if (currentlyEvaluatingTracebackFor == null) {
currentlyEvaluatingTracebackFor = key;
} else {
listOfKeysCurrentlyEvaluatingTracebackFor = new ArrayList<Pair<AnnotatedEObject, UnusedEvaluationRequestSet>>();
listOfKeysCurrentlyEvaluatingTracebackFor.add(currentlyEvaluatingTracebackFor);
currentlyEvaluatingTracebackFor = null;
listOfKeysCurrentlyEvaluatingTracebackFor.add(key);
}
}
}
/**
* This method is used to invoke the {@link TracebackStep#traceback(AnnotatedEObject, UnusedEvaluationRequestSet, TracebackCache, Notification)} method on all necessary subsequent {@link TracebackStep}s and return their results.
* Which subsequent steps are necessary depends on the respective <code>source</code> {@link OCLExpression} the {@link TracebackStep} was created for.
*/
protected abstract OperationCallExpKeyedSet performSubsequentTraceback(AnnotatedEObject source,
UnusedEvaluationRequestSet pendingUnusedEvalRequests, org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache tracebackCache, Notification changeEvent);
/**
* @return a new set that the caller therefore may modify at will without causing any harm
*/
private Set<Variable> getVariablesChangingScope(OCLExpression sourceExpression, OCLExpression targetExpression,
OperationBodyToCallMapper operationBodyToCallMapper) {
Set<Variable> result;
OCLExpression commonCompositionParent = commonCompositionParent(sourceExpression, targetExpression);
if (commonCompositionParent == null) {
result = new HashSet<Variable>();
result.addAll(getAllVariablesInScope(sourceExpression, operationBodyToCallMapper));
result.addAll(getAllVariablesInScope(targetExpression, operationBodyToCallMapper));
} else {
result = variablesIntroducedBetween(sourceExpression, commonCompositionParent, operationBodyToCallMapper);
result.addAll(variablesIntroducedBetween(targetExpression, commonCompositionParent, operationBodyToCallMapper));
}
return result;
}
/**
* This method returns all variables introduced by <code>origin</code> or any of its containers, up to but excluding
* <code>parent</code>. For example, if <code>parent</code> is the <code>in</code>-expression of a {@link LetExp} in which a
* {@link LoopExp} is nested whose {@link LoopExp#getBody() body} contains <code>origin</code>, then the
* {@link LoopExp#getIterator() iterator variables} will be part of the result, but the <code>let</code>-variable to which the
* <code>in</code>-expression belongs will not be because it is immediately scoped by <code>parent</code>.
*
* @param origin
* The {@link OCLExpression} used as the origin of the search.
* @param parent
* The {@link OCLExpression} that is an immediate or transitive containment parent of the origin (see
* {@link EObject#eContainer()}). Variables immediately scoped by <code>parent</code> are included in the result.
* @return A {@link Set} of {@link OCLExpression}s containing all scope creating expressions in the containment hierarchy
* between origin and parent. The set returned is new and may be modified by the caller without causing any harm.
*/
private Set<Variable> variablesIntroducedBetween(OCLExpression origin, OCLExpression parent,
OperationBodyToCallMapper operationBodyToCallMapper) {
EObject e = origin;
Set<Variable> result = new HashSet<Variable>();
while (e != null && e != parent) {
if (e instanceof OCLExpression) {
result.addAll(getVariablesScopedByExpression((OCLExpression) e, operationBodyToCallMapper));
}
e = e.eContainer();
}
return result;
}
protected static Set<Variable> getVariablesScopedByExpression(OCLExpression e, OperationBodyToCallMapper operationBodyToCallMapper) {
EObject container = e.eContainer();
Set<Variable> result = null;
if (container instanceof LoopExp && ((LoopExp) container).getBody() == e) {
// body of a loop expression
result = new HashSet<Variable>();
for (org.eclipse.ocl.expressions.Variable<EClassifier, EParameter> v : ((LoopExp) container).getIterator()) {
result.add((Variable) v);
}
if (container instanceof IterateExp) {
Variable resultVariable = (Variable) ((IterateExp) container).getResult();
if (resultVariable != null) {
result.add(resultVariable);
}
}
} else if (container instanceof LetExp && ((LetExp) container).getIn() == e) {
if (result == null) {
result = new HashSet<Variable>();
}
// in-expression of a let-expression
result.add((Variable) ((LetExp) container).getVariable());
} else {
Set<OperationCallExp> calls = operationBodyToCallMapper.getCallsOf(e);
if (!calls.isEmpty()) {
// body of an operation
result = addAll(result, operationBodyToCallMapper.getSelfVariablesUsedInBody(e));
result = addAll(result, operationBodyToCallMapper.getParameterVariablesUsedInBody(e));
} else if (e == OclHelper.getRootExpression(e)) {
result = addAll(result, operationBodyToCallMapper.getSelfVariablesUsedInBody(e));
}
}
if (result == null) {
result = Collections.emptySet();
}
return result;
}
private static <T> Set<T> addAll(Set<T> to, Collection<T> what) {
if (to == null) {
to = new HashSet<T>();
}
to.addAll(what);
return to;
}
/**
* Starting from <code>e</code> and ascending its containment hierarchy, adds to the resulting set all variables that are scoped by
* any of the expressions visited.
*
* @return a new set that the client may modify at will without doing any harm
*/
protected static Set<Variable> getAllVariablesInScope(OCLExpression e, OperationBodyToCallMapper operationBodyToCallMapper) {
Set<Variable> result = new HashSet<Variable>();
for (EObject cursor = e; cursor != null; cursor = cursor.eContainer()) {
if (cursor instanceof OCLExpression) {
result.addAll(getVariablesScopedByExpression((OCLExpression) cursor, operationBodyToCallMapper));
}
}
return result;
}
/**
* This method finds the common composition parent of the two given {@link OCLExpression}s. If the two expressions
* don't have a common container, <code>null</code> is returned.
*
* @param first The first {@link OCLExpression}.
* @param second The second {@link OCLExpression}.
* @return The common composition parent or null, in case there is none.
*/
private static OCLExpression commonCompositionParent(OCLExpression first, OCLExpression second) {
Set<OCLExpression> firstsContainersIncludingFirst = new HashSet<OCLExpression>();
EObject firstsContainer = first;
while (firstsContainer != null && firstsContainer instanceof OCLExpression) {
firstsContainersIncludingFirst.add((OCLExpression) firstsContainer);
firstsContainer = firstsContainer.eContainer();
}
EObject secondsContainer = second;
OCLExpression result = null;
while (result == null && secondsContainer != null && secondsContainer instanceof OCLExpression) {
if (firstsContainersIncludingFirst.contains(secondsContainer)) {
result = (OCLExpression) secondsContainer;
} else {
secondsContainer = secondsContainer.eContainer();
}
}
return result;
}
/**
* Creates a new {@link TracebackStepAndScopeChange}} object. Fetches from the cache or produces the {@link TracebackStep}}
* and computes the variable scope changes (see {@link #getVariablesChangingScope(OCLExpression, OCLExpression, OperationBodyToCallMapper)}.
* The results of these two operations are used for the {@link TracebackStepAndScopeChange} constructor.
*/
protected TracebackStepAndScopeChange createTracebackStepAndScopeChange(OCLExpression sourceExpression,
OCLExpression targetExpression, EClass context, OperationBodyToCallMapper operationBodyToCallMapper,
Stack<String> tupleLiteralNamesToLookFor, TracebackStepCache tracebackStepCache) {
return new TracebackStepAndScopeChange(tracebackStepCache.getOrCreateNavigationPath(targetExpression, context,
operationBodyToCallMapper, tupleLiteralNamesToLookFor, oclFactory), getVariablesChangingScope(sourceExpression,
targetExpression, operationBodyToCallMapper));
}
/**
* Creates a new {@link TracebackStepAndScopeChange}} object, remembering an {@link OperationCallExp} to which the
* results produced by this step are specific. The results produced by the step returned will all be keyed
* by the <code>call</code>. Fetches from the cache or produces the {@link TracebackStep}}
* and computes the variable scope changes (see {@link #getVariablesChangingScope(OCLExpression, OCLExpression, OperationBodyToCallMapper)}.
* The results of these two operations are used for the {@link TracebackStepAndScopeChange} constructor.
*/
protected TracebackStepAndScopeChangeWithOperationCallExp createTracebackStepAndScopeChange(OCLExpression sourceExpression,
OCLExpression targetExpression, OperationCallExp call, EClass context, OperationBodyToCallMapper operationBodyToCallMapper,
Stack<String> tupleLiteralNamesToLookFor, TracebackStepCache tracebackStepCache) {
return new TracebackStepAndScopeChangeWithOperationCallExp(tracebackStepCache.getOrCreateNavigationPath(targetExpression, context,
operationBodyToCallMapper, tupleLiteralNamesToLookFor, oclFactory), getVariablesChangingScope(sourceExpression,
targetExpression, operationBodyToCallMapper), call);
}
/**
* We assume a collection, possibly nested, that eventually has elements of a class-like type inside. If the innermost
* type is not an {@link EClass}, <code>null</code> is returned.
*/
protected EClass getInnermostClass(EClassifier type) {
EClass result = null;
while (!(type instanceof EClass) && type instanceof CollectionType) {
type = ((CollectionType) type).getElementType();
}
if (type instanceof EClass) {
result = (EClass) type;
}
return result;
}
/**
* We assume a collection, possibly nested, that eventually has elements of a class-like type inside. If the innermost
* type is not an {@link EClass}, <code>null</code> is returned.
*/
protected EClassifier getInnermostElementType(EClassifier type) {
while (!(type instanceof EClass) && type instanceof CollectionType) {
type = ((CollectionType) type).getElementType();
}
return type;
}
protected Stack<String> cloneWithTypeCheck(Stack<String> tupleLiteralNamesToLookFor) {
if (tupleLiteralNamesToLookFor == null) {
return null;
}
Object clone = tupleLiteralNamesToLookFor.clone();
if (clone instanceof Stack<?>) {
@SuppressWarnings("unchecked")
Stack<String> newTupleStack = (Stack<String>) clone;
return newTupleStack;
} else {
throw new ClassCastException("Cloning an instance of Stack<String> didn't return an instance of the same type.");
}
}
private String getAnnotation() {
return annotation;
}
/**
* Constructs a human-readable description of the OCL expression used as debug info for this
* navigation step. This includes traveling up to the root expression in which the debug info
* expression is embedded.
*/
private String getVerboseDebugInfo(OperationBodyToCallMapper operationBodyToCallMapper) {
try {
if (AnnotatedEObject.IS_IN_DEBUG_MODE) {
StringBuilder result = new StringBuilder();
result.append(" ==== ");
result.append(getClass().getSimpleName());
result.append(" for expression ====\n ");
result.append(getExpression());
OCLExpression root = OclHelper.getRootExpression(getExpression());
if (root != getExpression()) {
result.append("\n ==== in expression =====\n");
result.append(root.accept(HighlightingToStringVisitor.getInstance(root, getExpression())));
}
Set<OperationCallExp> calls = operationBodyToCallMapper.getCallsOf(root);
result.append(!calls.isEmpty() ? "\n ===== which is the body of operation "
+ formatOperation(calls.iterator().next().getReferredOperation()) + " ====="
: "");
return result.toString();
} else {
return AnnotatedEObject.NOT_IN_DEBUG_MODE_MESSAGE;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private String formatOperation(EOperation operation) {
StringBuilder result = new StringBuilder(((ENamedElement) operation.eContainer()).getName());
result.append('.');
result.append(operation.getName());
result.append('(');
boolean first = true;
for (EParameter param : operation.getEParameters()) {
if (!first) {
result.append(", ");
} else {
first = false;
}
result.append(param.getName());
result.append(':');
result.append(param.getEType().getName());
}
result.append(')');
return result.toString();
}
/**
* Annotates a navigation from <code>fromObject</code> to <code>next</code> by taking over <code>fromObject</code>'s annotation,
* adding a description of this step and telling at which object the navigation arrived. In case we're not in
* {@link AnnotatedEObject#IS_IN_DEBUG_MODE debug mode}, a default message (see {@link AnnotatedEObject#NOT_IN_DEBUG_MODE_MESSAGE})
* is used instead to save memory.
*/
protected AnnotatedEObject annotateEObject(AnnotatedEObject fromObject,
EObject next) {
if (AnnotatedEObject.IS_IN_DEBUG_MODE) {
return new AnnotatedEObject(next, fromObject, getAnnotation());
} else {
return new AnnotatedEObject(next, AnnotatedEObject.NOT_IN_DEBUG_MODE_MESSAGE);
}
}
/**
* Annotates a non-navigation where <code>object</code> is forwarded unchanged from this step to another. The result is an
* {@link AnnotatedEObject} with its from-object and reached object both being set to <code>object</code>, adding a
* description of this step and telling at which object the navigation arrived. In case we're not in
* {@link AnnotatedEObject#IS_IN_DEBUG_MODE debug mode}, a default message (see
* {@link AnnotatedEObject#NOT_IN_DEBUG_MODE_MESSAGE}) is used instead to save memory.
*/
protected AnnotatedEObject annotateEObject(AnnotatedEObject object) {
if (AnnotatedEObject.IS_IN_DEBUG_MODE) {
return new AnnotatedEObject(object.getAnnotatedObject(), object, getAnnotation());
} else {
return object;
}
}
/**
* If in {@link AnnotatedEObject#IS_IN_DEBUG_MODE debug mode}, creates a new {@link AnnotatedEObject} for each
* one in <code>newResults</code>, assuming that there is no "from" object; as an alibi "from" object, the
* annotated objects from <code>newResults</code> are used.
*/
protected Set<AnnotatedEObject> annotate(AnnotatedEObject fromObject, Set<AnnotatedEObject> newResults) {
if (AnnotatedEObject.IS_IN_DEBUG_MODE) {
Set<AnnotatedEObject> result = new HashSet<AnnotatedEObject>();
for (AnnotatedEObject newResult : newResults) {
result.add(new AnnotatedEObject(newResult.getAnnotatedObject(), fromObject, getAnnotation()));
}
return result;
} else {
return newResults;
}
}
protected OppositeEndFinder getOppositeEndFinder() {
return oppositeEndFinder;
}
}