/******************************************************************************* * 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.deltaPropagation; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EEnumLiteral; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EOperation; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EParameter; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.ocl.AbstractEvaluationVisitor; import org.eclipse.ocl.Environment; import org.eclipse.ocl.EvaluationEnvironment; import org.eclipse.ocl.EvaluationHaltedException; import org.eclipse.ocl.ecore.CallExp; import org.eclipse.ocl.ecore.CallOperationAction; import org.eclipse.ocl.ecore.Constraint; import org.eclipse.ocl.ecore.EvaluationEnvironmentWithHiddenOpposites; import org.eclipse.ocl.ecore.EvaluationVisitorImpl; import org.eclipse.ocl.ecore.OppositePropertyCallExp; import org.eclipse.ocl.ecore.SendSignalAction; import org.eclipse.ocl.examples.impactanalyzer.ValueNotFoundException; import org.eclipse.ocl.examples.impactanalyzer.impl.ImpactAnalyzerPlugin; import org.eclipse.ocl.expressions.AssociationClassCallExp; import org.eclipse.ocl.expressions.BooleanLiteralExp; import org.eclipse.ocl.expressions.CollectionKind; import org.eclipse.ocl.expressions.CollectionLiteralExp; import org.eclipse.ocl.expressions.EnumLiteralExp; import org.eclipse.ocl.expressions.IfExp; import org.eclipse.ocl.expressions.IntegerLiteralExp; import org.eclipse.ocl.expressions.InvalidLiteralExp; import org.eclipse.ocl.expressions.IterateExp; import org.eclipse.ocl.expressions.IteratorExp; import org.eclipse.ocl.expressions.LetExp; import org.eclipse.ocl.expressions.MessageExp; import org.eclipse.ocl.expressions.NullLiteralExp; import org.eclipse.ocl.expressions.OCLExpression; import org.eclipse.ocl.expressions.OperationCallExp; import org.eclipse.ocl.expressions.PropertyCallExp; import org.eclipse.ocl.expressions.RealLiteralExp; import org.eclipse.ocl.expressions.StateExp; import org.eclipse.ocl.expressions.StringLiteralExp; import org.eclipse.ocl.expressions.TupleLiteralExp; import org.eclipse.ocl.expressions.TypeExp; import org.eclipse.ocl.expressions.UnlimitedNaturalLiteralExp; import org.eclipse.ocl.expressions.UnspecifiedValueExp; import org.eclipse.ocl.expressions.VariableExp; import org.eclipse.ocl.types.CollectionType; import org.eclipse.ocl.util.CollectionUtil; import org.eclipse.ocl.utilities.PredefinedType; /** * When a {@link ValueNotFoundException} occurs during evaluating an expression, it is not caught, logged and swallowed but * forwarded to the caller. * <p> * * All <tt>visit...</tt> operations check if the expression to evaluate is the {@link #sourceExpression} passed to the * constructor. If so, instead of actually evaluating the expression, the {@link #valueOfSourceExpression} object is returned * which was also passed to the constructor. This allows for partial evaluation of any {@link CallExp} with a given value for the * source expression. * <p> * * When the {@link #sourceExpression} has once been evaluated it is nulled out so that when due to recursion it is * evaluated again, evaluation is based on the current environment and not on the cached {@link #valueOfSourceExpression}. * Without this it could happen that, e.g., the value for a <tt>self</tt> {@link org.eclipse.ocl.ecore.VariableExp} is * cached but would have to have a different value upon recursive evaluation. * * @author Axel Uhl * */ public class PartialEvaluationVisitorImpl extends EvaluationVisitorImpl { private org.eclipse.ocl.ecore.OCLExpression sourceExpression; private Object valueOfSourceExpression; /** * a {@link Notification} object such that an evaluation will be based on the state *before* the notification. For example, if * the notification indicates the removal of a reference from an element <tt>e1</tt> to an element <tt>e2</tt> across * reference <tt>r</tt> then when during partial evaluation <tt>r</tt> is traversed starting from <tt>e1</tt> then <tt>e2</tt> * will show in the results although in the current version of the model it would not. If <tt>null</tt>, the evaluator will * evaluate expressions on the model as is. */ private Notification atPre; /** * @param atPre * a {@link Notification} object such that an evaluation will be based on the state *before* the notification. For * example, if the notification indicates the removal of a reference from an element <tt>e1</tt> to an element * <tt>e2</tt> across reference <tt>r</tt> then when during partial evaluation <tt>r</tt> is traversed starting * from <tt>e1</tt> then <tt>e2</tt> will show in the results although in the current version of the model it would * not. If <tt>null</tt>, the evaluator will evaluate expressions on the model as is. */ public PartialEvaluationVisitorImpl( Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> env, EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> evalEnv, Map<? extends EClass, ? extends Set<? extends EObject>> extentMap, org.eclipse.ocl.ecore.OCLExpression sourceExpression, Object valueOfSourceExpression, Notification atPre) { super(env, evalEnv, extentMap); this.sourceExpression = sourceExpression; this.valueOfSourceExpression = valueOfSourceExpression; this.atPre = atPre; } @Override public Object visitOperationCallExp(OperationCallExp<EClassifier, EOperation> oc) { if (oc == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } else { int opCode = oc.getOperationCode(); if (opCode == PredefinedType.AT) { OCLExpression<EClassifier> source = oc.getSource(); List<OCLExpression<EClassifier>> args = oc.getArgument(); // evaluate source Object sourceVal = source.accept(getVisitor()); if (sourceVal == getInvalid() || ((Collection<?>) sourceVal).isEmpty()) { // then we know there's going to be an IndexOutOfBoundsException thrown in CollectionUtil.at; skip the // evaluation of the argument expression which could only raise a ValueNotFoundException in case a variable // is accessed that is not defined; but it wouldn't change the result which always will be invalid for // an empty collection. return getInvalid(); } // evaluate argument OCLExpression<EClassifier> arg = args.get(0); if (isUndefined(sourceVal)) { return getInvalid(); } @SuppressWarnings("unchecked") Collection<Object> sourceColl = (Collection<Object>) sourceVal; // bug 183144: inputting OclInvalid should result in OclInvalid Object argVal = arg.accept(getVisitor()); if (argVal == getInvalid()) { return argVal; } // OrderedSet, Sequence::at(Integer) if (!(argVal instanceof Integer)) { return getInvalid(); } int indexVal = ((Integer) argVal).intValue(); return CollectionUtil.at(sourceColl, indexVal); } } return super.visitOperationCallExp(oc); } @Override protected Object safeVisitExpression(OCLExpression<EClassifier> source) { Object sourceVal; try { sourceVal = source.accept(getVisitor()); } catch (EvaluationHaltedException ehe) { throw ehe; } catch (ValueNotFoundException vnfe) { throw vnfe; } catch (RuntimeException e) { sourceVal = getInvalid(); } return sourceVal; } @Override public Object visitIterateExp(IterateExp<EClassifier, EParameter> ie) { if (ie == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitIterateExp(ie); } @Override public Object visitIteratorExp(IteratorExp<EClassifier, EParameter> ie) { if (ie == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitIteratorExp(ie); } @Override public Object visitEnumLiteralExp(EnumLiteralExp<EClassifier, EEnumLiteral> el) { if (el == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitEnumLiteralExp(el); } @Override public PartialEcoreEvaluationEnvironment getEvaluationEnvironment() { return (PartialEcoreEvaluationEnvironment) super.getEvaluationEnvironment(); } @Override public Object visitVariableExp(VariableExp<EClassifier, EParameter> v) { if (v == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } // evaluate the variable in the current environment, using a special getValueOf(...) operation that // accepts the VariableExp so as to better be able to annotate an exception in case the variable is // unknown return getEvaluationEnvironment().getValueOf(v); } @SuppressWarnings("unchecked") @Override public Object visitPropertyCallExp(PropertyCallExp<EClassifier, EStructuralFeature> pc) { if (pc == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } /* * These are the sources of the super implementation copied here which is ugly. We only want to get access to the value of * the source expression because it may be needed for the comparison with the atPre event later. * * TODO evaluate source expression here and cache the result before delegating to super.visitPropertyCallExp; when then * super.visitPropertyCallExp asks the visitor to evaluate the source expression, the result is taken from the cache */ EStructuralFeature property = pc.getReferredProperty(); OCLExpression<EClassifier> source = pc.getSource(); // evaluate source Object context = source.accept(getVisitor()); Object localResult; // if source is undefined, result is OclInvalid if (isUndefined(context)) { localResult = getInvalid(); } else { OCLExpression<EClassifier> derivation = getPropertyBody(property); if (derivation != null) { // this is an additional property localResult = navigate(property, derivation, context); } else { List<Object> qualifiers; if (pc.getQualifier().isEmpty()) { qualifiers = Collections.emptyList(); } else { // handle qualified association navigation qualifiers = new java.util.ArrayList<Object>(); for (OCLExpression<EClassifier> q : pc.getQualifier()) { qualifiers.add(q.accept(getVisitor())); } } localResult = getEvaluationEnvironment().navigateProperty(property, qualifiers, context); if ((pc.getType() instanceof CollectionType<?, ?>) && !(localResult instanceof Collection<?>)) { // this was an XSD "unspecified multiplicity". Now that we know what // the multiplicity is, we can coerce it to a collection value CollectionKind kind = ((CollectionType<EClassifier, EOperation>) pc.getType()).getKind(); Collection<Object> collection = CollectionUtil.createNewCollection(kind); collection.add(localResult); localResult = collection; } } } if (atPre != null && atPre.getNotifier() != null && atPre.getNotifier() == context && pc.getReferredProperty() == atPre.getFeature()) { CollectionKind kind = (pc.getType() instanceof CollectionType<?, ?>) ? ((CollectionType<EClassifier, EOperation>) pc .getType()).getKind() : null; // evaluate property call based on the model state that existed before the change // described by the atPre notification switch (atPre.getEventType()) { case Notification.ADD: // if the addition operated on the result of the source expression, remove the element from the local results // again localResult = removeAt((Collection<?>) localResult, kind); break; case Notification.ADD_MANY: // if the addition operated on the result of the source expression, remove the elements from the local results // again ((Collection<?>) localResult).removeAll((Collection<?>) atPre.getNewValue()); break; case Notification.MOVE: // if the move operated on the result of the source expression, move the element back to the old index // indicated by getOldIntValue() localResult = move((Collection<?>) localResult, kind); break; case Notification.REMOVE: // if the removal operated on the result of the source expression, add the element back at the index provided localResult = insertAt((Collection<Object>) localResult, kind); break; case Notification.REMOVE_MANY: // if the removal operated on the result of the source expression, add the elements back at the positions // given in the getNewValue() int[]. // TODO restore at appropriate position if provided and collection is ordered (a list) localResult = insertManyAt((Collection<Object>) localResult, kind); break; case Notification.SET: case Notification.UNSET: // if the setting/unsetting operated on the result of the source expression, return the old value // TODO restore old value at position specified by index, if any, and if the local result is a collection localResult = atPre.getOldValue(); break; case Notification.RESOLVE: // TODO consider avoiding filtering for RESOLVE notifications in the first place break; // stay with new, resolved value because resolution is automatic default: throw new RuntimeException("Don't understand @pre notification " + atPre); } } return localResult; } /** * Assumes that {@link #atPre}.{@link Notification#getOldValue()} contains a collection of the * objects that were removed and that {@link #atPre}.{@link Notification#getNewValue()} is an * <tt>int[]</tt> describing the old positions at which the elements were removed. * * @param <T> * @param into * @param kind */ @SuppressWarnings("unchecked") private <T> Collection<T> insertManyAt(Collection<T> into, CollectionKind kind) { Collection<T> result; int[] oldPositions = (int[]) atPre.getNewValue(); if ((kind == CollectionKind.SEQUENCE_LITERAL || kind == CollectionKind.ORDERED_SET_LITERAL) && atPre.getPosition() != Notification.NO_INDEX) { if (into instanceof List<?>) { int i=0; for (T t : ((Collection<T>) atPre.getOldValue())) { ((List<T>) into).add(oldPositions[i++], t); } result = into; } else { // ordered but not a list; probably an OrderedSet rendered as a LinkedHashMap; we need to copy // maybe we're lucky and can append to the end: if (oldPositions[0] >= into.size()) { for (T t : ((Collection<T>) atPre.getOldValue())) { ((List<T>) into).add(t); } result = into; } else { result = CollectionUtil.createNewCollection(kind); int i = 0; Iterator<T> removedIter = ((Collection<T>) atPre.getOldValue()).iterator(); int targetPos = 0; for (T t : into) { if (targetPos == oldPositions[i]) { result.add(removedIter.next()); targetPos++; } result.add(t); targetPos++; } } } } else { into.addAll((Collection<T>) atPre.getOldValue()); result = into; } return result; } /** * Moves the {@link #atPre}'s {@link Notification#getNewValue() new value} from the new position * described by {@link #atPre}.{@link Notification#getPosition() getPosition()} to the old position * described by {@link #atPre}.{@link Notification#getOldValue() getOldValue()}. Does what is needed, * including cloning the collection and fixing the order by iteration if necessary. */ @SuppressWarnings("unchecked") private <T> Collection<T> move(Collection<T> collection, CollectionKind kind) { Collection<T> result = collection; if ((kind == CollectionKind.SEQUENCE_LITERAL || kind == CollectionKind.ORDERED_SET_LITERAL) && atPre.getPosition() != Notification.NO_INDEX) { int oldPositionBeforeMove = (Integer) atPre.getOldValue(); int newPositionAfterMove = atPre.getPosition(); if (collection instanceof List<?>) { if (((List<?>) collection).get(atPre.getPosition()) != atPre.getNewValue()) { throw new RuntimeException("Internal error. Didn't find " + atPre.getNewValue() + " at position " + atPre.getPosition()); } else { ((List<T>) collection).remove(newPositionAfterMove); ((List<T>) collection).add(oldPositionBeforeMove, (T) atPre.getNewValue()); result = collection; } } else { if (oldPositionBeforeMove >= collection.size()) { // can handle by remove and add at end collection.remove(atPre.getNewValue()); collection.add((T) atPre.getNewValue()); } else { // ordered but not a list; probably an OrderedSet rendered as a LinkedHashMap; we need to copy result = CollectionUtil.createNewCollection(kind); int sourcePos = 0; int targetPos = 0; for (T t : collection) { if (targetPos == oldPositionBeforeMove) { result.add((T) atPre.getNewValue()); targetPos++; } if (sourcePos != newPositionAfterMove) { result.add(t); targetPos++; } sourcePos++; } } } } // no action required otherwise because the collection is not ordered and moving is not defined // on an unordered collection return result; } /** * Removes {@link #atPre}'s new value from the <tt>from</tt> collection. If the collection kind indicates an ordered * collection and the {@link #atPre} event specifies a valid index, removal will take place at the index specified after a * check that at that index the {@link #atPre}.{@link Notification#getNewValue() getNewValue()} object is found. * <p> * * Precondition: {@link #atPre} <tt>!= null</tt> */ private <T> Collection<T> removeAt(Collection<T> from, CollectionKind kind) { Collection<T> result; if ((kind == CollectionKind.SEQUENCE_LITERAL || kind == CollectionKind.ORDERED_SET_LITERAL) && atPre.getPosition() != Notification.NO_INDEX) { if (from instanceof List<?>) { if (((List<?>) from).get(atPre.getPosition()) != atPre.getNewValue()) { throw new RuntimeException("Internal error. Didn't find " + atPre.getNewValue() + " at position " + atPre.getPosition()); } else { ((List<?>) from).remove(atPre.getPosition()); result = from; } } else { // ordered but not a list; probably an OrderedSet rendered as a LinkedHashMap; we need to copy result = CollectionUtil.createNewCollection(kind); int i = 0; for (T t : from) { if (i++ != atPre.getPosition()) { result.add(t); } } } } else { from.remove(atPre.getNewValue()); result = from; } return result; } /** * Adds {@link #atPre}'s old value to the <tt>into</tt> collection. If the collection kind indicates an ordered collection and * the {@link #atPre} event specifies a valid index, insertion will take place at the index specified. * <p> * * Precondition: {@link #atPre} <tt>!= null</tt> */ @SuppressWarnings("unchecked") private <T> Collection<T> insertAt(Collection<T> into, CollectionKind kind) { Collection<T> result; if ((kind == CollectionKind.SEQUENCE_LITERAL || kind == CollectionKind.ORDERED_SET_LITERAL) && atPre.getPosition() != Notification.NO_INDEX) { if (into instanceof List<?>) { ((List<T>) into).add(atPre.getPosition(), (T) atPre.getOldValue()); result = into; } else { // ordered but not a list; probably an OrderedSet rendered as a LinkedHashMap; we need to copy // maybe we're lucky and can append to the end: if (atPre.getPosition() >= into.size()) { into.add((T) atPre.getOldValue()); result = into; } else { result = CollectionUtil.createNewCollection(kind); int i = 0; for (T t : into) { if (i++ == atPre.getPosition()) { result.add((T) atPre.getOldValue()); i++; } result.add(t); } } } } else { into.add((T) atPre.getOldValue()); result = into; } return result; } @Override public Object visitAssociationClassCallExp(AssociationClassCallExp<EClassifier, EStructuralFeature> ae) { if (ae == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitAssociationClassCallExp(ae); } @Override public Object visitIfExp(IfExp<EClassifier> ie) { if (ie == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitIfExp(ie); } @Override public Object visitTypeExp(TypeExp<EClassifier> t) { if (t == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitTypeExp(t); } @Override public Object visitStateExp(StateExp<EClassifier, EObject> s) { if (s == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitStateExp(s); } @Override public Object visitMessageExp(MessageExp<EClassifier, CallOperationAction, SendSignalAction> m) { if (m == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitMessageExp(m); } @Override public Object visitUnspecifiedValueExp(UnspecifiedValueExp<EClassifier> uv) { if (uv == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitUnspecifiedValueExp(uv); } @Override public Object visitIntegerLiteralExp(IntegerLiteralExp<EClassifier> il) { if (il == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitIntegerLiteralExp(il); } @Override public Object visitUnlimitedNaturalLiteralExp(UnlimitedNaturalLiteralExp<EClassifier> literalExp) { if (literalExp == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitUnlimitedNaturalLiteralExp(literalExp); } @Override public Object visitRealLiteralExp(RealLiteralExp<EClassifier> rl) { if (rl == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitRealLiteralExp(rl); } @Override public Object visitStringLiteralExp(StringLiteralExp<EClassifier> sl) { if (sl == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitStringLiteralExp(sl); } @Override public Object visitBooleanLiteralExp(BooleanLiteralExp<EClassifier> bl) { if (bl == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitBooleanLiteralExp(bl); } @Override public Object visitInvalidLiteralExp(InvalidLiteralExp<EClassifier> il) { if (il == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitInvalidLiteralExp(il); } @Override public Object visitNullLiteralExp(NullLiteralExp<EClassifier> il) { if (il == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitNullLiteralExp(il); } @Override public Object visitLetExp(LetExp<EClassifier, EParameter> l) { if (l == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitLetExp(l); } @Override public Object visitCollectionLiteralExp(CollectionLiteralExp<EClassifier> cl) { if (cl == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitCollectionLiteralExp(cl); } @Override public Object visitTupleLiteralExp(TupleLiteralExp<EClassifier, EStructuralFeature> tl) { if (tl == sourceExpression) { sourceExpression = null; return valueOfSourceExpression; } return super.visitTupleLiteralExp(tl); } /** * Does the same as the {@link AbstractEvaluationVisitor} implementation but additionally catches the * {@link ValueNotFoundException} and in that case passes on the exception instead of turning it into an <tt>OclInvalid</tt> * value. * * @param expression * an OCL expression to evaluate * * @return the result of the evaluation */ @Override public Object visitExpression(OCLExpression<EClassifier> expression) { try { return expression.accept(getVisitor()); } catch (EvaluationHaltedException e) { // evaluation stopped on demand, propagate further throw e; } catch (ValueNotFoundException e) { throw e; } catch (RuntimeException e) { String msg = e.getLocalizedMessage(); if (msg == null) { msg = "(no message)"; } ImpactAnalyzerPlugin.log(Diagnostic.ERROR, ImpactAnalyzerPlugin.IGNORED_EXCEPTION_WARNING, "Evaluation failed with an exception: " + msg, e); // failure to evaluate results in invalid return getInvalid(); } } @SuppressWarnings("unchecked") public Object visitOppositePropertyCallExp(OppositePropertyCallExp pc) { if (pc == getSourceExpression()) { setSourceExpression(null); return getValueOfSourceExpression(); } /* * These are the sources of the super implementation copied here which is ugly. We only want to get access to the value of * the source expression because it may be needed for the comparison with the atPre event later. * * TODO evaluate source expression here and cache the result before delegating to super.visitPropertyCallExp; when then * super.visitPropertyCallExp asks the visitor to evaluate the source expression, the result is taken from the cache */ EReference property = pc.getReferredOppositeProperty(); OCLExpression<EClassifier> source = pc.getSource(); // evaluate source Object context = source.accept(getVisitor()); Object localResult; // if source is undefined, result is OclInvalid if (isUndefined(context)) { localResult = getInvalid(); } else { // TODO consider introduction of derivation expressions also for opposite property; for now it's not supported /* OCLExpression<EClassifier> derivation = getPropertyBody(property); if (derivation != null) { // this is an additional property localResult = navigate(property, derivation, context); } else { */ localResult = ((EvaluationEnvironmentWithHiddenOpposites) getEvaluationEnvironment()).navigateOppositeProperty(property, context); if ((pc.getType() instanceof CollectionType<?, ?>) && !(localResult instanceof Collection<?>)) { // this was an XSD "unspecified multiplicity". Now that we know what // the multiplicity is, we can coerce it to a collection value CollectionKind kind = ((CollectionType<EClassifier, EOperation>) pc.getType()).getKind(); Collection<Object> collection = CollectionUtil.createNewCollection(kind); collection.add(localResult); localResult = collection; } // } } // for @pre with opposite properties there can't be any ordering if (getAtPre() != null // the source of the opposite property call expression is the target of a normal EMF notification // because the notification talks about a forward reference; the old and new value described by the // event may be a collection of elements or a single element. Check if the source appears somewhere // in the notification's old or new value: && ((getAtPre().getOldValue() != null && ((getAtPre().getOldValue() instanceof Collection<?> && ((Collection<?>) getAtPre().getOldValue()).contains(context)) || getAtPre().getOldValue() == context)) || (getAtPre().getNewValue() != null && ((getAtPre().getNewValue() instanceof Collection<?> && ((Collection<?>) getAtPre().getNewValue()).contains(context)) || getAtPre().getNewValue() == context))) && pc.getReferredOppositeProperty() == getAtPre().getFeature()) { // evaluate property call based on the model state that existed before the change // described by the atPre notification switch (getAtPre().getEventType()) { case Notification.ADD: case Notification.ADD_MANY: // if the addition operated on the result of the source expression, remove the element from the local results // again ((Collection<?>) localResult).remove(getAtPre().getNotifier()); break; case Notification.MOVE: // there is no ordering for opposite properties in Ecore; it's safe to ignore this notification break; case Notification.REMOVE: case Notification.REMOVE_MANY: // if the removal operated on the result of the source expression, add the element back at the index provided ((Collection<Object>) localResult).add(getAtPre().getNotifier()); break; case Notification.SET: case Notification.UNSET: if (getAtPre().getOldValue() == context) { // the notification tells that previously the source context was referenced by the notifier; // re-add the notifier to the result: ((Collection<Object>) localResult).add(getAtPre().getNotifier()); } else if (getAtPre().getNewValue() == context) { // the notification tells that after the change the source context is referenced by the notifier; // remove the notifier from the result: ((Collection<Object>) localResult).remove(getAtPre().getNotifier()); } break; default: throw new RuntimeException("Don't understand @pre notification " + getAtPre()); } } return localResult; } protected org.eclipse.ocl.ecore.OCLExpression getSourceExpression() { return sourceExpression; } protected void setSourceExpression(org.eclipse.ocl.ecore.OCLExpression sourceExpression) { this.sourceExpression = sourceExpression; } protected Object getValueOfSourceExpression() { return valueOfSourceExpression; } protected Notification getAtPre() { return atPre; } }