/** * Copyright (c) 2012-2016 Marsha Chechik, Alessio Di Sandro, Michalis Famelis, * Rick Salay. * 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: * Alessio Di Sandro - Implementation. */ package edu.toronto.cs.se.modelepedia.operator.merge; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.emf.common.util.BasicEList; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EFactory; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.util.EObjectWithInverseResolvingEList; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.jdt.annotation.NonNull; import edu.toronto.cs.se.mmint.MMINT; import edu.toronto.cs.se.mmint.MMINTException; import edu.toronto.cs.se.mmint.java.reasoning.IJavaOperatorInputConstraint; import edu.toronto.cs.se.mmint.MIDTypeHierarchy; import edu.toronto.cs.se.mmint.mid.EMFInfo; import edu.toronto.cs.se.mmint.mid.GenericElement; import edu.toronto.cs.se.mmint.mid.MID; import edu.toronto.cs.se.mmint.mid.MIDLevel; import edu.toronto.cs.se.mmint.mid.Model; import edu.toronto.cs.se.mmint.mid.ModelElement; import edu.toronto.cs.se.mmint.mid.operator.impl.OperatorImpl; import edu.toronto.cs.se.mmint.mid.relationship.BinaryModelRel; import edu.toronto.cs.se.mmint.mid.relationship.MappingReference; import edu.toronto.cs.se.mmint.mid.relationship.ModelElementEndpointReference; import edu.toronto.cs.se.mmint.mid.relationship.ModelElementReference; import edu.toronto.cs.se.mmint.mid.relationship.ModelRel; import edu.toronto.cs.se.mmint.mid.utils.FileUtils; import edu.toronto.cs.se.mmint.mid.utils.MIDRegistry; //TODO MMINT[OPERATOR] Review this whole operator to find examples on how to make apis easier to use // e.g. direct access through ext table is useful, but there's that _AS_ thing to be fixed first // e.g. there is no direct link from a model to all its connected model rels public class ModelMerge extends OperatorImpl { public static class InputConstraint implements IJavaOperatorInputConstraint { @Override public boolean isAllowedInput(Map<String, Model> inputsByName) { ModelRel matchRel = (ModelRel) inputsByName.get(IN_MODELREL); if (matchRel.getModelEndpoints().size() != 2) { return false; } Model model1 = matchRel.getModelEndpoints().get(0).getTarget(); Model model2 = matchRel.getModelEndpoints().get(1).getTarget(); if (model1.getMetatype() != model2.getMetatype()) { return false; } return true; } } // input-output private final static @NonNull String IN_MODELREL = "match"; private final static @NonNull String OUT_MODEL = "merged"; private final static @NonNull String OUT_MODELREL1 = "trace1"; private final static @NonNull String OUT_MODELREL2 = "trace2"; // constants private static final @NonNull String MERGED_MODELOBJECT_ATTRIBUTE = "name"; private static final @NonNull String MERGED_SEPARATOR = "+"; // TODO MMINT[OPERATOR] Make this an api private @NonNull Set<ModelElementReference> getConnected(@NonNull ModelElementReference modelElemRef) { Set<ModelElementReference> connModelElemRefs = new HashSet<>(); for (ModelElementEndpointReference modelElemEndpointRef : modelElemRef.getModelElemEndpointRefs()) { for (ModelElementEndpointReference connModelElemEndpointRef : ((MappingReference) modelElemEndpointRef .eContainer()).getModelElemEndpointRefs()) { if (connModelElemEndpointRef == modelElemEndpointRef) { continue; } connModelElemRefs.add(connModelElemEndpointRef.getModelElemRef()); } } return connModelElemRefs; } private @NonNull EObject merge( @NonNull Model model1, @NonNull Model model2, @NonNull ModelRel matchRel, @NonNull Model mergedModel, @NonNull ModelRel traceRel1, @NonNull ModelRel traceRel2) throws MMINTException { // create merged root EObject rootModelObj1 = model1.getEMFInstanceRoot(); EFactory mergedModelFactory = rootModelObj1.eClass().getEPackage().getEFactoryInstance(); EObject rootMergedModelObj = mergedModelFactory.create(rootModelObj1.eClass()); Map<String, ModelElement> matchModelElems1 = new HashMap<>(); for (ModelElementReference modelElemRef1 : matchRel.getModelEndpointRefs().get(0).getModelElemRefs()) { ModelElementReference modelElemRef2 = getConnected(modelElemRef1).stream().findFirst().get(); matchModelElems1.put( modelElemRef1.getUri().substring(0, modelElemRef1.getUri().indexOf(MMINT.ROLE_SEPARATOR)), modelElemRef2.getObject()); } Map<String, EObject> mergedModelObjs = new HashMap<>(); Map<EObject, EObject> allModelObjs = new HashMap<>(); // copy elements from model1 for (EObject modelObj1 : rootModelObj1.eContents()) { EObject mergedModelObj = EcoreUtil.copy(modelObj1); allModelObjs.put(modelObj1, mergedModelObj); String modelElemUri1 = MIDRegistry.getModelAndModelElementUris(modelObj1, MIDLevel.INSTANCES)[1]; if (matchModelElems1.keySet().contains(modelElemUri1)) { ModelElement modelElem2 = matchModelElems1.get(modelElemUri1); EObject modelObj2 = modelElem2.getEMFInstanceObject(); mergedModelObjs.put( modelElem2.getUri().substring(0, modelElem2.getUri().indexOf(MMINT.ROLE_SEPARATOR)), mergedModelObj); try { // change merged attribute Object modelObjAttr1 = FileUtils.getModelObjectFeature(modelObj1, MERGED_MODELOBJECT_ATTRIBUTE); Object modelObjAttr2 = FileUtils.getModelObjectFeature(modelObj2, MERGED_MODELOBJECT_ATTRIBUTE); if (!modelObjAttr1.equals(modelObjAttr2)) { FileUtils.setModelObjectFeature( mergedModelObj, MERGED_MODELOBJECT_ATTRIBUTE, modelObjAttr1 + MERGED_SEPARATOR + modelObjAttr2); } } catch (MMINTException e) { // no attribute to be merged } } FileUtils.setModelObjectFeature( rootMergedModelObj, modelObj1.eContainingFeature().getName(), mergedModelObj); EList<ModelElementReference> traceModelElemRefs1 = new BasicEList<>(); traceModelElemRefs1.add(traceRel1.getModelEndpointRefs().get(0).createModelElementInstanceAndReference( modelObj1, null)); String newModelElemUri = mergedModel.getUri() + MIDRegistry.getModelAndModelElementUris(mergedModelObj, MIDLevel.INSTANCES)[1]; EMFInfo eInfo = MIDRegistry.getModelElementEMFInfo(mergedModelObj, MIDLevel.INSTANCES); String newModelElemName = MIDRegistry.getModelElementName(eInfo, mergedModelObj, MIDLevel.INSTANCES); traceModelElemRefs1.add( // merged model element is not serialized yet MIDTypeHierarchy.getRootModelElementType().createInstanceAndReference( newModelElemUri, newModelElemName, eInfo, traceRel1.getModelEndpointRefs().get(1))); MIDTypeHierarchy.getRootMappingType().createInstanceAndReferenceAndEndpointsAndReferences( true, traceModelElemRefs1); } // copy elements from model2 EObject rootModelObj2 = model2.getEMFInstanceRoot(); for (EObject modelObj2 : rootModelObj2.eContents()) { EObject mergedModelObj; String modelElemUri2 = MIDRegistry.getModelAndModelElementUris(modelObj2, MIDLevel.INSTANCES)[1]; if (mergedModelObjs.keySet().contains(modelElemUri2)) { // already copied mergedModelObj = mergedModelObjs.get(modelElemUri2); } else { mergedModelObj = EcoreUtil.copy(modelObj2); FileUtils.setModelObjectFeature( rootMergedModelObj, modelObj2.eContainingFeature().getName(), mergedModelObj); } allModelObjs.put(modelObj2, mergedModelObj); EList<ModelElementReference> traceModelElemRefs2 = new BasicEList<>(); traceModelElemRefs2.add(traceRel2.getModelEndpointRefs().get(0).createModelElementInstanceAndReference( modelObj2, null)); String newModelElemUri = mergedModel.getUri() + MIDRegistry.getModelAndModelElementUris(mergedModelObj, MIDLevel.INSTANCES)[1]; EMFInfo eInfo = MIDRegistry.getModelElementEMFInfo(mergedModelObj, MIDLevel.INSTANCES); String newModelElemName = MIDRegistry.getModelElementName(eInfo, mergedModelObj, MIDLevel.INSTANCES); traceModelElemRefs2.add( // merged model element is not serialized yet MIDTypeHierarchy.getRootModelElementType().createInstanceAndReference( newModelElemUri, newModelElemName, eInfo, traceRel2.getModelEndpointRefs().get(1))); MIDTypeHierarchy.getRootMappingType().createInstanceAndReferenceAndEndpointsAndReferences( true, traceModelElemRefs2); } // populate references for (Entry<EObject, EObject> entry : allModelObjs.entrySet()) { EObject modelObj = entry.getKey(); EObject mergedModelObj = entry.getValue(); for (EReference modelObjReference : modelObj.eClass().getEAllReferences()) { if (modelObjReference.isContainment()) { continue; } Object modelObjReferenceValue = FileUtils.getModelObjectFeature(modelObj, modelObjReference.getName()); if (modelObjReferenceValue == null || modelObjReferenceValue instanceof EObjectWithInverseResolvingEList<?>) { continue; } EList<EObject> modelObjValues; if (modelObjReferenceValue instanceof EList<?>) { modelObjValues = (EList<EObject>) modelObjReferenceValue; } else { modelObjValues = new BasicEList<>(); modelObjValues.add((EObject) modelObjReferenceValue); } for (EObject modelObjValue : modelObjValues) { FileUtils.setModelObjectFeature(mergedModelObj, modelObjReference.getName(), allModelObjs.get(modelObjValue)); } } } return rootMergedModelObj; } @Override public Map<String, Model> run( Map<String, Model> inputsByName, Map<String, GenericElement> genericsByName, Map<String, MID> outputMIDsByName) throws Exception { // TODO MMINT[MERGE] Support more complex cases than just first-level objects ModelRel matchRel = (ModelRel) inputsByName.get(IN_MODELREL); Model model1 = matchRel.getModelEndpoints().get(0).getTarget(); Model model2 = matchRel.getModelEndpoints().get(1).getTarget(); // create merged model and trace relationships as placeholders MID mergedModelMID = outputMIDsByName.get(OUT_MODEL); String mergedModelUri = FileUtils.replaceLastSegmentInUri( MIDRegistry.getModelAndModelElementUris(mergedModelMID, MIDLevel.INSTANCES)[0], model1.getName() + MERGED_SEPARATOR + model2.getName() + MMINT.MODEL_FILEEXTENSION_SEPARATOR + model1.getFileExtension()); Model mergedModel = model1.getMetatype().createInstance(mergedModelUri, mergedModelMID); BinaryModelRel traceRel1 = MIDTypeHierarchy.getRootModelRelType().createBinaryInstanceAndEndpoints( null, model1, mergedModel, outputMIDsByName.get(OUT_MODELREL1)); traceRel1.setName(OUT_MODELREL1); BinaryModelRel traceRel2 = MIDTypeHierarchy.getRootModelRelType().createBinaryInstanceAndEndpoints( null, model2, mergedModel, outputMIDsByName.get(OUT_MODELREL2)); traceRel2.setName(OUT_MODELREL2); // merge the models EObject rootMergedModelObj = merge(model1, model2, matchRel, mergedModel, traceRel1, traceRel2); FileUtils.writeModelFile(rootMergedModelObj, mergedModelUri, true); mergedModel.createInstanceEditor(); // opens the new model editor as side effect // output Map<String, Model> outputsByName = new HashMap<>(); outputsByName.put(OUT_MODEL, mergedModel); outputsByName.put(OUT_MODELREL1, traceRel1); outputsByName.put(OUT_MODELREL2, traceRel2); return outputsByName; } }