blob: 97f3e8078f76107bde81e8c02c20d3a9f10ddd77 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Willink Transformations 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:
* E.D.Willink - initial API and implementation
*******************************************************************************/
package org.eclipse.qvtd.cs2as.compiler.internal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.emf.common.util.EList;
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.util.EcoreUtil;
import org.eclipse.ocl.pivot.Class;
import org.eclipse.ocl.pivot.CompleteClass;
import org.eclipse.ocl.pivot.CompletePackage;
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.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.ClassUtil;
import org.eclipse.ocl.pivot.utilities.EnvironmentFactory;
import org.eclipse.ocl.pivot.utilities.FeatureFilter;
import org.eclipse.qvtd.pivot.qvtbase.Domain;
import org.eclipse.qvtd.pivot.qvtbase.Predicate;
import org.eclipse.qvtd.pivot.qvtbase.QVTbaseFactory;
import org.eclipse.qvtd.pivot.qvtbase.Rule;
import org.eclipse.qvtd.pivot.qvtbase.Transformation;
import org.eclipse.qvtd.pivot.qvtbase.TypedModel;
import org.eclipse.qvtd.pivot.qvtcore.CoreDomain;
import org.eclipse.qvtd.pivot.qvtcore.CoreModel;
import org.eclipse.qvtd.pivot.qvtcore.Mapping;
public class QVTpModelsMerger {
/**
* <p>
* Utility to merge the QVTp models result of the OCL2QVTp transformation on different CS2AS descriptions.
* </p>
*
* <ul>
* The merge utility will simply consist of:
* <li> Merging all the qvtp {@link Mapping mappings} in one qvtp {@link Transformation transformation} </li>
* <li> Merging all the {@link Import imports} in the containing qvtp {@link CoreModel model}, taking care of import duplications </li>
* </ul>
*
* @param extendedQVTpModels a list of the QVTp models to merge
* @return a clone of the first QVTp model of the provided list, having merged the remaining QVTp models of that list
*/
public static void merge(EnvironmentFactory envF, Resource targetQVTpModel, List<Resource> extendedQVTpModels) {
CoreModel qvtpModel = getCoreModel(targetQVTpModel);
Map<Class, List<Mapping>> inputType2RefiningMapping = getRefiningMappingInputTypes(qvtpModel);
for (Resource extendedQVTpModel : extendedQVTpModels) {
doMerge(envF, qvtpModel, getCoreModel(extendedQVTpModel), inputType2RefiningMapping);
}
}
private static Map<Class, List<Mapping>> getRefiningMappingInputTypes(CoreModel qvtpModel) {
Map<Class, List<Mapping>> result = new HashMap<Class, List<Mapping>>();
Package _package = qvtpModel.getOwnedPackages().get(0);
Transformation tx = (Transformation) _package.getOwnedClasses().get(0);
for (Rule rule : tx.getRule()) {
Mapping mapping = (Mapping) rule;
Variable inputVar = getInputVariable(mapping);
Class refiningType = (Class)inputVar.getType();
List<Mapping> refiningMappings = result.get(refiningType);
if (refiningMappings == null) {
refiningMappings = new ArrayList<Mapping>();
result.put(refiningType, refiningMappings);
}
refiningMappings.add(mapping);
}
return result;
}
/**
* @param resultQVTpModel
* @param mergedQVTpModel
* @param refMapInputTypes a list of the types involved in the mappings of the extending QVTp model
*/
private static void doMerge(EnvironmentFactory envF, CoreModel resultQVTpModel, CoreModel mergedQVTpModel, Map<Class, List<Mapping>> inputType2RefiningMapping) {
// Imports
Set<Namespace> alreadyImportedNamespaces = new HashSet<Namespace>();
Set<Import> importsToRemove = new HashSet<Import>();
for (Import _import : resultQVTpModel.getOwnedImports()) {
Namespace importedNS = _import.getImportedNamespace();
alreadyImportedNamespaces.add(importedNS);
if (doesNamespaceCorrespondToMergedQVTpModel(mergedQVTpModel, importedNS)) {
importsToRemove.add(_import);
}
}
// Add a copy of the imported NS
for (Import _import : mergedQVTpModel.getOwnedImports()) {
Namespace importedNS = _import.getImportedNamespace();
if (!alreadyImportedNamespaces.contains(importedNS)) {
resultQVTpModel.getOwnedImports().add(EcoreUtil.copy(_import));
}
}
for (Import _import : importsToRemove) {
resultQVTpModel.getOwnedImports().remove(_import);
}
Transformation resultTransformation = getTransformation(resultQVTpModel);
Transformation mergedTransformation = getTransformation(mergedQVTpModel);
// TypedModels
Map<String, List<Package>> tmName2oldUsedPackages = new HashMap<String, List<Package>>();
for (TypedModel typedModel : mergedTransformation.getModelParameter()) {
tmName2oldUsedPackages.put(typedModel.getName(),typedModel.getUsedPackage());
}
Map<String, TypedModel> tmName2newTypedModel = new HashMap<String, TypedModel>();
for (TypedModel typedModel : resultTransformation.getModelParameter()) {
typedModel.getUsedPackage().addAll(tmName2oldUsedPackages.get(typedModel.getName()));
tmName2newTypedModel.put(typedModel.getName(), typedModel);
}
// Mapping rules
EList<Rule> resultRules = resultTransformation.getRule();
for (Rule rule : mergedTransformation.getRule()) {
Mapping baseRule = (Mapping) EcoreUtil.copy(rule);
refactorMapping(envF, baseRule, inputType2RefiningMapping, tmName2newTypedModel);
resultRules.add(baseRule);
}
}
private static boolean doesNamespaceCorrespondToMergedQVTpModel(CoreModel mergedQVTpModel, Namespace ns) {
if (ns instanceof Model && isAnOCLModel(ns)) {
URI qvtpModelURI = URI.createURI(mergedQVTpModel.getExternalURI());
assert assertCorrectQVTpModelFileExtenion(qvtpModelURI);
URI importedNSURI = URI.createURI(((Model) ns).getExternalURI());
String fileName1 = qvtpModelURI.trimFragment().trimFileExtension().trimFileExtension().lastSegment(); // xxx.qvtp.qvias
String fileName2 = importedNSURI.trimFragment().trimFileExtension().trimFileExtension(). lastSegment(); // xxx.ocl or xxx.ocl.oclas
return ClassUtil.nonNullState(fileName1).equals(fileName2);
}
return false;
}
private static boolean isAnOCLModel(Namespace ns) {
URI nsURI = URI.createURI(((Model) ns).getExternalURI());
if ("ocl".equals(nsURI.fileExtension())) {
return true;
}
if (! "oclas".equals(nsURI.fileExtension())) {
return false;
}
if (! "ocl".equals(nsURI.trimFileExtension().fileExtension())) {
return false;
}
return true;
}
private static Transformation getTransformation(CoreModel iModel) {
for (org.eclipse.ocl.pivot.Package aPackage : iModel.getOwnedPackages()) {
for (org.eclipse.ocl.pivot.Class aClass : aPackage.getOwnedClasses()) {
if (aClass instanceof Transformation) {
return (Transformation) aClass;
}
}
}
throw new IllegalStateException(MessageFormat.format("The QVTd model '{0}' does not have a Transformation element.", iModel.getExternalURI()));
}
private static CoreModel getCoreModel(Resource qvtpResource) {
for (EObject eContent : qvtpResource.getContents()) {
if (eContent instanceof CoreModel) {
return (CoreModel) eContent;
}
}
throw new IllegalStateException(MessageFormat.format("The QVTd model '{0}' does not have an CoreModel element.", qvtpResource.getURI()));
}
private static boolean assertCorrectQVTpModelFileExtenion(URI qvtpModelURI ) {
assert qvtpModelURI.fileExtension().equals("qvtcas");
assert qvtpModelURI.trimFileExtension().fileExtension().equals("qvtp");
return true;
}
private static void refactorMapping (EnvironmentFactory envF, Mapping mappingToRefactor, Map<Class, List<Mapping>> inputType2extendingMapping,
Map<String, TypedModel> tmName2newTypedModel) {
// TypedModel refactor
for (Domain domain : mappingToRefactor.getDomain()) {
domain.setTypedModel(tmName2newTypedModel.get(domain.getTypedModel().getName()));
}
Variable inputVar = getInputVariable(mappingToRefactor);
Type refinedType = inputVar.getType();
for (Entry<Class, List<Mapping>> mEntry : inputType2extendingMapping.entrySet()) {
Class refiningType = mEntry.getKey();
for (Mapping refiningMapping : mEntry.getValue()){
List<Class> superClasses = new ArrayList<Class>();
computeAllSuperClasses(refiningType, superClasses);
if (superClasses.contains(refinedType)) {
// We check the mapping name, to ensure it's the correct refining mapping
String refinedMappingName = mappingToRefactor.getName();
String refiningMappingName = refiningMapping.getName();
if (refinedMappingName.startsWith("c")){ // if it's a creation mapping
if (refiningMappingName.startsWith("c")) {
doMappingRefactoring(envF, mappingToRefactor, refiningMapping, inputVar, refiningType);
}
} else if (refinedMappingName.startsWith("u")) {
if (refiningMappingName.startsWith("u")) {
String refinedUMappingFeature = refinedMappingName.split("u.*_")[1];
String refiningUMappingFeature = refiningMappingName.split("u.*_")[1];
if (refinedUMappingFeature.equals(refiningUMappingFeature)) {
doMappingRefactoring(envF, mappingToRefactor, refiningMapping, inputVar, refiningType);
}
}
} else {
//This point should never be reached given the mapping naming convention introduced in OCL2QVTp.
throw new IllegalStateException("This point should never be reached given the mapping naming convention introduced in OCL2QVTp.");
}
}
}
}
}
private static void doMappingRefactoring(EnvironmentFactory envF, Mapping mappingToRefactor, Mapping refiningMapping, Variable inputVar, Class refiningType) {
// We add the guard
Predicate predicate = QVTbaseFactory.eINSTANCE.createPredicate();
predicate.setConditionExpression(createOclIsKindOfOperationCall(envF, inputVar, refiningType));
mappingToRefactor.getGuardPattern().getPredicate().add(predicate);
// And set the refinement
refiningMapping.setOverrides(mappingToRefactor);
}
private static Variable getInputVariable(Mapping mapping) {
// In OCL2QVTp the first one is always the input domain with a unique input variable
CoreDomain inputDomain = (CoreDomain) mapping.getDomain().get(0);
return inputDomain.getGuardPattern().getVariable().get(0);
}
private static void computeAllSuperClasses(Class aClass, List<Class> allSuperClasses) {
for (Class superClass : aClass.getSuperClasses()) {
if (!allSuperClasses.contains(superClass)) {
allSuperClasses.add(superClass);
computeAllSuperClasses(superClass, allSuperClasses);
}
}
}
private static OCLExpression createOclIsKindOfOperationCall(EnvironmentFactory envF, Variable inputVar, Class refiningType) {
// oclIsKindOf OperationCallExp
OperationCallExp opCallExp = PivotFactory.eINSTANCE.createOperationCallExp();
CompletePackage cPackage = envF.getCompleteModel().getCompletePackage(envF.getStandardLibrary().getPackage());
CompleteClass cClass = cPackage.getCompleteClass(envF.getStandardLibrary().getOclAnyType());
Operation oclIsKindOfOp = cClass.getOperations(FeatureFilter.SELECT_NON_STATIC, "oclIsKindOf").iterator().next();
opCallExp.setReferredOperation(oclIsKindOfOp);
opCallExp.setName(oclIsKindOfOp.getName());
opCallExp.setType(oclIsKindOfOp.getType());
// We set the source
VariableExp source = PivotFactory.eINSTANCE.createVariableExp();
source.setReferredVariable(inputVar);
source.setName(inputVar.getName());
source.setType(inputVar.getType());
opCallExp.setOwnedSource(source);
// We set the argument
TypeExp arg = PivotFactory.eINSTANCE.createTypeExp();
arg.setType(envF.getStandardLibrary().getClassType());
arg.setReferredType(refiningType);
opCallExp.getOwnedArguments().add(arg);
return createNotOperation(envF, opCallExp);
}
private static OperationCallExp createNotOperation(EnvironmentFactory envF, OCLExpression expToNegate) {
// not OperationCallExp
OperationCallExp opCallExp = PivotFactory.eINSTANCE.createOperationCallExp();
CompletePackage cPackage = envF.getCompleteModel().getPrimitiveCompletePackage();
CompleteClass cClass = cPackage.getCompleteClass(envF.getStandardLibrary().getBooleanType());
Operation oclIsKindOfOp = cClass.getOperations(FeatureFilter.SELECT_NON_STATIC, "not").iterator().next();
opCallExp.setReferredOperation(oclIsKindOfOp);
opCallExp.setName(oclIsKindOfOp.getName());
opCallExp.setType(oclIsKindOfOp.getType());
// we set the source
opCallExp.setOwnedSource(expToNegate);
return opCallExp;
}
}