[ocl2qvtp] - Introducing OCL2QVTm component
diff --git a/plugins/org.eclipse.qvtd.cs2as.compiler/src/org/eclipse/qvtd/cs2as/compiler/internal/OCL2QVTiCompilerChain.java b/plugins/org.eclipse.qvtd.cs2as.compiler/src/org/eclipse/qvtd/cs2as/compiler/internal/OCL2QVTiCompilerChain.java
index ab1218f..167eeab 100644
--- a/plugins/org.eclipse.qvtd.cs2as.compiler/src/org/eclipse/qvtd/cs2as/compiler/internal/OCL2QVTiCompilerChain.java
+++ b/plugins/org.eclipse.qvtd.cs2as.compiler/src/org/eclipse/qvtd/cs2as/compiler/internal/OCL2QVTiCompilerChain.java
@@ -47,7 +47,8 @@
 
 	@Override
 	public @NonNull Transformation compile(@NonNull String enforcedOutputName) throws IOException {
-		return qvtp2qvti(ocl2qvtp());
+		return qvtp2qvti(qvtm2qvtp(ocl2qvtm(oclASUri)));
+		// return qvtp2qvti(ocl2qvtp());
 	}
 	
 	public @NonNull Transformation compile() throws IOException {
@@ -77,6 +78,13 @@
 		return pResource;
 	}
 	
