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