blob: 846995c020551f939eb91e668894d8636d7164b3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2011 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;
}
}