blob: 33acc1f7fcd4805071fe7ef9fae5ab0aeea84eb7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2018 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* SAP AG - initial API and implementation
******************************************************************************/
package org.eclipse.ocl.examples.impactanalyzer.instanceScope;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.ocl.ecore.CallExp;
import org.eclipse.ocl.ecore.IteratorExp;
import org.eclipse.ocl.ecore.LoopExp;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.ecore.opposites.OppositeEndFinder;
import org.eclipse.ocl.examples.impactanalyzer.PartialEvaluator;
import org.eclipse.ocl.examples.impactanalyzer.PartialEvaluatorFactory;
import org.eclipse.ocl.examples.impactanalyzer.ValueNotFoundException;
import org.eclipse.ocl.examples.impactanalyzer.util.AnnotatedEObject;
import org.eclipse.ocl.examples.impactanalyzer.util.OCLFactory;
import org.eclipse.ocl.examples.impactanalyzer.util.SemanticIdentity;
import org.eclipse.ocl.utilities.PredefinedType;
/**
* Evaluates an OCL expression that is expected to be the {@link IteratorExp#getBody() body} expression of an iterator expression.
* Before the evaluation is triggered, the single iterator variable obtained from {@link LoopExp#getIterator()} is set to the
* {@link RefObject} passed to {@link #navigate(CoreConnection, AnnotatedRefObjectImpl, Map, Set)} in the <tt>fromObject</tt>
* parameter. Depending on the particular type of iterator (<tt>select</tt>, <tt>reject</tt> or <tt>any</tt>), the object is
* passed on if and only if the OCL expression evaluates to <tt>true</tt>/<tt>false</tt>/<tt>true</tt>, respectively. For any
* iterators other than <tt>select</tt>, <tt>reject</tt> or <tt>any</tt> the object is passed through right away because the
* semantics of the iterator are not known.
*
* @author Axel Uhl D043530
*
*/
public class PredicateCheckNavigationStep extends AbstractNavigationStep {
private final String contentAsString;
private final SemanticIdentity semanticIdentity;
private final PartialEvaluatorFactory partialEvaluatorFactory;
/**
* Tells if the OCL predicate of the {@link #getIteratorExp() iterator expression} needs to be <tt>true</tt> so that the
* element can pass through. This is the case for <tt>select</tt> and <tt>any</tt> iterators. For <tt>reject</tt> it's
* <tt>false</tt>.
*/
private final boolean positive;
private final IteratorExp iteratorExp;
/**
* The opposite end finder is required for partial evaluation which shall use this finder to navigate
* hidden opposites.
*/
private final OppositeEndFinder oppositeEndFinder;
private final OCLFactory oclFactory;
public PredicateCheckNavigationStep(EClass sourceType, EClass targetType, IteratorExp iteratorExp, PathCache pathCache, OCLFactory oclFactory) {
super(sourceType, targetType, (OCLExpression)iteratorExp);
this.iteratorExp = iteratorExp;
this.oclFactory = oclFactory;
if (!haveIntersectingSubclassTree(sourceType, targetType)) {
setAlwaysEmpty();
}
if (iteratorExp.getName().equals(PredefinedType.SELECT_NAME) || iteratorExp.getName().equals(PredefinedType.ANY_NAME)) {
positive = true;
} else {
positive = false;
}
this.oppositeEndFinder = pathCache.getOppositeEndFinder();
contentAsString = "checkPredicate[" + iteratorExp.getBody().toString() + "]";
semanticIdentity = new PredicateCheckNavigationStepIdentity();
partialEvaluatorFactory = pathCache.getInstanceScopeAnalysis().getPartialEvaluatorFactory();
}
private class PredicateCheckNavigationStepIdentity extends SemanticIdentity{
public PredicateCheckNavigationStep getNavigationStep(){
return PredicateCheckNavigationStep.this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || hashCode() != o.hashCode()) {
return false;
}
return getSemanticIdentityOfSuper().equals(o) && PredicateCheckNavigationStep.this.positive == ((PredicateCheckNavigationStepIdentity)o).getNavigationStep().positive &&
PredicateCheckNavigationStep.this.iteratorExp.equals(((PredicateCheckNavigationStepIdentity)o).getNavigationStep().iteratorExp);
}
@Override
public int calculateHashCode() {
return getSemanticIdentityOfSuper().hashCode() ^ (positive ? 123 : 0) ^ iteratorExp.hashCode();
}
@Override
public NavigationStep getStep() {
return getNavigationStep();
}
}
public SemanticIdentity getSemanticIdentityOfSuper(){
return super.getSemanticIdentity();
}
private IteratorExp getIteratorExp() {
return iteratorExp;
}
@Override
protected Set<AnnotatedEObject> navigate(AnnotatedEObject fromObject, TracebackCache cache,
Notification atPre) {
Set<AnnotatedEObject> result = new LinkedHashSet<AnnotatedEObject>(1);
Set<EObject> sourceValue = new LinkedHashSet<EObject>(1);
sourceValue.add(fromObject.getAnnotatedObject());
boolean passedPredicate = evaluate((OCLExpression) getIteratorExp(), sourceValue, atPre);
if (passedPredicate) {
result.add(fromObject);
}
return result;
}
private Boolean evaluate(OCLExpression exp, Collection<EObject> sourceObjects, Notification atPre) {
// evaluate whether the source object would have passed the iterator's body before the change
Boolean resultPre = positive;
if (atPre != null) {
PartialEvaluator evalPre = partialEvaluatorFactory.createPartialEvaluator(atPre, oppositeEndFinder, oclFactory);
try {
Object result = evalPre.evaluate(null, (CallExp) exp, sourceObjects);
resultPre = sourceObjects.contains(result);
} catch (ValueNotFoundException vnfe) {
// be conservative about undefined situations
resultPre = positive;
} catch (ClassCastException cce) {
throw new RuntimeException("The result of the iterator expression's body is not of type Boolean.");
}
}
// evaluate whether the source object passes the iterator's body after the change
PartialEvaluator evalPost = partialEvaluatorFactory.createPartialEvaluator(oppositeEndFinder, oclFactory);
Boolean resultPost = positive;
if (atPre == null || resultPre != positive) {
try {
Object result = evalPost.evaluate(null, (CallExp) exp,
sourceObjects);
if (result instanceof Collection<?>) {
if (((Collection<?>) result).isEmpty()) {
resultPost = false;
} else {
resultPost = sourceObjects
.contains(((Collection<?>) result).iterator()
.next());
}
} else {
resultPost = sourceObjects.contains(result);
}
} catch (ValueNotFoundException vnfe) {
// be conservative about undefined situations
resultPost = positive;
} catch (ClassCastException cce) {
throw new RuntimeException(
"The result of the iterator expression's body is not of type Boolean.");
}
}
// if the source object fulfills the condition before or after the change event
// or accesses an undefined variable before or after the change event
// it passes this navigation step
return (atPre != null && resultPre == positive) || resultPost == positive;
}
@Override
public String contentToString(Map<NavigationStep, Integer> visited, int indent) {
return contentAsString;
}
@Override
public SemanticIdentity getSemanticIdentity() {
return semanticIdentity;
}
}