/*******************************************************************************
* Copyright (c) 2008, 2015 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 - bug 424584
* Christopher Gerking - bug 446375
*******************************************************************************/
package org.eclipse.m2m.internal.qvt.oml.ast.parser;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.m2m.internal.qvt.oml.NLS;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalEnv;
import org.eclipse.m2m.internal.qvt.oml.cst.MappingExtensionKindCS;
import org.eclipse.m2m.internal.qvt.oml.expressions.DirectionKind;
import org.eclipse.m2m.internal.qvt.oml.expressions.MappingBody;
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.VarParameter;
import org.eclipse.ocl.cst.CSTNode;
import org.eclipse.ocl.util.TypeUtil;
import org.eclipse.ocl.utilities.UMLReflection;
class MappingExtensionHelper {
private static final class DisjunctValidator extends ExtensionValidator {
private DisjunctValidator(MappingOperation operation,
List<MappingOperation> extendedOperations,
MappingExtensionKindCS extensionKind) {
super(operation, extendedOperations, extensionKind);
}
@Override
List<MappingSourceReference> getMappingSourceRefs() {
return getSrcAdapter().getDisjunctReferences();
}
@Override
boolean isConformantForExtension(MappingOperation extendingOper, MappingOperation extendedOper, QvtOperationalEnv env) {
return isDisjunctCompatible(extendingOper, extendedOper, env);
}
@Override
boolean validate(QvtOperationalEnv env) {
boolean result = true;
MappingBody body = (MappingBody)fOperation.getBody();
if (body != null) {
boolean isEmptyBody = body.getContent().isEmpty() ||
(body.getContent().size() == 1
&& body.getContent().get(0).getStartPosition() == body.getStartPosition()
&& body.getContent().get(0).getEndPosition() == body.getEndPosition());
boolean nonEmptyMapping = !isEmptyBody
|| !body.getInitSection().isEmpty()
|| !body.getEndSection().isEmpty();
if(nonEmptyMapping) {
env.reportWarning(NLS.bind(ValidationMessages.MappingExtension_disjunctingMappingBodyNotExecuted,
QvtOperationalParserUtil.safeGetMappingQualifiedName(env, fOperation)),
body.getStartPosition(), body.getEndPosition());
}
}
result &= reportInvalidExtensionsInDisjunctingMapping(env, fOperation.getInherited(), MappingExtensionKindCS.INHERITS);
result &= reportInvalidExtensionsInDisjunctingMapping(env, fOperation.getMerged(), MappingExtensionKindCS.MERGES);
result &= reportInvalidOutParameters(env);
return result && super.validate(env);
}
private boolean reportInvalidOutParameters(QvtOperationalEnv env) {
boolean result = true;
int pos = 0;
for (MappingOperation extended : fOperation.getDisjunct()) {
Iterator<EParameter> itParams = fOperation.getEParameters().iterator();
Iterator<EParameter> itExtendedParams = extended.getEParameters().iterator();
while (itParams.hasNext()) {
MappingParameter mappingParam = (MappingParameter) itParams.next();
if (mappingParam.getKind() != DirectionKind.OUT) {
continue;
}
if (!itExtendedParams.hasNext()) {
break;
}
if (itExtendedParams.next().getEType() != mappingParam.getEType()) {
result = false;
ExtensionSourceRefAdapter adapter = getSrcAdapter();
if (adapter != null) {
int startOffset = extended.getStartPosition();
int endOffset = extended.getEndPosition();
MappingSourceReference ref = safeGetSourceRef(adapter.getDisjunctReferences(), pos);
if (ref != null) {
startOffset = ref.getStartOffset();
endOffset = ref.getEndOffset();
}
env.reportError(
NLS.bind(
ValidationMessages.MappingExtension_illegalOutParamDisjunctingMapping,
new Object[] {
QvtOperationalParserUtil.safeGetMappingQualifiedName(env, extended),
mappingParam.getName(),
QvtOperationalParserUtil.safeGetMappingQualifiedName(env, fOperation)
}
),
startOffset, endOffset);
}
}
}
pos++;
}
return result;
}
private boolean reportInvalidExtensionsInDisjunctingMapping(QvtOperationalEnv env, EList<MappingOperation> extendedOperations, MappingExtensionKindCS kind) {
boolean result = true;
int pos = 0;
for (MappingOperation extended : extendedOperations) {
result = false;
ExtensionSourceRefAdapter adapter = getSrcAdapter();
if(adapter != null) {
int startOffset = extended.getStartPosition();
int endOffset = extended.getEndPosition();
MappingSourceReference ref = null;
if(kind == MappingExtensionKindCS.INHERITS) {
ref = safeGetSourceRef(adapter.getInheritReferences(), pos++);
} else if(kind == MappingExtensionKindCS.MERGES) {
ref = safeGetSourceRef(adapter.getMergeReferences(), pos++);
}
if(ref != null) {
startOffset = ref.getStartOffset();
endOffset = ref.getEndOffset();
}
env.reportError(NLS.bind(ValidationMessages.MappingExtension_illegalExtensionKindOnDisjunctingMapping,
new Object[] {
QvtOperationalParserUtil.safeGetMappingQualifiedName(env, fOperation),
kind.getName(),
QvtOperationalParserUtil.safeGetMappingQualifiedName(env, extended),
}), startOffset, endOffset);
}
}
return result;
}
}
private static class MappingSourceReference {
private int startOffset;
private int endOffset;
private MappingSourceReference(int startOffset, int endOffset) {
this.startOffset = startOffset;
this.endOffset = endOffset;
}
public int getStartOffset() { return startOffset; }
public int getEndOffset() { return endOffset; }
}
private static class ExtensionSourceRefAdapter extends AdapterImpl {
List<MappingSourceReference> inheritRefs;
List<MappingSourceReference> disjunctRefs;
List<MappingSourceReference> mergeRefs;
ExtensionSourceRefAdapter() {
super();
}
void addDisjunct(MappingOperation mapping, CSTNode sourceRef) {
if(disjunctRefs == null) {
disjunctRefs = new ArrayList<MappingSourceReference>();
}
addRef(mapping, sourceRef, disjunctRefs);
}
void addInherit(MappingOperation mapping, CSTNode sourceRef) {
if(inheritRefs == null) {
inheritRefs = new ArrayList<MappingSourceReference>();
}
addRef(mapping, sourceRef, inheritRefs);
}
void addMerge(MappingOperation mapping, CSTNode sourceRef) {
if(mergeRefs == null) {
mergeRefs = new ArrayList<MappingSourceReference>();
}
addRef(mapping, sourceRef, mergeRefs);
}
@Override
public boolean isAdapterForType(Object type) {
if(type instanceof Class<?>) {
return ExtensionSourceRefAdapter.class.isAssignableFrom((Class<?>)type);
}
return super.isAdapterForType(ExtensionSourceRefAdapter.class);
}
static void addRef(MappingOperation mapping, CSTNode sourceRef, List<MappingSourceReference> refList) {
refList.add(new MappingSourceReference(sourceRef.getStartOffset(), sourceRef.getEndOffset()));
}
public List<MappingSourceReference> getInheritReferences() {
return inheritRefs;
}
public List<MappingSourceReference> getDisjunctReferences() {
return disjunctRefs;
}
public List<MappingSourceReference> getMergeReferences() {
return mergeRefs;
}
}
private static abstract class ExtensionValidator {
MappingOperation fOperation;
List<MappingOperation> fExtendedOpers;
ExtensionSourceRefAdapter fAdapter;
boolean shouldUseSourceAdapter;
MappingExtensionKindCS fExtensionKind;
ExtensionValidator(MappingOperation operation, List<MappingOperation> extendedOperations, MappingExtensionKindCS extensionKind) {
fOperation = operation;
fExtendedOpers = extendedOperations;
fExtensionKind = extensionKind;
shouldUseSourceAdapter = true;
}
ExtensionSourceRefAdapter getSrcAdapter() {
if(fAdapter == null) {
fAdapter = MappingExtensionHelper.getSourceRefAdapter(fOperation);
if(getMappingSourceRefs().size() != fExtendedOpers.size()) {
// TODO - inconsistent mapping source reference adapter constructed by the parser, use trace, etc.
// Resolved by problem attached to the mapping operation AST
shouldUseSourceAdapter = false;
}
}
return shouldUseSourceAdapter ? fAdapter : null;
}
boolean validate(QvtOperationalEnv env) {
boolean isValid = true;
for(int i = 0; i < fExtendedOpers.size(); i++) {
MappingOperation extended = fExtendedOpers.get(i);
boolean isNonConformantSignature = !isConformantForExtension(fOperation, extended, env);
isValid &= !isNonConformantSignature;
if(isNonConformantSignature) {
int startOffset = fOperation.getStartPosition();
int endOffset = fOperation.getEndPosition();
ExtensionSourceRefAdapter adapter = getSrcAdapter();
if(adapter != null) {
MappingSourceReference ref = safeGetSourceRef(i);
if(ref != null) {
startOffset = ref.getStartOffset();
endOffset = ref.getEndOffset();
}
}
String errMessage = NLS.bind(ValidationMessages.MappingExtension_nonConformantSignatureForMappingExtension, new Object[] {
QvtOperationalParserUtil.safeGetMappingQualifiedName(env, extended), fExtensionKind,
QvtOperationalParserUtil.safeGetMappingQualifiedName(env, fOperation)
});
env.reportError(errMessage, startOffset, endOffset);
}
}
return isValid;
}
static MappingSourceReference safeGetSourceRef(List<MappingSourceReference> refList, int pos) {
return (pos >= refList.size()) ? null : refList.get(pos);
}
MappingSourceReference safeGetSourceRef(int pos) {
return safeGetSourceRef(getMappingSourceRefs(), pos);
}
abstract List<MappingSourceReference> getMappingSourceRefs();
abstract boolean isConformantForExtension(MappingOperation extendingOper, MappingOperation extendedOper, QvtOperationalEnv env);
}
private static ExtensionSourceRefAdapter attachSourceRefAdapter(MappingOperation mapping) {
ExtensionSourceRefAdapter adapter = new ExtensionSourceRefAdapter();
mapping.eAdapters().add(adapter);
return adapter;
}
public static void bind2SourceElement(MappingOperation extendedOperation, CSTNode sourceRefElement, MappingExtensionKindCS extensionKindCS) {
ExtensionSourceRefAdapter adapter = getSourceRefAdapter(extendedOperation);
if(adapter == null) {
adapter = attachSourceRefAdapter(extendedOperation);
}
if(extensionKindCS == MappingExtensionKindCS.INHERITS) {
adapter.addInherit(extendedOperation, sourceRefElement);
} else if(extensionKindCS == MappingExtensionKindCS.DISJUNCTS) {
adapter.addDisjunct(extendedOperation, sourceRefElement);
} else if(extensionKindCS == MappingExtensionKindCS.MERGES) {
adapter.addMerge(extendedOperation, sourceRefElement);
} else {
assert false : "Uknown extension kind"; //$NON-NLS-1$
}
}
private static ExtensionSourceRefAdapter getSourceRefAdapter(MappingOperation mapping) {
return (ExtensionSourceRefAdapter)EcoreUtil.getExistingAdapter(mapping, ExtensionSourceRefAdapter.class);
}
public static boolean validate(MappingOperation operation, QvtOperationalEnv env) {
boolean isValid = true;
if(!operation.getInherited().isEmpty()) {
isValid &= new ExtensionValidator(operation, operation.getInherited(), MappingExtensionKindCS.INHERITS) {
@Override
List<MappingSourceReference> getMappingSourceRefs() {
return getSrcAdapter().getInheritReferences();
}
@Override
boolean isConformantForExtension(MappingOperation extendingOper, MappingOperation extendedOper, QvtOperationalEnv env) {
return isInheritCompatible(extendingOper, extendedOper, env);
}
}.validate(env);
}
if(!operation.getDisjunct().isEmpty()) {
isValid &= new DisjunctValidator(operation, operation.getDisjunct(), MappingExtensionKindCS.DISJUNCTS).validate(env);
}
if(!operation.getMerged().isEmpty()) {
isValid &= new ExtensionValidator(operation, operation.getMerged(), MappingExtensionKindCS.MERGES) {
@Override
List<MappingSourceReference> getMappingSourceRefs() {
return getSrcAdapter().getMergeReferences();
}
@Override
boolean isConformantForExtension(MappingOperation extendingOper, MappingOperation extendedOper, QvtOperationalEnv env) {
return isMergeCompatible(extendingOper, extendedOper, env);
}
}.validate(env);
}
return isValid;
}
public static boolean isMergeCompatible(MappingOperation mergingOperation, MappingOperation mergedOperation, QvtOperationalEnv env) {
return isCaller2CalleeCompatible(mergedOperation, mergingOperation, env);
}
public static boolean isDisjunctCompatible(MappingOperation disjunctingOperation, MappingOperation disjunctedOperation, QvtOperationalEnv env) {
return isCaller2CalleeCompatible(disjunctingOperation, disjunctedOperation, env);
}
public static boolean isInheritCompatible(MappingOperation inheritingOperation, MappingOperation intheritedOperation, QvtOperationalEnv env) {
return isCaller2CalleeCompatible(intheritedOperation, inheritingOperation, env);
}
public static Collection<MappingOperation> checkForExtensionCycle(MappingOperation extendingOperation) {
// TODO - investigate possible rules for checking cyclic reused mapping references
return Collections.emptyList();
}
static boolean isCaller2CalleeCompatible(MappingOperation extendingOper, MappingOperation extendedOper, QvtOperationalEnv env) {
if(extendingOper.getEParameters().size() != extendedOper.getEParameters().size()) {
return false;
}
EClassifier ctx1 = QvtOperationalParserUtil.getContextualType(extendingOper);
EClassifier ctx2 = QvtOperationalParserUtil.getContextualType(extendedOper);
if(ctx1 == null || ctx2 == null) {
if(ctx1 != ctx2) {
return false;
}
} else if(!isAssignableTo(ctx2, ctx1, env)) {
return false;
}
if(!isParameterListCaller2CalleeCompatible(extendingOper.getEParameters(), extendedOper.getEParameters(), env)) {
return false;
}
if(!isParameterListCaller2CalleeCompatible(extendingOper.getResult(), extendedOper.getResult(), env)) {
return false;
}
if(extendingOper.getEType() == null || extendedOper.getEType() == null) {
return false;
}
return extendingOper.getResult().size() == 1 || isAssignableTo(extendingOper.getEType(), extendedOper.getEType(), env);
}
private static boolean isParameterListCaller2CalleeCompatible(EList<? extends EParameter> callerParams, EList<? extends EParameter> calleeParams, QvtOperationalEnv env) {
if(callerParams.size() != calleeParams.size()) {
return false;
}
for(int i = 0; i < calleeParams.size(); i++) {
EParameter callerPar = callerParams.get(i);
EParameter calleePar = calleeParams.get(i);
boolean isAssignable = isAssignableTo(calleePar.getEType(), callerPar.getEType(), env);
boolean isDirectionOK = true;
if(calleePar instanceof VarParameter) {
if(callerPar instanceof VarParameter) {
isDirectionOK = isDirectionKindCaller2CalleeCompatible(
((VarParameter)callerPar).getKind(), ((VarParameter)calleePar).getKind());
} else {
return false;
}
}
if(!isAssignable || !isDirectionOK) {
return false;
}
}
return true;
}
private static boolean isDirectionKindCaller2CalleeCompatible(DirectionKind callerKind, DirectionKind calleeKind) {
if(callerKind == calleeKind) {
return true;
}
if(callerKind == DirectionKind.IN) {
return calleeKind == DirectionKind.INOUT || calleeKind == DirectionKind.IN;
} else if(callerKind == DirectionKind.INOUT) {
return calleeKind == DirectionKind.INOUT;
}
return false;
}
private static boolean isAssignableTo(EClassifier sourceType, EClassifier targetType, QvtOperationalEnv env) {
return (TypeUtil.getRelationship(env, sourceType, targetType) & UMLReflection.SUBTYPE) != 0;
}
}