blob: a74f1900920a08571fe1fb931ceac06e2846d5e4 [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.Collection;
import java.util.Collections;
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.EObject;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.ocl.ecore.IteratorExp;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.ecore.opposites.OppositeEndFinder;
import org.eclipse.ocl.examples.impactanalyzer.PartialEvaluator;
import org.eclipse.ocl.examples.impactanalyzer.PartialEvaluatorFactory;
import org.eclipse.ocl.examples.impactanalyzer.ValueNotFoundException;
import org.eclipse.ocl.examples.impactanalyzer.deltaPropagation.PartialEvaluatorImpl;
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.util.AnnotatedEObject;
import org.eclipse.ocl.examples.impactanalyzer.util.OCLFactory;
import org.eclipse.ocl.examples.impactanalyzer.util.OperationCallExpKeyedSet;
import org.eclipse.ocl.util.OCLStandardLibraryUtil;
import org.eclipse.ocl.utilities.PredefinedType;
public class IteratorTracebackStep extends AbstractTracebackStep<IteratorExp> {
private enum Strategy { EMPTY, MAP, PASSTHROUGH };
private final Strategy strategy;
private final TracebackStepAndScopeChange step;
private final boolean checkPredicate;
private final boolean acceptIfPredicateTrue;
private final OppositeEndFinder oppositeEndFinder;
private final PartialEvaluatorFactory partialEvaluatorFactory;
public IteratorTracebackStep(IteratorExp sourceExpression, EClass context,
OperationBodyToCallMapper operationBodyToCallMapper, Stack<String> tupleLiteralNamesToLookFor, TracebackStepCache tracebackStepCache, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory, OCLFactory oclFactory) {
super(sourceExpression, tupleLiteralNamesToLookFor, tracebackStepCache.getOppositeEndFinder(), operationBodyToCallMapper, unusedEvaluationRequestFactory, oclFactory);
this.partialEvaluatorFactory = tracebackStepCache.getInstanceScopeAnalysis().getPartialEvaluatorFactory();
String name = sourceExpression.getName();
int opCode = OCLStandardLibraryUtil.getOperationCode(name);
if (opCode == PredefinedType.SELECT || opCode == PredefinedType.REJECT || opCode == PredefinedType.SORTED_BY
|| opCode == PredefinedType.ANY) {
strategy = Strategy.PASSTHROUGH;
step = createTracebackStepAndScopeChange(sourceExpression, (OCLExpression) sourceExpression.getSource(), context,
operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache);
if (opCode == PredefinedType.SELECT || opCode == PredefinedType.REJECT || opCode == PredefinedType.ANY) {
// evaluate predicate before checking how it goes on
org.eclipse.ocl.expressions.Variable<EClassifier, EParameter> varDecl = sourceExpression.getIterator().get(0);
requiredType = getInnermostTypeConsideringTupleLiteralsLookedFor(tupleLiteralNamesToLookFor,
varDecl.getType());
if (opCode == PredefinedType.SELECT || opCode == PredefinedType.ANY) {
acceptIfPredicateTrue = true;
} else {
acceptIfPredicateTrue = false;
}
checkPredicate = true;
oppositeEndFinder = tracebackStepCache.getOppositeEndFinder();
} else {
requiredType = null;
checkPredicate = false;
acceptIfPredicateTrue = false;
oppositeEndFinder = null;
}
} else if (opCode == PredefinedType.COLLECT || opCode == PredefinedType.COLLECT_NESTED || opCode == PredefinedType.CLOSURE) {
strategy = Strategy.MAP;
requiredType = null;
checkPredicate = false;
acceptIfPredicateTrue = false;
step = createTracebackStepAndScopeChange(sourceExpression, (OCLExpression) sourceExpression.getBody(), context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache);
oppositeEndFinder = null;
} else {
// boolean or other non-class-type-result iterator
strategy = Strategy.EMPTY;
requiredType = null;
checkPredicate = false;
acceptIfPredicateTrue = false;
step = null;
oppositeEndFinder = null;
}
}
/**
* @param changeEvent
* if <code>null</code>, the evaluation will happen on the model
* as-is; otherwise, it will take place on the state the model
* was in before the change described by <code>changeEvent</code> took
* place
*/
@Override
protected OperationCallExpKeyedSet performSubsequentTraceback(AnnotatedEObject source,
UnusedEvaluationRequestSet pendingUnusedEvalRequests,
org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache tracebackCache,
Notification changeEvent) {
switch (strategy) {
case EMPTY:
return tracebackCache.getOperationCallExpKeyedSetFactory().emptySet();
case MAP:
return step.traceback(annotateEObject(source), pendingUnusedEvalRequests, tracebackCache, changeEvent);
case PASSTHROUGH:
boolean passedPredicate = !checkPredicate || evaluatePredicate(source.getAnnotatedObject(), changeEvent);
if (passedPredicate) {
return step.traceback(annotateEObject(source), pendingUnusedEvalRequests, tracebackCache, changeEvent);
} else {
return tracebackCache.getOperationCallExpKeyedSetFactory().emptySet();
}
default:
throw new RuntimeException("Internal error: unknown traceback strategy "+strategy);
}
}
/**
* @param atPre
* if <code>null</code>, the evaluation will only happen on the model
* as-is; otherwise, it will also take place on the state the model
* was in before the change described by <code>atPre</code> took
* place
*/
private boolean evaluatePredicate(EObject sourceObject, Notification atPre) {
Collection<EObject> sourceCollection = Collections.singleton(sourceObject);
// evaluate whether the source object would have passed the iterator's body before the change
boolean resultPre = acceptIfPredicateTrue;
if (atPre != null) {
// TODO perform the partial evaluation only if the values of all variables inevitably required by the predicate are known
// TODO otherwise, think about using the UnusedEvaluationRequest technique to queue predicate evaluation for later
PartialEvaluatorImpl evalPre = new PartialEvaluatorImpl(atPre, oppositeEndFinder, oclFactory);
try {
Object result = evalPre.evaluate(null, getExpression(), sourceCollection);
resultPre = isSourceInResult(sourceObject, result);
} catch (ValueNotFoundException vnfe) {
// be conservative about undefined situations
resultPre = acceptIfPredicateTrue;
} catch (ClassCastException cce) {
throw new RuntimeException("The result of the iterator expression's body is not of type Boolean.");
}
}
boolean resultPost = acceptIfPredicateTrue;
if (atPre == null || resultPre != acceptIfPredicateTrue) {
// evaluate whether the source object passes the iterator's body after the change
PartialEvaluator evalPost = partialEvaluatorFactory.createPartialEvaluator(oppositeEndFinder, oclFactory);
try {
Object result = evalPost.evaluate(null, getExpression(), sourceCollection);
resultPost = isSourceInResult(sourceObject, result);
} catch (ValueNotFoundException vnfe) {
// be conservative about undefined situations
resultPost = acceptIfPredicateTrue;
} catch (ClassCastException cce) {
throw new RuntimeException("The result of the iterator expression's body is not of type Boolean.");
}
// if the source object fulfills the condition before or after the change event
// or accesses an undefined variable before or after the change event
// it passes this navigation step
}
return (atPre != null && resultPre == acceptIfPredicateTrue) || resultPost == acceptIfPredicateTrue;
}
private boolean isSourceInResult(EObject sourceObject, Object result) {
boolean resultPost;
if (result instanceof Collection<?>) {
if (((Collection<?>) result).isEmpty()) {
resultPost = false;
} else {
resultPost = ((Collection<?>) result).contains(sourceObject);
}
} else {
resultPost = result == sourceObject;
}
return resultPost;
}
}