/******************************************************************************* * Copyright (c) 2009, 2014 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.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>->at(...)</code></b>: When the * <code>->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; } }