blob: 9e11da3049d6d1b039bbc132930c64544a7680ce [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2014 Borland Software Corporation 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:
* Borland Software Corporation - initial API and implementation
* Christopher Gerking - bugs 358709, 432885, 433292
*******************************************************************************/
package org.eclipse.m2m.internal.qvt.oml.library;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.m2m.internal.qvt.oml.ast.env.InternalEvaluationEnv;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalEvaluationEnv;
import org.eclipse.m2m.internal.qvt.oml.evaluator.EvaluationUtil;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtOperationalEvaluationVisitor;
import org.eclipse.m2m.internal.qvt.oml.expressions.ImperativeOperation;
import org.eclipse.m2m.internal.qvt.oml.expressions.MappingOperation;
import org.eclipse.m2m.internal.qvt.oml.expressions.ResolveExp;
import org.eclipse.m2m.internal.qvt.oml.expressions.ResolveInExp;
import org.eclipse.m2m.internal.qvt.oml.trace.EMappingResults;
import org.eclipse.m2m.internal.qvt.oml.trace.Trace;
import org.eclipse.m2m.internal.qvt.oml.trace.TraceRecord;
import org.eclipse.m2m.internal.qvt.oml.trace.VarParameterValue;
import org.eclipse.m2m.qvt.oml.ecore.ImperativeOCL.AssignExp;
import org.eclipse.ocl.ecore.CallExp;
import org.eclipse.ocl.ecore.IteratorExp;
import org.eclipse.ocl.ecore.OperationCallExp;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.PropertyCallExp;
import org.eclipse.ocl.types.CollectionType;
import org.eclipse.ocl.util.CollectionUtil;
import org.eclipse.ocl.utilities.PredefinedType;
/**
* @author aigdalov
*/
public class QvtResolveUtil {
/**
* A helper interface to hold the source object for late resolve call which
* is to be called on during deferred assignment execution.
* <p>
* Note: The motivation for this interface is the need to distinguish
* between not- passing a source object of a source resulting in
* <code>null</code> during evaluation.
*/
interface SavedSourceObjectHolder {
/**
* @return the source object of <code>null</code>
*/
Object getSourceObj();
/**
* Indicates whether the source object is used in the end of the
* transformation as source of late resolve calls.
*/
boolean isInDeferredExecution();
}
private QvtResolveUtil() {
super();
}
/**
* Indicates whether the given assignment has late resolved right value and
* is to be executed as deferred assignment.
*
* @param resolveExp
* the resolve expression to analyze
* @return <code>true</code> if the assignment is to receive a future
* value from late resolve; <code>false</code> otherwise
*/
public static boolean hasDeferredRightSideValue(AssignExp assignExp) {
if(assignExp.getValue().isEmpty()) {
return false;
}
OCLExpression<EClassifier> rightValue = assignExp.getValue().get(0);
if(rightValue instanceof ResolveExp && ((ResolveExp)rightValue).isIsDeferred()) {
return true;
}
if(rightValue instanceof IteratorExp) {
IteratorExp iterator = (IteratorExp) rightValue;
return isLateResolveImplicitCollect(iterator);
}
if(rightValue instanceof OperationCallExp) {
OperationCallExp operCall = (OperationCallExp) rightValue;
return isLateResolveResultConversion(operCall);
}
return false;
}
/**
* Indicates whether the given resolve expression can be used in conjunction
* with deferred assignment.
* <p>
* Note: The late resolve call result is assigned at deferred time if is assigned
* to a property directly or by calling a single <code>->as...</code> collection type
* conversion method.
*
* @param resolveExp
* the resolve expression to analyze.
* @return <code>true</code> if there is a supported deferred assignment
* to receive the future value; <code>false</code> otherwise.
* @see #getDeferredAssignmentFor(ResolveExp)
*/
public static boolean isSuppportedAsDeferredAssigned(ResolveExp resolveExp) {
return getDeferredAssignmentFor(resolveExp) != null;
}
/**
* Gets deferred assignment used with the given resolve expression which is
* to be executed in the end of the transformation.
*
* @param resolveExp
* a resolve expression
* @return the assignment receiving the late resolve result, if the resolve
* expression is deferred and its result is assigned to it either directly,
* by using a collection type conversion, or by using an implicit collect.
* Otherwise, <code>null</code> is returned.
*/
public static AssignExp getDeferredAssignmentFor(ResolveExp resolveExp) {
if(!resolveExp.isIsDeferred()) {
return null;
}
EObject resolveContainer = resolveExp.eContainer();
if(resolveContainer instanceof AssignExp) {
AssignExp assignExp = (AssignExp) resolveContainer;
if(assignExp.getLeft() instanceof PropertyCallExp) {
return assignExp;
}
} else if(resolveContainer instanceof CallExp) {
CallExp call = (CallExp) resolveContainer;
if(call instanceof OperationCallExp && !isLateResolveResultConversion((OperationCallExp) call)) {
return null;
}
if(call instanceof IteratorExp && !isLateResolveImplicitCollect((IteratorExp) call)) {
return null;
}
// lookup the closest outer assignment node
EObject parent = call.eContainer();
while(parent != null) {
if(parent instanceof AssignExp) {
AssignExp assignExp = (AssignExp)parent;
if(call != assignExp.getLeft()) {
return assignExp;
}
}
parent = parent.eContainer();
}
}
return null;
}
/**
* Indicate whether a operation call expression is supported collection
* type conversion for late resolve results.
*/
private static boolean isLateResolveResultConversion(OperationCallExp operCall) {
if (!isCollectionConversionCall(operCall)) {
return false;
}
if(operCall.getSource() instanceof ResolveExp) {
ResolveExp resolveExp = (ResolveExp) operCall.getSource();
return isCollectionTypeLateResolve(resolveExp);
}
if(operCall.getSource() instanceof IteratorExp) {
IteratorExp iteratorExp = (IteratorExp) operCall.getSource();
return isLateResolveImplicitCollect(iteratorExp);
}
return false;
}
private static boolean isCollectionTypeLateResolve(ResolveExp resolveExp) {
return resolveExp.isIsDeferred() && resolveExp.getType() instanceof CollectionType<?, ?>;
}
private static boolean isLateResolveImplicitCollect(IteratorExp iterator) {
if(iterator.getBody() instanceof ResolveExp) {
ResolveExp resolveExp = (ResolveExp) iterator.getBody();
return isCollectionTypeLateResolve(resolveExp);
}
return false;
}
/**
* Indicate whether a operation call expression is collection type conversion.
*/
private static boolean isCollectionConversionCall(OperationCallExp operCall) {
switch(operCall.getOperationCode()) {
case PredefinedType.AS_BAG :
case PredefinedType.AS_SEQUENCE :
case PredefinedType.AS_SET :
case PredefinedType.AS_ORDERED_SET :
return true;
}
return false;
}
private static Object coerceResultValue(ResolveExp resolveExp, Object resolveRawResult) {
// always return non-null if collection type is expected
if(resolveRawResult == null) {
resolveRawResult = createEmptyCollectionOrNull(resolveExp);
}
if (resolveExp.isIsDeferred() && resolveExp.eContainer() instanceof OperationCallExp) {
OperationCallExp opCallExp = (OperationCallExp) resolveExp.eContainer();
if(opCallExp.getSource() == resolveExp) {
Collection<?> resultCollection = (resolveRawResult instanceof Collection) ?
(Collection<?>) resolveRawResult : Collections.singletonList(resolveRawResult);
// supported collection conversion operation call on the result of late resolve
switch (opCallExp.getOperationCode()) {
case PredefinedType.AS_SET:
return CollectionUtil.asSet((Collection<?>) resultCollection);
case PredefinedType.AS_BAG:
return CollectionUtil.asBag((Collection<?>) resultCollection);
case PredefinedType.AS_ORDERED_SET:
return CollectionUtil.asOrderedSet((Collection<?>) resultCollection);
case PredefinedType.AS_SEQUENCE:
return CollectionUtil.asSequence((Collection<?>) resultCollection);
}
}
}
return resolveRawResult;
}
public static final Object resolveNow(ResolveExp resolveExp, QvtOperationalEvaluationVisitor visitor, QvtOperationalEvaluationEnv env) {
return resolveNow(resolveExp, visitor, env, null);
}
public static final Object resolveInNow(ResolveInExp resolveInExp, QvtOperationalEvaluationVisitor visitor, QvtOperationalEvaluationEnv env) {
return resolveInNow(resolveInExp, visitor, env, null);
}
/**
* Resolves resolve expression using the given evaluation visitor and
* environment.
*
* @param resolveExp
* the resolve expression to resolve
* @param visitor
* the visitor to perform evaluation
* @param env
* environment for condition or eventual source object evaluation
* @param savedSrcObj
* the source object evaluated and saved for late resolve
* execution or <code>null</code> if the source is to be
* evaluated by this method
* @return resolved object or collection of objects
*/
static final Object resolveNow(ResolveExp resolveExp, QvtOperationalEvaluationVisitor visitor, QvtOperationalEvaluationEnv env, SavedSourceObjectHolder savedSrcObj) {
InternalEvaluationEnv internEnv = env.getAdapter(InternalEvaluationEnv.class);
Trace trace = internEnv.getTraces();
EMap<Object, EList<TraceRecord>> map = chooseKeyToTraceRecordMap(resolveExp, trace);
OCLExpression<EClassifier> source = resolveExp.getSource();
Object sourceEval = (savedSrcObj == null) ? (source == null ? null : source.accept(visitor)) : savedSrcObj.getSourceObj();
EClassifier sourceType = (source == null) ? null : source.getType();
List<TraceRecord> traceRecords = lookupTraceRecordsBySource(sourceEval, sourceType, map);
if (traceRecords == null) {
return createEmptyCollectionOrNull(resolveExp);
}
Object result = searchByTypeAndCondition(resolveExp, traceRecords, visitor, env);
if(savedSrcObj != null && savedSrcObj.isInDeferredExecution() && resolveExp.isIsDeferred()) {
// Note: executing immediately but at deferred execution time, this is possible for instance if late resolve is called from with a condition
result = coerceResultValue(resolveExp, result);
}
return result;
}
/**
* Resolves resolveIn expression using the given evaluation visitor and
* environment.
*
* @param resolveInExp
* the resolve expression to resolve
* @param visitor
* the visitor to perform evaluation
* @param env
* environment for condition or eventual source object evaluation
* @param savedSrcObj
* the source object evaluated and saved for late resolve
* execution or <code>null</code> if the source is to be
* evaluated by this method
*
* @return resolved object or collection of objects
*/
static final Object resolveInNow(ResolveInExp resolveInExp, QvtOperationalEvaluationVisitor visitor, QvtOperationalEvaluationEnv env, SavedSourceObjectHolder savedSrcObj) {
InternalEvaluationEnv internEnv = env.getAdapter(InternalEvaluationEnv.class);
OCLExpression<EClassifier> source = resolveInExp.getSource();
List<TraceRecord> selectedTraceRecords = new ArrayList<TraceRecord>();
Trace trace = internEnv.getTraces();
if (source == null) {
List<TraceRecord> traceRecords = new ArrayList<TraceRecord>();
MappingOperation inMapping = resolveInExp.getInMapping();
// bug 358709: consider overriding mapping
ImperativeOperation overridingOper = EvaluationUtil.getOverridingOperation(env, inMapping);
if (overridingOper instanceof MappingOperation) {
inMapping = (MappingOperation) overridingOper;
}
EList<TraceRecord> inMappingTraceRecords = trace.getTraceRecordMap().get(inMapping);
if (inMappingTraceRecords != null) {
traceRecords.addAll(inMappingTraceRecords);
}
if (traceRecords.isEmpty()) {
return createEmptyCollectionOrNull(resolveInExp);
}
selectedTraceRecords.addAll(traceRecords);
} else {
EMap<Object, EList<TraceRecord>> map = chooseKeyToTraceRecordMap(resolveInExp, trace);
Object sourceEval = (savedSrcObj == null) ? source.accept(visitor) : savedSrcObj.getSourceObj();
List<TraceRecord> traceRecords = lookupTraceRecordsBySource(sourceEval, source.getType(), map);
if (traceRecords.isEmpty()) {
return createEmptyCollectionOrNull(resolveInExp);
}
for (TraceRecord traceRecord : traceRecords) {
MappingOperation inMapping = resolveInExp.getInMapping();
if (traceRecord.getMappingOperation().getRuntimeMappingOperation().equals(inMapping)) {
selectedTraceRecords.add(traceRecord);
}
}
}
Object result = searchByTypeAndCondition(resolveInExp, selectedTraceRecords, visitor, env);
if(savedSrcObj != null && savedSrcObj.isInDeferredExecution() && resolveInExp.isIsDeferred()) {
// Note: executing immediately but at deferred execution time, this is possible for instance if late resolve is called from with a condition
result = coerceResultValue(resolveInExp, result);
}
return result;
}
private static List<TraceRecord> lookupTraceRecordsBySource(Object source, EClassifier declaredSourceType, EMap<Object, EList<TraceRecord>> source2RecordMap) {
List<TraceRecord> result = null;
if (declaredSourceType == null) {
for (EList<TraceRecord> rec : source2RecordMap.values()) {
if(result == null) {
result = new BasicEList<TraceRecord>();
}
result.addAll(rec);
}
}
else {
result = source2RecordMap.get(source);
}
return (result != null) ? Collections.unmodifiableList(result) : Collections.<TraceRecord>emptyList();
}
private static Object searchByTypeAndCondition(ResolveExp resolveExp, List<TraceRecord> traceRecords, QvtOperationalEvaluationVisitor visitor, QvtOperationalEvaluationEnv env) {
if (resolveExp.isOne()) {
for (TraceRecord traceRecord : traceRecords) {
EMappingResults results = traceRecord.getResult();
if (resolveExp.isIsInverse()) {
if (traceRecord.getContext().getContext() != null) {
Object target = traceRecord.getContext().getContext().getValue().getOclObject();
if (!checkTypeAndCondition(resolveExp, target, visitor, env)) {
continue;
}
return target;
}
} else {
for (VarParameterValue varParameterValue : results.getResult()) {
Object target = varParameterValue.getValue().getOclObject();
if (!checkTypeAndCondition(resolveExp, target, visitor, env)) {
continue;
}
return target;
}
}
}
return null;
} else {
List<Object> sequence = CollectionUtil.createNewSequence();
for (TraceRecord traceRecord : traceRecords) {
EMappingResults results = traceRecord.getResult();
if (resolveExp.isIsInverse()) {
if (traceRecord.getContext().getContext() != null) {
Object target = traceRecord.getContext().getContext().getValue().getOclObject();
if (!checkTypeAndCondition(resolveExp, target, visitor, env)) {
continue;
}
sequence.add(target);
}
} else {
for (VarParameterValue varParameterValue : results.getResult()) {
Object target = varParameterValue.getValue().getOclObject();
if (!checkTypeAndCondition(resolveExp, target, visitor, env)) {
continue;
}
sequence.add(target);
}
}
}
return sequence;
}
}
private static EMap<Object, EList<TraceRecord>> chooseKeyToTraceRecordMap(ResolveExp resolveExp, Trace trace) {
return resolveExp.isIsInverse() ? trace.getTargetToTraceRecordMap() : trace.getSourceToTraceRecordMap();
}
private static boolean checkTypeAndCondition(ResolveExp resolveExp, Object resolveCandidate, QvtOperationalEvaluationVisitor visitor,
QvtOperationalEvaluationEnv env) {
if ((resolveExp.getTarget() != null) && (resolveExp.getTarget().getType() != null)) {
EClassifier type = resolveExp.getTarget().getType();
if (!type.isInstance(resolveCandidate)) { // TODO : Perhaps, this won't work on primitive datatypes
return false;
}
}
if (resolveExp.getCondition() != null) {
if ((resolveExp.getTarget() != null) && (resolveExp.getTarget().getName() != null)) {
env.add(resolveExp.getTarget().getName(), resolveCandidate);
}
Object conditionEval = resolveExp.getCondition().accept(visitor);
if ((resolveExp.getTarget() != null) && (resolveExp.getTarget().getName() != null)) {
env.remove(resolveExp.getTarget().getName());
}
return Boolean.TRUE.equals(conditionEval);
}
return true;
}
private static Object createEmptyCollectionOrNull(ResolveExp resolveExp) {
if (resolveExp.isOne()) {
return null;
}
return CollectionUtil.createNewSequence();
}
}