blob: 4d950e67f7b5aa54a18446c276a6c445410772e9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 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
* Alex Paperno - bugs 424584
* Yuri Blankenstein - bug 428325
*******************************************************************************/
package org.eclipse.m2m.internal.qvt.oml.evaluator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.AbstractEList;
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.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EStructuralFeature;
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.ast.parser.QvtOperationalParserUtil;
import org.eclipse.m2m.internal.qvt.oml.ast.parser.QvtOperationalUtil;
import org.eclipse.m2m.internal.qvt.oml.expressions.DirectionKind;
import org.eclipse.m2m.internal.qvt.oml.expressions.MappingOperation;
import org.eclipse.m2m.internal.qvt.oml.expressions.MappingParameter;
import org.eclipse.m2m.internal.qvt.oml.expressions.Module;
import org.eclipse.m2m.internal.qvt.oml.expressions.VarParameter;
import org.eclipse.m2m.internal.qvt.oml.trace.EDirectionKind;
import org.eclipse.m2m.internal.qvt.oml.trace.EMappingContext;
import org.eclipse.m2m.internal.qvt.oml.trace.EMappingOperation;
import org.eclipse.m2m.internal.qvt.oml.trace.EMappingParameters;
import org.eclipse.m2m.internal.qvt.oml.trace.EMappingResults;
import org.eclipse.m2m.internal.qvt.oml.trace.ETuplePartValue;
import org.eclipse.m2m.internal.qvt.oml.trace.EValue;
import org.eclipse.m2m.internal.qvt.oml.trace.Trace;
import org.eclipse.m2m.internal.qvt.oml.trace.TraceFactory;
import org.eclipse.m2m.internal.qvt.oml.trace.TraceRecord;
import org.eclipse.m2m.internal.qvt.oml.trace.VarParameterValue;
import org.eclipse.m2m.qvt.oml.util.Dictionary;
import org.eclipse.m2m.qvt.oml.util.MutableList;
import org.eclipse.m2m.qvt.oml.util.Utils;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.types.TupleType;
import org.eclipse.ocl.util.Bag;
import org.eclipse.ocl.util.Tuple;
import org.eclipse.ocl.utilities.PredefinedType;
public class TraceUtil {
private TraceUtil() {
}
static TraceRecord getTraceRecord(QvtOperationalEvaluationEnv evalEnv, MappingOperation mappingOperation) {
InternalEvaluationEnv internEnv = evalEnv.getAdapter(InternalEvaluationEnv.class);
Trace trace = internEnv.getTraces();
Object selfObj = evalEnv.getValueOf(Environment.SELF_VARIABLE_NAME);
Object key = createKey(selfObj, evalEnv, mappingOperation);
if (key != null) {
TraceRecord record = trace.getRecordBySource(mappingOperation, key);
if (record != null && Boolean.TRUE.equals(TraceUtil.checkResultMatch(record, evalEnv))) {
return record;
}
// nothing found, mapping executed for the first time on the given
// source
return null;
}
// Fall back on 'original' TraceUtil
return TraceUtil.getTraceRecordDefault(evalEnv, mappingOperation);
}
private static TraceRecord getTraceRecordDefault(QvtOperationalEvaluationEnv evalEnv, MappingOperation mappingOperation) {
InternalEvaluationEnv internEnv = evalEnv.getAdapter(InternalEvaluationEnv.class);
Trace trace = internEnv.getTraces();
Object selfObj = evalEnv.getValueOf(Environment.SELF_VARIABLE_NAME);
// the direct fetch by the contextual source object
if(selfObj != null && isParameterLessContextual(mappingOperation)) {
TraceRecord record = trace.getRecordBySource(mappingOperation, selfObj);
if(record != null && Boolean.TRUE.equals(checkResultMatch(record, evalEnv))) {
return record;
}
// nothing found, mapping executed for the first time on the given source
return null;
}
EMap<MappingOperation, EList<TraceRecord>> allTraceRecordMap = trace.getTraceRecordMap();
EList<TraceRecord> traceRecords = allTraceRecordMap.get(mappingOperation);
if (traceRecords == null) {
return null;
}
// Section [8.2.1.15]
// After call resolution, all the parameters of the mapping are passed as a tuple.
// The parameters include, in this order: the context parameter (if any), the owned
// parameters (from Operation::ownedParameter), and the parameters declared as result.
traceCheckCycle:
for (TraceRecord nextRecord : traceRecords) {
// check context parameter
if (QvtOperationalParserUtil.isContextual(mappingOperation)) {
VarParameterValue nextContext = nextRecord.getContext().getContext();
if (nextContext == null) {
continue;
}
if (!isOclEqual(selfObj, nextContext.getValue().getOclObject(), mappingOperation.getContext().getKind(), evalEnv)) {
continue;
}
}
// check owned parameters
int candidateParamSize = mappingOperation.getEParameters().size();
if (nextRecord.getParameters().getParameters().size() != candidateParamSize) {
continue;
}
for (int i = 0; i < candidateParamSize; i++) {
EParameter param = mappingOperation.getEParameters().get(i);
Object paramValue = evalEnv.getValueOf(param.getName());
VarParameterValue traceParamVal = (VarParameterValue) nextRecord.getParameters().getParameters().get(i);
DirectionKind paramKind = DirectionKind.IN;
if (param instanceof VarParameter) {
paramKind = ((VarParameter) param).getKind();
}
if (paramKind != DirectionKind.OUT
&& !isOclEqual(paramValue, traceParamVal.getValue().getOclObject(), paramKind, evalEnv)) {
continue traceCheckCycle;
}
}
// check result parameters
Boolean checkResult = checkResultMatch(nextRecord, evalEnv);
if(checkResult == null) {
continue;
} else if(Boolean.FALSE.equals(checkResult)) {
continue traceCheckCycle;
}
return nextRecord;
}
return null;
}
static TraceRecord addTraceRecord(QvtOperationalEvaluationEnv evalEnv, MappingOperation mappingOperation) {
TraceRecord traceRecord = TraceFactory.eINSTANCE.createTraceRecord();
InternalEvaluationEnv internEnv = evalEnv.getAdapter(InternalEvaluationEnv.class);
Trace trace = internEnv.getTraces();
EList<TraceRecord> allRecList = createOrGetListElementFromMap(trace.getTraceRecordMap(), mappingOperation);
addUnique(traceRecord, allRecList);
EMappingOperation eMappingOperation = TraceFactory.eINSTANCE.createEMappingOperation();
traceRecord.setMappingOperation(eMappingOperation);
eMappingOperation.setName(mappingOperation.getName());
Module module = QvtOperationalParserUtil.getOwningModule(mappingOperation);
eMappingOperation.setPackage(module.getNsPrefix());
eMappingOperation.setModule(module.getName());
eMappingOperation.setRuntimeMappingOperation(mappingOperation);
EMappingContext eMappingContext = TraceFactory.eINSTANCE.createEMappingContext();
traceRecord.setContext(eMappingContext);
if(QvtOperationalParserUtil.isContextual(mappingOperation)) {
VarParameter operContext = mappingOperation.getContext();
VarParameterValue contextVPV = createVarParameterValue(mappingOperation,
operContext.getKind(), operContext.getEType(), Environment.SELF_VARIABLE_NAME, evalEnv);
eMappingContext.setContext(contextVPV);
EList<TraceRecord> contextMappings = createOrGetListElementFromMap(trace.getSourceToTraceRecordMap(), contextVPV.getValue().getOclObject());
addUnique(traceRecord, contextMappings);
}
else if(!mappingOperation.getEParameters().isEmpty()) {
// make the first in parameter as the mapping source object
for (EParameter nextEParam : mappingOperation.getEParameters()) {
if(nextEParam instanceof VarParameter) {
VarParameter firstInVarParam = (VarParameter) nextEParam;
if((firstInVarParam.getEType() instanceof PredefinedType<?> == false) && (firstInVarParam.getKind() == DirectionKind.IN || firstInVarParam.getKind() == DirectionKind.INOUT)) {
Object val = createVarParameterValue(mappingOperation, firstInVarParam.getKind() ,
firstInVarParam.getEType(), firstInVarParam.getName(), evalEnv).getValue().getOclObject();
EList<TraceRecord> sourceMappings = createOrGetListElementFromMap(trace.getSourceToTraceRecordMap(), val);
addUnique(traceRecord, sourceMappings);
break;
}
}
}
}
EMappingParameters eMappingParameters = TraceFactory.eINSTANCE.createEMappingParameters();
traceRecord.setParameters(eMappingParameters);
for (EParameter param : mappingOperation.getEParameters()) {
VarParameter varParameter = (VarParameter) param;
VarParameterValue paramVPV = createVarParameterValue(mappingOperation, varParameter.getKind(),
varParameter.getEType(), varParameter.getName(), evalEnv);
eMappingParameters.getParameters().add(paramVPV);
}
EMappingResults eMappingResults = TraceFactory.eINSTANCE.createEMappingResults();
traceRecord.setResult(eMappingResults);
EList<VarParameter> results = mappingOperation.getResult();
if (!results.isEmpty()) {
String resultVarName = results.size() == 1 ? results.get(0).getName() : Environment.RESULT_VARIABLE_NAME;
EClassifier resultElementType = results.size() == 1 ? results.get(0).getEType() : mappingOperation.getEType();
VarParameterValue resultVPV = createVarParameterValue(mappingOperation, DirectionKind.OUT, resultElementType, resultVarName, evalEnv);
eMappingResults.getResult().add(resultVPV);
EList<TraceRecord> resultMappings = createOrGetListElementFromMap(trace.getTargetToTraceRecordMap(), resultVPV.getValue().getOclObject());
addUnique(traceRecord, resultMappings);
}
// Note: add it here so we ensure the record is fully initialized
addUnique(traceRecord, trace.getTraceRecords());
addTraceRecordByMapping(evalEnv, mappingOperation, traceRecord, trace);
return traceRecord;
}
/**
* Improves performance a lot for mapping operations with parameters by
* means of (re)using the cache.
*/
private static void addTraceRecordByMapping(QvtOperationalEvaluationEnv evalEnv, MappingOperation mappingOperation,
TraceRecord traceRecord, Trace trace) {
Object selfObj = null;
if (traceRecord.getContext() != null && traceRecord.getContext().getContext() != null) {
EValue value = traceRecord.getContext().getContext().getValue();
selfObj = value.getOclObject();
}
Object key = createKey(selfObj, evalEnv, mappingOperation);
if (key != null) {
trace.addRecordBySource(key, mappingOperation, traceRecord);
}
}
/**
* Creates a key for mapping operations, based on the context (if available)
* and all parameters (if any).
*/
private static Object createKey(Object selfObj, QvtOperationalEvaluationEnv evalEnv, MappingOperation mappingOperation) {
EList<EParameter> eParameters = mappingOperation.getEParameters();
if (eParameters.isEmpty()) {
// Backwards compatible for isParameterLessContextual
return selfObj;
}
ArrayList<Object> key = new ArrayList<Object>(eParameters.size() + 1);
key.add(selfObj);
for (EParameter param : eParameters) {
if (param instanceof VarParameter) {
VarParameter varParam = (VarParameter) param;
if (varParam.getKind() == DirectionKind.OUT) {
continue;
}
}
key.add(evalEnv.getValueOf(param.getName()));
}
return key;
}
static Object fetchResultFromTrace(QvtOperationalEvaluationEnv evalEnv, TraceRecord trace) {
MappingOperation operation = trace.getMappingOperation().getRuntimeMappingOperation();
if (operation.getResult().isEmpty()) {
return null;
}
Iterator<EParameter> itParams = operation.getEParameters().iterator();
ListIterator<Object> itArgument = evalEnv.getOperationArgs().listIterator();
Iterator<VarParameterValue> itValues = trace.getParameters().getParameters().iterator();
while (itArgument.hasNext()) {
MappingParameter mappingParam = (MappingParameter) itParams.next();
VarParameterValue value = itValues.next();
itArgument.next();
if (mappingParam.getKind() != DirectionKind.OUT) {
continue;
}
itArgument.set(value.getValue().getOclObject());
}
EList<VarParameterValue> traceResult = trace.getResult().getResult();
assert traceResult.size() == 1;
return traceResult.get(0).getValue().getOclObject();
}
private static VarParameterValue createVarParameterValue(MappingOperation mappingOperation, DirectionKind kind, EClassifier type, String name, QvtOperationalEvaluationEnv evalEnv) {
VarParameterValue varParameterValue = TraceFactory.eINSTANCE.createVarParameterValue();
varParameterValue.setKind(getDirectionKind(kind));
varParameterValue.setName(name);
varParameterValue.setType(type.getName());
Object oclObject = evalEnv.getValueOf(name);
varParameterValue.setValue(createEValue(oclObject));
return varParameterValue;
}
@SuppressWarnings("unchecked")
public static EValue createEValue(Object oclObject) {
EValue value = TraceFactory.eINSTANCE.createEValue();
value.setOclObject(cloneOclObject(oclObject));
if (oclObject != null) {
if (oclObject instanceof Dictionary) {
Dictionary<Object, Object> dict = (Dictionary<Object, Object>) oclObject;
value.setCollectionType("Dictionary"); //$NON-NLS-1$
for (Object dictKey : dict.keys()) {
ETuplePartValue tuplePartValue = TraceFactory.eINSTANCE.createETuplePartValue();
tuplePartValue.setName("key"); //$NON-NLS-1$
tuplePartValue.setValue(createEValue(dictKey));
value.getCollection().add(tuplePartValue);
Object dictValue = dict.get(dictKey);
tuplePartValue = TraceFactory.eINSTANCE.createETuplePartValue();
tuplePartValue.setName("value"); //$NON-NLS-1$
tuplePartValue.setValue(createEValue(dictValue));
value.getCollection().add(tuplePartValue);
}
} else if (oclObject instanceof Tuple) {
Tuple<EOperation, EStructuralFeature> tuple = (Tuple<EOperation, EStructuralFeature>) oclObject;
value.setCollectionType("Tuple"); //$NON-NLS-1$
TupleType<EOperation, EStructuralFeature> tupleType = tuple.getTupleType();
for (EStructuralFeature part : tupleType.oclProperties()) {
Object partValue = tuple.getValue(part);
ETuplePartValue tuplePartValue = TraceFactory.eINSTANCE.createETuplePartValue();
tuplePartValue.setName(part.getName());
EValue partEValue = createEValue(partValue);
tuplePartValue.setValue(partEValue);
value.getCollection().add(tuplePartValue);
}
} else if (oclObject instanceof Collection) {
Collection<Object> oclCollection = (Collection<Object>) oclObject;
// TODO: Write a test for checking collections
value.setCollectionType(getCollectionTypeName(oclCollection));
for (Object collectionElement : oclCollection) {
value.getCollection().add(createEValue(collectionElement));
}
} else if (oclObject instanceof ModelInstance) {
value.setCollectionType("ModelType"); //$NON-NLS-1$
for (Object collectionElement : ((ModelInstance) oclObject).getExtent().getInitialObjects()) {
value.getCollection().add(createEValue(collectionElement));
}
} else if (oclObject instanceof EObject) {
value.setModelElement((EObject) oclObject);
} else {
if (oclObject != null) {
value.setPrimitiveValue(oclObject.toString());
}
}
}
return value;
}
private static String getCollectionTypeName(Collection<?> c) {
String result = "OclCollection"; //$NON-NLS-1$
if (c instanceof MutableList<?>) {
result = "List"; //$NON-NLS-1$
} else if (c instanceof Dictionary<?, ?>) {
result = "Dictionary"; //$NON-NLS-1$
} else if (c instanceof Bag<?>) {
result = "Bag"; //$NON-NLS-1$
} else if (c instanceof LinkedHashSet<?>) {
result = "OrderedSet"; //$NON-NLS-1$
} else if (c instanceof Set<?>) {
result = "Set"; //$NON-NLS-1$
} else if (c instanceof ArrayList<?>) {
result = "Sequence"; //$NON-NLS-1$
}
return result;
}
private static Object cloneOclObject(Object obj) {
return cloneOclObjectRec(obj, new IdentityHashMap<Object, Object>());
}
@SuppressWarnings("unchecked")
private static Object cloneOclObjectRec(Object obj, Map<Object, Object> processed) {
if (obj instanceof MutableList<?>) {
if (processed.containsKey(obj)) {
return processed.get(obj);
}
MutableList<Object> original = (MutableList<Object>) obj;
MutableList<Object> result = Utils.createList();
processed.put(obj, result);
for (Object o : original) {
result.add(cloneOclObjectRec(o, processed));
}
return result;
}
if (obj instanceof Dictionary<?, ?>) {
if (processed.containsKey(obj)) {
return processed.get(obj);
}
Dictionary<Object, Object> original = (Dictionary<Object, Object>) obj;
Dictionary<Object, Object> result = Utils.createDictionary();
processed.put(obj, result);
for (Object k : original.keys()) {
result.put(cloneOclObjectRec(k, processed), cloneOclObjectRec(original.get(k), processed));
}
return result;
}
return obj;
}
private static EDirectionKind getDirectionKind(DirectionKind kind) {
if (kind == DirectionKind.IN) {
return EDirectionKind.IN;
} else if (kind == DirectionKind.INOUT) {
return EDirectionKind.INOUT;
} else if (kind == DirectionKind.OUT) {
return EDirectionKind.OUT;
}
throw new RuntimeException("Wrong DirectionKind: " + kind.name()); //$NON-NLS-1$
}
private static <K, T> EList<T> createOrGetListElementFromMap(EMap<K, EList<T>> map, K key) {
EList<T> list = map.get(key);
if (list == null) {
list = new BasicEList<T>();
map.put(key, list);
list = map.get(key);
}
return list;
}
private static boolean isOclEqual(Object candidateObject, Object traceObject, DirectionKind directionKind, QvtOperationalEvaluationEnv evalEnv) {
if (directionKind == DirectionKind.OUT) {
if (candidateObject == null) {
// yet not bound 'out' parameter, suit for any
return true;
}
}
if (candidateObject == traceObject) {
return true;
}
if (QvtOperationalUtil.isUndefined(candidateObject, evalEnv)) {
return QvtOperationalUtil.isUndefined(traceObject, evalEnv);
}
if ((candidateObject == null) || (traceObject == null)) {
return false;
}
return candidateObject.equals(traceObject); // Overridden equals() is implied
}
private static Boolean checkResultMatch(TraceRecord nextRecord, QvtOperationalEvaluationEnv evalEnv) {
// check result parameters
Object resultValue = evalEnv.getValueOf(Environment.RESULT_VARIABLE_NAME);
if (resultValue != null) {
List<Object> resultValues = new ArrayList<Object>(1);
resultValues.add(resultValue);
if (nextRecord.getResult().getResult().size() != resultValues.size()) {
return null;
}
for (int i = 0, n = resultValues.size(); i < n; i++) {
Object paramValue = resultValues.get(i);
VarParameterValue traceParamVal = (VarParameterValue) nextRecord.getResult().getResult().get(i);
if (!isOclEqual(paramValue, traceParamVal.getValue().getOclObject(), DirectionKind.OUT, evalEnv)) {
return Boolean.FALSE;
}
}
}
return Boolean.TRUE;
}
private static boolean isParameterLessContextual(MappingOperation mappingOperation) {
return QvtOperationalParserUtil.isContextual(mappingOperation) && mappingOperation.getEParameters().isEmpty();
}
private static void addUnique(TraceRecord record, EList<TraceRecord> recordList) {
if(recordList instanceof AbstractEList<?>) {
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=287589
AbstractEList<TraceRecord> basicRecList = (AbstractEList<TraceRecord>) recordList;
basicRecList.addUnique(record);
} else {
// TODO - spit a trace warning
recordList.add(record);
}
}
}