+	protected Resource ocl2qvtm(URI oclURI) throws IOException {
+		OCL2QVTp ocl2qvtm = new OCL2QVTp(environmentFactory, traceabilityPropName);
+		Resource mResource = ocl2qvtm.run(environmentFactory.getMetamodelManager().getASResourceSet(), oclURI);
+		saveResource(mResource, QVTM_STEP);
+		return mResource;
+	}
+	
 	private @NonNull String getTraceabilityPropertyName() {
 		String tracePropName = getOption(QVTP_STEP, TRACE_PROPERTY_NAME_KEY);
 		return tracePropName == null ? DEFAULT_TRACE_PROPERTY_NAME : tracePropName;
diff --git a/plugins/org.eclipse.qvtd.cs2as.compiler/src/org/eclipse/qvtd/cs2as/compiler/internal/OCL2QVTm.java b/plugins/org.eclipse.qvtd.cs2as.compiler/src/org/eclipse/qvtd/cs2as/compiler/internal/OCL2QVTm.java
new file mode 100644
index 0000000..fbd104e
--- /dev/null
+++ b/plugins/org.eclipse.qvtd.cs2as.compiler/src/org/eclipse/qvtd/cs2as/compiler/internal/OCL2QVTm.java
@@ -0,0 +1,460 @@
+package org.eclipse.qvtd.cs2as.compiler.internal;
+
+import static org.eclipse.qvtd.cs2as.compiler.internal.OCL2QVTpUtil.firstToLowerCase;
+import static org.eclipse.qvtd.cs2as.compiler.internal.OCL2QVTpUtil.getAllContainers;
+import static org.eclipse.qvtd.cs2as.compiler.internal.OCL2QVTpUtil.getAllContents;
+import static org.eclipse.qvtd.cs2as.compiler.internal.OCL2QVTpUtil.getAllContentsIncludingSelf;
+import static org.eclipse.qvtd.cs2as.compiler.internal.OCL2QVTpUtil.getCreationMappingName;
+import static org.eclipse.qvtd.cs2as.compiler.internal.OCL2QVTpUtil.getExpressionContextType;
+import static org.eclipse.qvtd.cs2as.compiler.internal.OCL2QVTpUtil.getSuperClasses;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.ocl.pivot.Class;
+import org.eclipse.ocl.pivot.IfExp;
+import org.eclipse.ocl.pivot.Import;
+import org.eclipse.ocl.pivot.Model;
+import org.eclipse.ocl.pivot.Namespace;
+import org.eclipse.ocl.pivot.OCLExpression;
+import org.eclipse.ocl.pivot.Operation;
+import org.eclipse.ocl.pivot.OperationCallExp;
+import org.eclipse.ocl.pivot.Package;
+import org.eclipse.ocl.pivot.PivotFactory;
+import org.eclipse.ocl.pivot.Property;
+import org.eclipse.ocl.pivot.PropertyCallExp;
+import org.eclipse.ocl.pivot.ShadowExp;
+import org.eclipse.ocl.pivot.ShadowPart;
+import org.eclipse.ocl.pivot.Type;
+import org.eclipse.ocl.pivot.TypeExp;
+import org.eclipse.ocl.pivot.Variable;
+import org.eclipse.ocl.pivot.VariableExp;
+import org.eclipse.ocl.pivot.utilities.EnvironmentFactory;
+import org.eclipse.qvtd.pivot.qvtbase.QVTbaseFactory;
+import org.eclipse.qvtd.pivot.qvtbase.Transformation;
+import org.eclipse.qvtd.pivot.qvtbase.TypedModel;
+import org.eclipse.qvtd.pivot.qvtcore.CoreModel;
+import org.eclipse.qvtd.pivot.qvtcore.Mapping;
+import org.eclipse.qvtd.pivot.qvtcore.QVTcoreFactory;
+import org.eclipse.qvtd.pivot.qvtcorebase.BottomPattern;
+import org.eclipse.qvtd.pivot.qvtcorebase.CoreDomain;
+import org.eclipse.qvtd.pivot.qvtcorebase.GuardPattern;
+import org.eclipse.qvtd.pivot.qvtcorebase.PropertyAssignment;
+import org.eclipse.qvtd.pivot.qvtcorebase.QVTcoreBaseFactory;
+import org.eclipse.qvtd.pivot.qvtcorebase.RealizedVariable;
+
+public class OCL2QVTm {
+
+	private @NonNull Logger logger = Logger.getLogger(getClass().getName());
+	private @NonNull EnvironmentFactory envFact;
+	private @NonNull String traceabilityPropName;
+	private @Nullable Map<?,?> saveOptions;
+	
+	public static final @NonNull String RIGHT_MODEL_TYPE_NAME = "rightAS";
+	public static final @NonNull String LEFT_MODEL_TYPE_NAME = "leftCS";
+	
+	public OCL2QVTm(@NonNull EnvironmentFactory envFact, @NonNull String traceabilityPropName) {
+		this.envFact = envFact;
+		this.traceabilityPropName = traceabilityPropName;
+	}
+	
+	public Resource run(ResourceSet resourceSet, URI oclDocURI) {
+		
+		if (!"oclas".equals(oclDocURI.fileExtension())) {
+			throw new IllegalArgumentException(oclDocURI.toString() + " is not an .oclas URI");
+		}
+		Resource input = resourceSet.getResource(oclDocURI, true);
+		EObject rootModel = input.getContents().get(0);
+		if (rootModel instanceof Model) {
+			Model model = (Model) rootModel;
+			CoreModel outputModel = oclModelToImperativeModel().apply(model);
+			
+			// We create the output resource
+			URI outputURI = oclDocURI.trimFileExtension().trimFileExtension().appendFileExtension("qvtm");
+			Resource outputResource = resourceSet.createResource(outputURI);
+			outputResource.getContents().add(outputModel);
+			return outputResource;
+		
+		} else {
+			throw new IllegalArgumentException(oclDocURI.toString() + " doesn't contain an OCL Model");
+		}
+	}
+	
+	private TypedModel leftTypedModel = QVTbaseFactory.eINSTANCE.createTypedModel();
+	private TypedModel rightTypedModel = QVTbaseFactory.eINSTANCE.createTypedModel();
+	
+	public Function<Model, CoreModel> oclModelToImperativeModel() {
+		return oclModel -> {
+		
+			CoreModel iModel = QVTcoreFactory.eINSTANCE.createCoreModel();
+		
+		List<Operation> allAstOps = getAllContents().apply(oclModel)
+					.filter(isAstOp())
+					.map(Operation.class::cast).collect(Collectors.toList());
+		List<ShadowExp> shadowExps = allAstOps.stream()
+					.flatMap(getAllContents())
+					.filter(ShadowExp.class::isInstance)
+					.map(ShadowExp.class::cast).collect(Collectors.toList());
+		
+		iModel.setExternalURI(oclModel.getExternalURI().replace(".ocl", ".qvtm")); // When the externalURI is set, also is its name
+		iModel.getOwnedImports().addAll(oclModel.getOwnedImports().stream()
+				.map(importToImport())
+				.collect(Collectors.toList()));
+		
+		Package pPackage = PivotFactory.eINSTANCE.createPackage();
+		pPackage.setName("");
+		iModel.getOwnedPackages().add(pPackage);
+		
+		Transformation pTx = QVTbaseFactory.eINSTANCE.createTransformation();
+		pTx.setName(iModel.getName().replace('.', '_')); // FIXME . as part of the name is causing issues in the CG);
+		pPackage.getOwnedClasses().add(pTx);
+		
+		List<Package> importedPackges = new ArrayList<Package>();
+		for (Namespace ns : iModel.getOwnedImports().stream()
+							.map(x -> x.getImportedNamespace())
+							.collect(Collectors.toList())) {
+			if (ns instanceof Model) {
+				importedPackges.addAll(((Model)ns).getOwnedPackages());
+			} else if (ns instanceof Package) {
+				importedPackges.add((Package) ns);
+			} else {
+				logger.warning("imported namespace not recognised: " + ns.getName());
+			}
+		}
+		
+		leftTypedModel.setName(LEFT_MODEL_TYPE_NAME);
+		leftTypedModel.getUsedPackage().add(
+				importedPackges.stream()
+				.filter(p -> p.getName().equals(getExpressionContextType().apply(shadowExps.get(0)).getOwningPackage().getName()))
+				.findFirst().get());
+		pTx.getModelParameter().add(leftTypedModel);
+		
+		rightTypedModel.setName(RIGHT_MODEL_TYPE_NAME);
+		rightTypedModel.getUsedPackage().add(
+				importedPackges.stream()
+				.filter(p -> p.getName().equals(shadowExps.get(0).getType().getOwningPackage().getName()))
+				.findFirst().get());
+		pTx.getModelParameter().add(rightTypedModel);
+		
+		pTx.getRule().addAll(shadowExps.stream()
+				.filter(shadowExpToCreationMappingGuard())
+				.map(shadowExpToCreationMapping())
+				.collect(Collectors.toList()));
+		return iModel;
+		};
+	}
+	
+	public Function<@NonNull Import, @NonNull Import> importToImport(){
+		return oclImport -> {
+		Import pImport = PivotFactory.eINSTANCE.createImport();
+		pImport.setName(oclImport.getName());
+		pImport.setImportedNamespace(oclImport.getImportedNamespace());
+		return pImport;
+		};
+	}
+	
+	private Predicate<@NonNull ShadowExp> shadowExpToCreationMappingGuard() {
+		return shadowExp -> {
+			return ! getAllContainers().apply(shadowExp)
+					.anyMatch(ShadowExp.class::isInstance);	
+		};
+	}
+	
+	public Function<@NonNull ShadowExp, @NonNull Mapping>  shadowExpToCreationMapping() {
+		return shadowExp -> {
+			Mapping mapping = QVTcoreFactory.eINSTANCE.createMapping();
+			mapping.setName(getCreationMappingName().apply(shadowExp));
+			
+			CoreDomain leftDomain = createCreationMapping_LeftDomain(shadowExp);
+			CoreDomain rightDomain = createCreationMapping_RightDomain(shadowExp);
+			mapping.getDomain().add(leftDomain);
+			mapping.getDomain().add(rightDomain);
+			
+			Variable leftVar = leftDomain.getGuardPattern().getVariable().get(0);
+			Variable rightVar = rightDomain.getBottomPattern().getVariable().get(0);
+			
+			GuardPattern guardPattern = QVTcoreBaseFactory.eINSTANCE.createGuardPattern();
+			BottomPattern bottomPattern = QVTcoreBaseFactory.eINSTANCE.createBottomPattern();
+			mapping.setGuardPattern(guardPattern);
+			mapping.setBottomPattern(bottomPattern);
+			
+			PropertyAssignment pAssignment = QVTcoreBaseFactory.eINSTANCE.createPropertyAssignment();
+			VariableExp value = PivotFactory.eINSTANCE.createVariableExp();
+			value.setReferredVariable(rightVar);
+			value.setType(value.getReferredVariable().getType());
+			
+			VariableExp slotExpression = PivotFactory.eINSTANCE.createVariableExp();
+			slotExpression.setReferredVariable(leftVar);
+			slotExpression.setType(slotExpression.getReferredVariable().getType());
+			
+			pAssignment.setValue(value);
+			pAssignment.setSlotExpression(slotExpression);
+			pAssignment.setTargetProperty(getTraceabilityProperty(slotExpression.getType()));
+			
+			bottomPattern.getAssignment().add(pAssignment);
+			updateGuardPattern(shadowExp, guardPattern, leftVar);
+			bottomPattern.getAssignment().addAll(shadowExp.getOwnedParts().stream().
+					map(shadowPartToPropertyAssignment(leftVar, rightVar)).
+					collect(Collectors.toList()));
+			
+			return mapping;
+		};
+	}
+	
+	
+	public Function<@NonNull ShadowPart, @NonNull PropertyAssignment>  shadowPartToPropertyAssignment(Variable leftVar, Variable rightVar) {
+		return shadowPart -> {
+			VariableExp slotExpression = PivotFactory.eINSTANCE.createVariableExp();
+			slotExpression.setReferredVariable(rightVar);
+			slotExpression.setType(slotExpression.getReferredVariable().getType());
+			
+			PropertyAssignment pAssignment = QVTcoreBaseFactory.eINSTANCE.createPropertyAssignment();
+			pAssignment.setTargetProperty(shadowPart.getReferredProperty());
+			pAssignment.setValue(createPropertyAssignmentValue(shadowPart, leftVar));
+			pAssignment.setSlotExpression(slotExpression);
+			return pAssignment;
+		};
+	}
+	
+	
+	private CoreDomain createCreationMapping_LeftDomain(ShadowExp shadowExp) {
+		
+		Class contextType = getExpressionContextType().apply(shadowExp);
+		CoreDomain domain = QVTcoreBaseFactory.eINSTANCE.createCoreDomain();
+		domain.setTypedModel(leftTypedModel);
+		domain.setIsCheckable(true);
+		
+		GuardPattern guardPattern = QVTcoreBaseFactory.eINSTANCE.createGuardPattern();
+		BottomPattern bottomPattern = QVTcoreBaseFactory.eINSTANCE.createBottomPattern();
+		domain.setGuardPattern(guardPattern);
+		domain.setBottomPattern(bottomPattern);
+		
+		Variable variable = PivotFactory.eINSTANCE.createVariable();
+		variable.setName(firstToLowerCase().apply(contextType.getName()));
+		variable.setType(contextType);
+		
+		guardPattern.getVariable().add(variable);
+		
+		return domain;
+	}
+	
+	private CoreDomain createCreationMapping_RightDomain(ShadowExp shadowExp) {
+
+		Class constructedType = shadowExp.getType();
+		CoreDomain domain = QVTcoreBaseFactory.eINSTANCE.createCoreDomain();
+		domain.setTypedModel(rightTypedModel);
+		domain.setIsEnforceable(true);
+		
+		GuardPattern guardPattern = QVTcoreBaseFactory.eINSTANCE.createGuardPattern();
+		BottomPattern bottomPattern = QVTcoreBaseFactory.eINSTANCE.createBottomPattern();
+		domain.setGuardPattern(guardPattern);
+		domain.setBottomPattern(bottomPattern);
+		
+		RealizedVariable variable = QVTcoreBaseFactory.eINSTANCE.createRealizedVariable();
+		variable.setName(firstToLowerCase().apply(constructedType.getName()));
+		variable.setType(constructedType);
+		
+		bottomPattern.getRealizedVariable().add(variable);
+		return domain;
+	}
+	
+	private OCLExpression createPropertyAssignmentValue(ShadowPart shadowPart, Variable leftVar) {
+		
+		// FIXME what happens with synthetised types ????
+		OCLExpression initExp = shadowPart.getOwnedInit();
+		OCLExpression newInitExp = EcoreUtil.copy(initExp);
+		//We need to replace the OCL refered "self" varible by the QVTi domain "leftVar" and ast op calls
+		return doReplacements(newInitExp, leftVar);
+	}
+	
+	private OCLExpression doReplacements(OCLExpression oclExp, Variable leftVar) {
+		
+		List<OCLExpression> result = new ArrayList<OCLExpression>();// Simple work aroound to the forEach constraint ;
+		result.add(oclExp);
+		getAllContentsIncludingSelf().apply(oclExp).forEach(x -> {
+			if (isSelfVarExp().test(x)) {
+				((VariableExp)x).setReferredVariable(leftVar);
+			} else if(isAstOpCallExp().test(x)) {
+				OperationCallExp exp = (OperationCallExp) x;
+				PropertyCallExp astPropCallExp = PivotFactory.eINSTANCE.createPropertyCallExp();
+				astPropCallExp.setOwnedSource(exp.getOwnedSource());
+				astPropCallExp.setReferredProperty(getTraceabilityProperty(astPropCallExp.getOwnedSource().getType()));
+				astPropCallExp.setType(astPropCallExp.getReferredProperty().getType());
+				// Copying remaining changeable OpCallExp properties (excepting ownedArguments and referredOperation)
+				astPropCallExp.setTypeValue(exp.getTypeValue());
+				astPropCallExp.getOwnedComments().addAll(exp.getOwnedComments());
+				astPropCallExp.getOwnedExtensions().addAll(exp.getOwnedExtensions());
+				astPropCallExp.setName(exp.getName());
+				astPropCallExp.setIsSafe(exp.isIsSafe());
+				astPropCallExp.setIsRequired(exp.isIsRequired());
+				astPropCallExp.setIsImplicit(exp.isIsImplicit());
+				astPropCallExp.setIsPre(exp.isIsPre());
+				// end of copy
+				
+				Type castType = exp.getType();
+				OperationCallExp asTypeOpCallExp = PivotFactory.eINSTANCE.createOperationCallExp();
+				asTypeOpCallExp.setOwnedSource(astPropCallExp);
+				asTypeOpCallExp.setReferredOperation(getOclAnyOclAsTypeOp());
+				asTypeOpCallExp.setType(castType);
+				asTypeOpCallExp.setIsSafe(astPropCallExp.isIsSafe());
+				
+				TypeExp argTypeExp = PivotFactory.eINSTANCE.createTypeExp();
+				argTypeExp.setReferredType(castType);
+				argTypeExp.setType(getOclMetaClass());
+				
+				asTypeOpCallExp.getOwnedArguments().add(argTypeExp);
+				
+				if (result.contains(exp)) { // if exp is the initial oclExp, the new asTypeOpCallExp will be the new result
+					result.remove(exp);
+					result.add(asTypeOpCallExp);
+				} else {
+					EcoreUtil.replace(exp,asTypeOpCallExp);
+				}
+				EcoreUtil.delete(exp);
+			}
+		});
+		return result.get(0);
+	}
+	
+	/**
+	 * Function which takes into account that the shadow is embedded inside of an IfExp
+	 * so that the guard pattern have the proper guards associated to the the IfExp
+	 */ 
+	 private void updateGuardPattern(ShadowExp shadowExp, GuardPattern guardPattern, Variable leftVar) {
+
+		EObject container = shadowExp.eContainer();		
+		
+		if (container instanceof IfExp) {
+			IfExp ifExp = (IfExp) container;
+			OCLExpression condition = ifExp.getOwnedCondition();
+			List<OCLExpression> guardPredicates = new ArrayList<OCLExpression>();
+			if (ifExp.getOwnedThen() == shadowExp) {
+				guardPredicates.add(EcoreUtil.copy(condition));
+			} else { // it's the else
+				guardPredicates.add(createNegatedExpression(condition));
+			}
+			
+			container = container.eContainer();
+			// FIXME create a cached operation to improve performance
+			while (container instanceof IfExp) {
+				ifExp = (IfExp) container;
+				guardPredicates.add(createNegatedExpression(ifExp.getOwnedCondition()));
+				container = container.eContainer();
+			}
+			
+			// We need to replace the OCL refered "self" varible by the QVTi domain "leftVar"
+			Collections.reverse(guardPredicates);
+			for (OCLExpression guardPredicate : guardPredicates ) {
+				org.eclipse.qvtd.pivot.qvtbase.Predicate predicate = QVTbaseFactory.eINSTANCE.createPredicate();
+				predicate.setConditionExpression(doReplacements(guardPredicate, leftVar));
+				guardPattern.getPredicate().add(predicate);
+			}
+		}
+	}
+	 
+	private OCLExpression createNegatedExpression(OCLExpression oclExp){
+		// We don't want to create a 'not not conditionExp'
+		if (isBooleanNotOpCallExp().test(oclExp)) {
+			OperationCallExp notOpCallExp = (OperationCallExp) oclExp;
+			return EcoreUtil.copy(notOpCallExp.getOwnedSource());
+		} else {
+			OperationCallExp notOpCallExp = PivotFactory.eINSTANCE.createOperationCallExp();
+			notOpCallExp.setName("not");
+			notOpCallExp.setReferredOperation(getBooleanNotOp());
+			notOpCallExp.setType(getBooleanPrimitiveType());
+			notOpCallExp.setOwnedSource(EcoreUtil.copy(oclExp));
+			return notOpCallExp;
+		}
+	}  
+	
+	private Predicate<EObject> isSelfVarExp() {
+		return element -> { 
+			return element instanceof VariableExp && 
+				"self".equals(((VariableExp)element).getReferredVariable().getName());
+		};
+	}
+	
+	private Predicate<EObject> isAstOpCallExp() {
+		return element -> { 
+			return element instanceof OperationCallExp &&
+				isAstOp().test(((OperationCallExp)element).getReferredOperation());
+		};
+	}
+	
+	private Predicate<EObject> isAstOp() {
+		return element -> {
+				return element instanceof Operation &&
+				"ast".equals(((Operation)element).getName());
+		};
+	}
+	
+	/*private List<OperationCallExp> getAstCalls(ShadowPart shadowPart) {
+		return getAllContentsIncludingSelf().apply(shadowPart.getOwnedInit())
+			.filter(isAstOpCallExp())
+			.map(OperationCallExp.class::cast)
+			.collect(Collectors.toList());
+	}*/
+	
+	private @NonNull Property getTraceabilityProperty(Type type) {
+		Class aClass = type.isClass();
+		assert(aClass != null);
+		Set<Class> allClasses = getSuperClasses().apply(aClass);
+		allClasses.add(aClass);
+		return allClasses.stream()
+			.flatMap(x -> x.getOwnedProperties().stream())
+			.filter(x -> traceabilityPropName.equals(x.getName()))
+			.findFirst().get();
+	}
+	
+	private @NonNull Operation getOclAnyEqualsOp() {
+		Class oclAny = envFact.getStandardLibrary().getOclAnyType();
+		return envFact.getMetamodelManager().getPrimaryClass(oclAny).getOwnedOperations().stream()
+			.filter(x -> "=".equals(x.getName()))
+			.findFirst().get();
+	}
+	
+	private @NonNull Class getOclMetaClass() { 
+		return envFact.getStandardLibrary().getClassType();
+	}
+	
+	private @NonNull Operation getOclAnyOclAsTypeOp() {
+		Class oclAny = envFact.getStandardLibrary().getOclAnyType();
+		return envFact.getMetamodelManager().getPrimaryClass(oclAny).getOwnedOperations().stream()
+			.filter(x -> "oclAsType".equals(x.getName()))
+			.findFirst().get();
+	}
+	
+	private @NonNull Class getBooleanPrimitiveType() {
+		return envFact.getStandardLibrary().getBooleanType();
+		
+	}
+	
+	private @NonNull Operation getBooleanNotOp() {
+		Class boolType = envFact.getStandardLibrary().getBooleanType();
+		return envFact.getMetamodelManager().getPrimaryClass(boolType).getOwnedOperations().stream()
+				.filter(x -> "not".equals(x.getName()))
+				.findFirst().get();
+	}
+	
+	private Predicate<OCLExpression> isBooleanNotOpCallExp() {
+		return exp -> {
+			return exp instanceof OperationCallExp &&
+					((OperationCallExp)exp).getReferredOperation() == getBooleanNotOp();
+		};
+	}
+	
+}
\ No newline at end of file