blob: 5f15aedab171f98749e28da67404f691f2dcc5ce [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2012 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
******************************************************************************/
package org.eclipse.ocl.examples.impactanalyzer.instanceScope;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.logging.Logger;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.ocl.ecore.BooleanLiteralExp;
import org.eclipse.ocl.ecore.CallExp;
import org.eclipse.ocl.ecore.CollectionLiteralExp;
import org.eclipse.ocl.ecore.EcorePackage;
import org.eclipse.ocl.ecore.EnumLiteralExp;
import org.eclipse.ocl.ecore.IfExp;
import org.eclipse.ocl.ecore.IntegerLiteralExp;
import org.eclipse.ocl.ecore.InvalidLiteralExp;
import org.eclipse.ocl.ecore.IterateExp;
import org.eclipse.ocl.ecore.IteratorExp;
import org.eclipse.ocl.ecore.LetExp;
import org.eclipse.ocl.ecore.LiteralExp;
import org.eclipse.ocl.ecore.NavigationCallExp;
import org.eclipse.ocl.ecore.NullLiteralExp;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.ecore.OperationCallExp;
import org.eclipse.ocl.ecore.OppositePropertyCallExp;
import org.eclipse.ocl.ecore.PrimitiveLiteralExp;
import org.eclipse.ocl.ecore.PrimitiveType;
import org.eclipse.ocl.ecore.PropertyCallExp;
import org.eclipse.ocl.ecore.RealLiteralExp;
import org.eclipse.ocl.ecore.StringLiteralExp;
import org.eclipse.ocl.ecore.TupleLiteralExp;
import org.eclipse.ocl.ecore.TypeExp;
import org.eclipse.ocl.ecore.VariableExp;
import org.eclipse.ocl.ecore.opposites.OppositeEndFinder;
import org.eclipse.ocl.examples.eventmanager.NotificationHelper;
import org.eclipse.ocl.examples.impactanalyzer.PartialEvaluatorFactory;
import org.eclipse.ocl.examples.impactanalyzer.configuration.ActivationOption;
import org.eclipse.ocl.examples.impactanalyzer.deltaPropagation.PartialEvaluatorFactoryImpl;
import org.eclipse.ocl.examples.impactanalyzer.deltaPropagation.PartialEvaluatorImpl;
import org.eclipse.ocl.examples.impactanalyzer.filterSynthesis.FilterSynthesisImpl;
import org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackStep;
import org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackStepCache;
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.internal.evaluation.NumberUtil;
import org.eclipse.ocl.utilities.PredefinedType;
/**
* Supports a lookup from a source model element of either an attribute value change event or a link add/remove event together
* with an {@link OCLExpression} to a set of context objects for which the expression may return a value different
* from the one it returned before the change event occurred. An instance of this class manages the analysis for all expression
* occuring within one root OCL expression, including the expressions reachable in operation body expressions where the operation
* may be called directly of indirectly by the root expression.
*
*/
public class InstanceScopeAnalysis extends PartialEvaluatorFactoryImpl {
private final Logger logger = Logger.getLogger(InstanceScopeAnalysis.class.getName());
private final PathCache pathCache;
private final TracebackStepCache tracebackStepCache;
private final OCLExpression expression;
private final FilterSynthesisImpl filterSynthesizer;
private final EClass context;
private final OppositeEndFinder oppositeEndFinder;
private final ActivationOption configuration;
private final OCLFactory oclFactory;
private final PartialEvaluatorImpl partialEvaluatorForAllInstancesDeltaPropagation;
private static final Set<String> comparisonOpNames = new HashSet<String>(Arrays.asList(new String[] {
PredefinedType.EQUAL_NAME, PredefinedType.LESS_THAN_NAME, PredefinedType.LESS_THAN_EQUAL_NAME,
PredefinedType.GREATER_THAN_NAME, PredefinedType.GREATER_THAN_EQUAL_NAME, PredefinedType.NOT_EQUAL_NAME }));
/**
* @param expression
* the OCL expression for which to perform instance scope impact analysis
* @param oppositeEndFinder
* used during partial evaluation and for metamodel queries, e.g., finding opposite role names, or finding all
* subclasses of a class; as well as for obtaining all instances of a type while performing an
* {@link AllInstancesNavigationStep}. It is handed to the {@link PathCache} object from where
* {@link Tracer}s can retrieve it using {@link PathCache#getOppositeEndFinder()}.
*/
public InstanceScopeAnalysis(OCLExpression expression, EClass exprContext, FilterSynthesisImpl filterSynthesizer, OppositeEndFinder oppositeEndFinder, ActivationOption configuration, OCLFactory oclFactory) {
this(expression, exprContext, filterSynthesizer, oppositeEndFinder, new PartialEvaluatorImpl(oclFactory, oppositeEndFinder), configuration,
oclFactory,
/* pathCache */ configuration.isTracebackStepISAActive() ? null : new PathCache(oppositeEndFinder), /* tracebackStepCache */ configuration.isTracebackStepISAActive() ? new TracebackStepCache(oppositeEndFinder) : null);
}
protected void initInstanceScopeAnalysisOnPathCache() {
if (pathCache != null) {
pathCache.initInstanceScopeAnalysis(this);
}
if (tracebackStepCache != null) {
tracebackStepCache.initInstanceScopeAnalysis(this);
}
}
protected InstanceScopeAnalysis(OCLExpression expression, EClass exprContext, FilterSynthesisImpl filterSynthesizer, OppositeEndFinder oppositeEndFinder, PartialEvaluatorImpl partialEvaluator, ActivationOption configuration, OCLFactory oclFactory, PathCache pathCache, TracebackStepCache tracebackStepCache) {
checkConstructorArgs(expression, exprContext, filterSynthesizer);
context = exprContext;
this.oclFactory = oclFactory;
this.expression = expression;
partialEvaluatorForAllInstancesDeltaPropagation = partialEvaluator;
this.filterSynthesizer = filterSynthesizer;
this.oppositeEndFinder = oppositeEndFinder;
this.configuration = configuration;
this.tracebackStepCache = tracebackStepCache;
this.pathCache = pathCache;
initInstanceScopeAnalysisOnPathCache();
}
private void checkConstructorArgs(OCLExpression expression, EClass exprContext, FilterSynthesisImpl filterSynthesizer) {
if (exprContext == null) {
throw new IllegalArgumentException("exprContext must not be null. Maybe no context type specified to ImpactAnalyzerImpl constructor, and no self-expression found to infer it?");
}
if (expression == null || filterSynthesizer == null) {
throw new IllegalArgumentException("Arguments must not be null");
}
}
protected PathCache getPathCache() {
return pathCache;
}
/**
* Finds all elements of type <code>cls</code> or conforming to <code>cls</code> such that based on the scope definition
* implemented by the <code>oppositeEndFinder</code>'s
* {@link OppositeEndFinder#getAllInstancesSeeing(EClass, org.eclipse.emf.common.notify.Notifier)} method, the elements
* returned can "see" <code>container</code>.
*
* @param cls
* the overall context type for the entire expression; this context type defines the type for <tt>self</tt> if used
* outside of operation bodies.
* @param oppositeEndFinder
* used to determine all instances
*/
public static Set<EObject> getAllPossibleContextInstances(Notifier context, EClass cls, OppositeEndFinder oppositeEndFinder) {
return oppositeEndFinder.getAllInstancesSeeing(cls, context);
}
/**
* Factory method that creates an instance of some {@link Tracer}-implementing class specific to the type of the OCL
* <tt>expression</tt>.
* @param caller the calling tracer from which the list of tuple part names to look for are copied
* unchanged to the new tracer created by this operation. May be <tt>null</tt> in which case the
* new tracer does not look for any tuple literal parts initially.
*/
protected Tracer createTracer(OCLExpression expression, Stack<String> tuplePartNames, OCLFactory oclFactory) {
// Using the class loader is another option, but that would create implicit naming conventions.
// Thats why we do the mapping "manually".
switch (expression.eClass().getClassifierID()) {
case EcorePackage.PROPERTY_CALL_EXP:
return new PropertyCallExpTracer((PropertyCallExp) expression, tuplePartNames, oclFactory);
case EcorePackage.OPPOSITE_PROPERTY_CALL_EXP:
return new OppositePropertyCallExpTracer((OppositePropertyCallExp) expression, tuplePartNames, oclFactory);
case EcorePackage.BOOLEAN_LITERAL_EXP:
return new BooleanLiteralExpTracer((BooleanLiteralExp) expression, tuplePartNames, oclFactory);
case EcorePackage.COLLECTION_LITERAL_EXP:
return new CollectionLiteralExpTracer((CollectionLiteralExp) expression, tuplePartNames, oclFactory);
case EcorePackage.ENUM_LITERAL_EXP:
return new EnumLiteralExpTracer((EnumLiteralExp) expression, tuplePartNames, oclFactory);
case EcorePackage.IF_EXP:
return new IfExpTracer((IfExp) expression, tuplePartNames, oclFactory);
case EcorePackage.INTEGER_LITERAL_EXP:
return new IntegerLiteralExpTracer((IntegerLiteralExp) expression, tuplePartNames, oclFactory);
case EcorePackage.ITERATE_EXP:
return new IterateExpTracer((IterateExp) expression, tuplePartNames, oclFactory);
case EcorePackage.ITERATOR_EXP:
return new IteratorExpTracer((IteratorExp) expression, tuplePartNames, oclFactory);
case EcorePackage.LET_EXP:
return new LetExpTracer((LetExp) expression, tuplePartNames, oclFactory);
case EcorePackage.OPERATION_CALL_EXP:
return new OperationCallExpTracer((OperationCallExp) expression, tuplePartNames, oclFactory);
case EcorePackage.REAL_LITERAL_EXP:
return new RealLiteralExpTracer((RealLiteralExp) expression, tuplePartNames, oclFactory);
case EcorePackage.STRING_LITERAL_EXP:
return new StringLiteralExpTracer((StringLiteralExp) expression, tuplePartNames, oclFactory);
case EcorePackage.TUPLE_LITERAL_EXP:
return new TupleLiteralExpTracer((TupleLiteralExp) expression, tuplePartNames, oclFactory);
case EcorePackage.TYPE_EXP:
return new TypeExpTracer((TypeExp) expression, tuplePartNames, oclFactory);
case EcorePackage.VARIABLE_EXP:
return new VariableExpTracer((VariableExp) expression, tuplePartNames, oclFactory);
case EcorePackage.NULL_LITERAL_EXP:
return new NullLiteralExpTracer((NullLiteralExp) expression, tuplePartNames, oclFactory);
case EcorePackage.INVALID_LITERAL_EXP:
return new InvalidlLiteralExpTracer((InvalidLiteralExp) expression, tuplePartNames, oclFactory);
default:
throw new RuntimeException("Unsupported expression type " + expression.eClass().getName());
}
}
public Collection<EObject> getContextObjects(Notification event, boolean notifyOnNewContextElements) {
Collection<EObject> resultCollection;
if (NotificationHelper.isElementLifeCycleEvent(event)) {
resultCollection = handleLifeCycleEvent(event, notifyOnNewContextElements);
} else {
resultCollection = new HashSet<EObject>();
}
TracebackCache cacheForNavigationSteps = createNavigationStepCache();
org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache cacheForTracebackSteps = createTracebackStepCache();
for (NavigationCallExp attributeOrAssociationEndCall : getAttributeOrAssociationEndCalls(event)) {
for (AnnotatedEObject sourceElement : getSourceElement(event, attributeOrAssociationEndCall)) {
// TODO contemplate parallel execution of hasNoEffectOnOverallExpression and self which both may take long and we
// don't know which one takes longer
if (!hasNoEffectOnOverallExpression(event, attributeOrAssociationEndCall, sourceElement)) {
if (sourceElement != null) {
// the source element may have been deleted already by subsequent events; at this point,
// this makes it impossible to trace the change event back to a context; all we have is
Iterable<AnnotatedEObject> selfCandidates;
if (configuration.isTracebackStepISAActive()) {
selfCandidates = selfWithTracebackSteps(attributeOrAssociationEndCall, sourceElement, context, event, cacheForTracebackSteps);
} else {
selfCandidates = selfWithNavigationSteps(attributeOrAssociationEndCall, sourceElement, context, event, cacheForNavigationSteps);
}
for (AnnotatedEObject r : selfCandidates) {
resultCollection.add(r.getAnnotatedObject());
}
}
}
}
}
return resultCollection;
}
private org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache createTracebackStepCache() {
org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache cacheForTracebackSteps;
if (configuration.isTracebackStepISAActive()) {
cacheForTracebackSteps = new org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache(
configuration, tracebackStepCache.getUnusedEvaluationRequestFactory());
} else {
cacheForTracebackSteps = null;
}
return cacheForTracebackSteps;
}
private TracebackCache createNavigationStepCache() {
TracebackCache cacheForNavigationSteps;
if (configuration.isTracebackStepISAActive()) {
cacheForNavigationSteps = null;
} else {
cacheForNavigationSteps = new TracebackCache();
}
return cacheForNavigationSteps;
}
/**
* Determines a superset of the set of context objects for which the overall
* {@link #expression} managed by this instance scope analysis results in
* <code>evaluationResult</code> or a collection containing
* <code>evaluationResult</code>. The result is always a valid collection,
* never <code>null</code>, but possibly empty.
*
* @param evaluationResult
* has to be a non-<code>null</code> {@link EObject} because
* backwards navigation is not easily possible for
* primitive-typed values and is impossible from
* <code>null</code> values for now.
*/
public Collection<EObject> getContextObjects(EObject evaluationResult) {
Iterable<AnnotatedEObject> annotatedResults;
AnnotatedEObject startObject = createStartObject(evaluationResult);
if (configuration.isTracebackStepISAActive()) {
org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache cache = createTracebackStepCache();
TracebackStep step = getTracebackStepForExpression(expression, context);
annotatedResults = step.traceback(startObject,
/* pending unused evaluation requests */ null, cache, /* notification */ null);
} else {
TracebackCache cache = createNavigationStepCache();
NavigationStep step = getNavigationStepsToSelfForExpression(
expression, context);
Set<AnnotatedEObject> sourceElementAsSet = Collections.singleton(startObject);
annotatedResults = step.navigate(sourceElementAsSet, cache, /* notification */ null);
}
Set<EObject> result = new HashSet<EObject>();
for (AnnotatedEObject annotatedResult : annotatedResults) {
result.add(annotatedResult.getAnnotatedObject());
}
return result;
}
private boolean hasNoEffectOnOverallExpression(Notification event, NavigationCallExp attributeOrAssociationEndCall,
AnnotatedEObject sourceElement){
if(configuration.isDeltaPropagationActive()) {
PartialEvaluatorImpl partialEvaluatorAtPre = createPartialEvaluator(event, oppositeEndFinder, oclFactory);
Object oldValue = partialEvaluatorAtPre.evaluate(null, attributeOrAssociationEndCall, sourceElement.getAnnotatedObject());
PartialEvaluatorImpl partialEvaluatorAtPost = createPartialEvaluator(oppositeEndFinder, oclFactory);
Object newValue = partialEvaluatorAtPost.evaluate(null, attributeOrAssociationEndCall, sourceElement.getAnnotatedObject());
return partialEvaluatorAtPost.hasNoEffectOnOverallExpression(attributeOrAssociationEndCall, oldValue, newValue, filterSynthesizer);
} else {
return false;
}
}
private Collection<EObject> handleLifeCycleEvent(Notification event, boolean notifyOnNewContextElements) {
Collection<EObject> result = new HashSet<EObject>();
Boolean addEvent = NotificationHelper.isAddEvent(event);
if (NotificationHelper.isManyEvent(event)) {
List<?> featureValue;
if (addEvent) {
featureValue = (List<?>) event.getNewValue();
} else {
featureValue = (List<?>) event.getOldValue();
}
for (Object value : featureValue) {
if (value instanceof EObject) {
if (addEvent && notifyOnNewContextElements && getContext().isInstance(value)) {
// put new elements into result set
result.add((EObject) value);
}
EClass valuesClass = ((EObject) value).eClass();
addAllPossibleContextsIfNonEmptyDelta(featureValue, result, event, valuesClass);
}
}
} else {
Object value;
if (addEvent) {
value = event.getNewValue();
} else {
value = event.getOldValue();
}
if (value instanceof EObject) {
if (addEvent && notifyOnNewContextElements && getContext().isInstance(value)) {
// put new elements into result set
result.add((EObject) value);
}
Collection<Object> featureValueAsObjectCollection = Collections.singletonList(value);
EClass valuesClass = ((EObject) value).eClass();
addAllPossibleContextsIfNonEmptyDelta(featureValueAsObjectCollection, result, event, valuesClass);
}
}
return result;
}
private void addAllPossibleContextsIfNonEmptyDelta(Collection<?> featureValue, Collection<EObject> result,
Notification event, EClass eClass) {
addAllPossibleContextsIfNotEmptyDeltaForSingleClass(featureValue, result, event, eClass);
for (EClass superClass : eClass.getEAllSuperTypes()) {
addAllPossibleContextsIfNotEmptyDeltaForSingleClass(featureValue, result, event, superClass);
}
}
private void addAllPossibleContextsIfNotEmptyDeltaForSingleClass(Collection<?> featureValue, Collection<EObject> result,
Notification event, EClass eClass) {
for (OperationCallExp allInstancesCall : filterSynthesizer.getAllInstancesCallsFor(eClass)) {
// if we can prove that the delta of allInstances() propagates to an empty set,
// the overall expression remains unchanged by the original change:
@SuppressWarnings("unchecked")
Collection<Object> featureValueAsObjectCollection = (Collection<Object>) featureValue;
if (!getPartialEvaluatorForAllInstancesDeltaPropagation().transitivelyPropagateDelta(allInstancesCall,
featureValueAsObjectCollection, filterSynthesizer).isEmpty()) {
result.addAll(getAllPossibleContextInstances((Notifier) event.getNotifier(), getContext(), oppositeEndFinder));
}
}
}
/**
* Checks if all {@link ModelChangeEvent}s contained in <tt>events</tt> are attribute changes and the expressions affected by
* the change event are all attribute call expressions for an attribute of primitive type that is used in a direct comparison
* with a constant literal. If this is the case, compares the old and new value with the literal's value, considering the
* comparison operator. If no change occurs in comparison result for any of the events, <tt>true</tt> is returned because then
* the expression value didn't change based on the change event. If any of the events is an event of different type or the
* attribute is not of primitive type or its value is not compared to a constant, <tt>false</tt> is returned.
* <p>
*
* Note that further performance improvements are conceivable but not yet implemented. For example, the attribute call
* expression may be used in a <tt>let</tt>-expression and then the variable may be compared to a primitive literal.
*
* @param replacementFor__TEMP__
* as a special case, expressions can contain the special string literal "__TEMP__" (see
* GlobalDelayedReferenceResolver.TEMPORARY_QUERY_PARAM_REPLACEMENT). Those will be replaced by the value of a
* lexical token. This value can be passed here so that the comparison does not happen with the special "__TEMP__"
* constant but with the parameter value instead in case the comparison argument is a string literal with value
* "__TEMP__".
*/
public boolean isUnaffectedDueToPrimitiveAttributeValueComparisonWithLiteralOnly(Notification changeEvent,
String replacementFor__TEMP__) {
if (!NotificationHelper.isAttributeValueChangeEvent(changeEvent)) {
return false;
}
Set<? extends NavigationCallExp> calls = getAttributeOrAssociationEndCalls(changeEvent);
if (calls.size() == 0) {
// this is likely to be dead code because if the filter synthesis works correctly,
// we always reevaluate the expressions containing the changed attribute once an attributeValueChange occurs
System.err.println("Could niot find any attribute or association end calls for the attribute value change event : " + changeEvent);
return false; // probably an allInstances-triggered element creation/deletion event
}
for (NavigationCallExp ace : calls) {
if (ace.getType() instanceof PrimitiveType) {
if (((PropertyCallExp) ace).getReferredProperty().equals(NotificationHelper.getNotificationFeature(changeEvent))) {
OCLExpression otherArgument = null;
OperationCallExp op;
boolean attributeIsParameter = false;
if (!(ace.eContainer() instanceof OperationCallExp)) {
continue;
}
op = (OperationCallExp) ace.eContainer(); // argument of a comparison operation?
if (op != null && isComparisonOperation(op)) {
otherArgument = (OCLExpression) op.getSource();
attributeIsParameter = true;
} else {
CallExp callExp = (CallExp) ace.eContainer(); // source of a comparison operation?
if (callExp != null && callExp instanceof OperationCallExp
&& isComparisonOperation(((OperationCallExp) callExp))) {
op = ((OperationCallExp) callExp);
otherArgument = (OCLExpression) op.getArgument().iterator().next();
attributeIsParameter = false;
}
}
if (otherArgument != null && otherArgument instanceof PrimitiveLiteralExp) {
if (doesComparisonResultChange(changeEvent, (PrimitiveLiteralExp) otherArgument, replacementFor__TEMP__,
op.getReferredOperation().getName(), attributeIsParameter)) {
return false;
}
} else {
// attribute not used in comparison operation; we assume a change
return false;
}
}
}
}
return true;
}
@SuppressWarnings("unchecked")
// due to Comparable<Object> casts
private boolean doesComparisonResultChange(Notification avce, LiteralExp otherArgument, String replacementFor__TEMP__,
String operationName, boolean attributeIsParameter) {
Object value;
// As a change event should only have occurred if something actually changed, if one of old and new
// value is null, this always represents a change because when compared to either the null literal
// or a constant, this would change, perhaps from/to an OclInvalid value:
boolean result = avce.getNewValue() == null || avce.getOldValue() == null;
if (!result) {
if (otherArgument instanceof StringLiteralExp) {
value = ((StringLiteralExp) otherArgument).getStringSymbol();
if (value.equals("__TEMP__")) {
value = replacementFor__TEMP__;
}
} else if (otherArgument instanceof BooleanLiteralExp) {
value = ((BooleanLiteralExp) otherArgument).getBooleanSymbol();
} else if (otherArgument instanceof IntegerLiteralExp) {
value = NumberUtil.coerceNumber(((IntegerLiteralExp) otherArgument).getLongSymbol());
} else if (otherArgument instanceof RealLiteralExp) {
value = ((RealLiteralExp) otherArgument).getRealSymbol();
} else {
throw new RuntimeException("Internal error. Unknown OCL primitive literal expression " + otherArgument);
}
int oldComparison = (attributeIsParameter ? ((Comparable<Object>) value).compareTo(avce.getOldValue())
: ((Comparable<Object>) avce.getOldValue()).compareTo(value));
int newComparison = (attributeIsParameter ? ((Comparable<Object>) value).compareTo(avce.getNewValue())
: ((Comparable<Object>) avce.getNewValue()).compareTo(value));
if (operationName.equals(PredefinedType.EQUAL_NAME)) {
result = (oldComparison == 0) != (newComparison == 0);
} else if (operationName.equals(PredefinedType.NOT_EQUAL_NAME)) {
result = (oldComparison != 0) != (newComparison != 0);
} else if (operationName.equals(PredefinedType.LESS_THAN_NAME)) {
result = oldComparison < 0 != newComparison < 0;
} else if (operationName.equals(PredefinedType.LESS_THAN_EQUAL_NAME)) {
result = oldComparison <= 0 != newComparison <= 0;
} else if (operationName.equals(PredefinedType.GREATER_THAN_NAME)) {
result = oldComparison > 0 != newComparison > 0;
} else if (operationName.equals(PredefinedType.GREATER_THAN_EQUAL_NAME)) {
result = oldComparison >= 0 != newComparison >= 0;
} else {
logger.info("operator " + operationName
+ " not supported in impact analysis performance improvement; assuming a change");
result = true;
}
}
return result;
}
/**
* Finds all attribute and association end call expressions in <tt>expression</tt> that are affected by the
* <tt>changeEvent</tt>. The result is always non-<tt>null</tt> but may be empty.
*/
private Set<? extends NavigationCallExp> getAttributeOrAssociationEndCalls(Notification changeEvent) {
Set<? extends NavigationCallExp> result;
if (NotificationHelper.isAttributeValueChangeEvent(changeEvent)) {
result = filterSynthesizer.getAttributeCallExpressions((EAttribute) NotificationHelper
.getNotificationFeature(changeEvent));
} else if (NotificationHelper.isLinkLifeCycleEvent(changeEvent)) {
EReference ref = (EReference) NotificationHelper.getNotificationFeature(changeEvent);
Set<NavigationCallExp> localResult = new HashSet<NavigationCallExp>();
localResult.addAll(filterSynthesizer.getAssociationEndCallExpressions(ref));
result = localResult;
} else {
result = Collections.emptySet();
}
return result;
}
/**
* @return the overall context type for the entire expression; this context type defines
* the type for <tt>self</tt> if used outside of operation bodies.
*/
private EClass getContext() {
return context;
}
/**
* Looks up <tt>exp</tt> in {@link #expressionToStep}. If not found, the respective {@link Tracer} is created and used to
* compute and then cache the required {@link NavigationStep}.
*
* @param context
* the overall context for the entire expression of which <tt>exp</tt> is a subexpression; this context type
* defines the type for <tt>self</tt> if used outside of operation bodies.
*/
private NavigationStep getNavigationStepsToSelfForExpression(OCLExpression exp, EClass context) {
NavigationStep result = getPathCache().getOrCreateNavigationPath(exp, context, filterSynthesizer, /* tupleLiteralNamesToLookFor */ null, oclFactory);
return result;
}
/**
* Looks up <tt>exp</tt> in {@link #expressionToStep}. If not found, the respective {@link Tracer} is created and used to
* compute and then cache the required {@link NavigationStep}.
*
* @param context
* the overall context for the entire expression of which <tt>exp</tt> is a subexpression; this context type
* defines the type for <tt>self</tt> if used outside of operation bodies.
*/
private TracebackStep getTracebackStepForExpression(OCLExpression exp, EClass context) {
TracebackStep result = tracebackStepCache.getOrCreateNavigationPath(exp, context, filterSynthesizer,
/* tupleLiteralNamesToLookFor */ null, oclFactory);
return result;
}
/**
* @param changeEvent
* either an {@link AttributeValueChangeEvent} or a {@link LinkChangeEvent}.
* @param attributeOrAssociationEndCall
* a (sub-)expression originally affected by <tt>changeEvent</tt>. For {@link LinkChangeEvent}s it depends on which
* end the expression uses what will be considered the source element of the change which will then be returned by
* this method. The source is the element at the end of the link changed that is the opposite end of the end used
* by the {@link AssociationEndCallExp}. <tt>attributeOrAssociationEndCall</tt> has to be of type
* {@link AttributeCallExp} in case <tt>changeEvent</tt> is an {@link AttributeValueChangeEvent}, and of type
* {@link AssociationEndCallExp} in case <tt>changeEvent</tt> is of type {@link LinkChangeEvent}.
* @return <tt>null</tt> in case the source element indicated by the change event does not conform to the static attribute or
* association call's source expression type. <tt>null</tt> may also result if the element indicated by the event
* cannot be resolved (anymore). This is still an open issue. See the to-do marker below. In all other cases, the
* source element on which the event occured, is returned.
*/
protected Collection<AnnotatedEObject> getSourceElement(Notification changeEvent, NavigationCallExp attributeOrAssociationEndCall) {
assert NotificationHelper.isAttributeValueChangeEvent(changeEvent)
|| NotificationHelper.isLinkLifeCycleEvent(changeEvent);
EClassifier sourceType = attributeOrAssociationEndCall.getSource().getType();
Collection<AnnotatedEObject> result = new HashSet<AnnotatedEObject>();
// can't be source element of attributeOrAssociationEndCall because of incompatible type
// also see the ASCII arts in AssociationEndCallExpTracer.traceback
if (attributeOrAssociationEndCall instanceof PropertyCallExp) {
if (sourceType.isInstance(changeEvent.getNotifier())) {
result.add(createStartObject((EObject) changeEvent.getNotifier()));
}
} else {
throw new RuntimeException("Can only handle PropertyCallExp and OppositePropertyCallExp expression types, not "+
attributeOrAssociationEndCall.getClass().getName());
}
return result;
}
private AnnotatedEObject createStartObject(EObject eObject) {
return new AnnotatedEObject(eObject, "<start>");
}
protected Collection<Object> getSourceElementsForOppositePropertyCallExp(Notification changeEvent) {
Collection<Object> result = new HashSet<Object>();
Object oldValue = changeEvent.getOldValue();
Object newValue = changeEvent.getNewValue();
switch (changeEvent.getEventType()) {
case Notification.SET:
case Notification.UNSET:
result.addAll(OclHelper.flatten(oldValue));
result.addAll(OclHelper.flatten(newValue));
break;
case Notification.ADD:
case Notification.ADD_MANY:
result.addAll(OclHelper.flatten(newValue));
break;
case Notification.REMOVE:
case Notification.REMOVE_MANY:
result.addAll(OclHelper.flatten(oldValue));
break;
}
return result;
}
private boolean isComparisonOperation(OperationCallExp op) {
return comparisonOpNames.contains(op.getReferredOperation().getName());
}
/**
* For attribute call expressions or association end call expressions determines for which context elements used for the
* global <tt>self</tt> (not to confuse with the <tt>self</tt> value that applies for an operation body and which is set to
* the value of the source expression on which the operation is invoked) the property call's
* {@link ModelPropertyCallExp#getSource() source expression} may evaluate to <tt>sourceElement</tt>. This is a conservative
* estimate which means that for some elements of the result set, when used as element for the global <tt>self</tt> variable,
* the source expression may not necessarily evaluate to <tt>sourceElement</tt>. However, there are no other {@link RefObject}
* elements that are not part of the result and for which the source expression evaluates to <tt>sourceElement</tt>. This
* means, all contexts for which the source expression evaluates to <tt>sourceElement</tt> are guaranteed to be found.<p>
*
* This implementation variant uses {@link TracebackStep}s.
*
* @param context
* the overall context for the entire expression of which <tt>exp</tt> is a subexpression; this context type
* defines the type for <tt>self</tt> if used outside of operation bodies.
*/
private Iterable<AnnotatedEObject> selfWithNavigationSteps(NavigationCallExp attributeOrAssociationEndCall,
AnnotatedEObject sourceElement, EClass context, Notification changeEvent, TracebackCache cache) {
Iterable<AnnotatedEObject> result;
NavigationStep step = getNavigationStepsToSelfForExpression(
(OCLExpression) attributeOrAssociationEndCall.getSource(), context);
Set<AnnotatedEObject> sourceElementAsSet = Collections.singleton(sourceElement);
result = step.navigate(sourceElementAsSet, cache, changeEvent);
return result;
}
/**
* For attribute call expressions or association end call expressions determines for which context elements used for the
* global <tt>self</tt> (not to confuse with the <tt>self</tt> value that applies for an operation body and which is set to
* the value of the source expression on which the operation is invoked) the property call's
* {@link ModelPropertyCallExp#getSource() source expression} may evaluate to <tt>sourceElement</tt>. This is a conservative
* estimate which means that for some elements of the result set, when used as element for the global <tt>self</tt> variable,
* the source expression may not necessarily evaluate to <tt>sourceElement</tt>. However, there are no other {@link RefObject}
* elements that are not part of the result and for which the source expression evaluates to <tt>sourceElement</tt>. This
* means, all contexts for which the source expression evaluates to <tt>sourceElement</tt> are guaranteed to be found.<p>
*
* This implementation variant uses {@link NavigationStep}s.
*
* @param context
* the overall context for the entire expression of which <tt>exp</tt> is a subexpression; this context type
* defines the type for <tt>self</tt> if used outside of operation bodies.
*/
private Iterable<AnnotatedEObject> selfWithTracebackSteps(NavigationCallExp attributeOrAssociationEndCall,
AnnotatedEObject sourceElement, EClass context, Notification changeEvent,
org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache cache) {
Iterable<AnnotatedEObject> result;
TracebackStep step = getTracebackStepForExpression((OCLExpression) attributeOrAssociationEndCall.getSource(), context);
result = step.traceback(sourceElement, /* pending unused evaluation requests */ null, cache, changeEvent);
return result;
}
protected PartialEvaluatorImpl getPartialEvaluatorForAllInstancesDeltaPropagation() {
return partialEvaluatorForAllInstancesDeltaPropagation;
}
public PartialEvaluatorFactory getPartialEvaluatorFactory() {
return this;
}
}