| /******************************************************************************* |
| * 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; |
| } |
| |
| } |