001/* 002 * Copyright c 2018 Rusi Popov, MDA Tools.net All rights reserved. 003 * 004 * This program and the accompanying materials are made available under the terms of the 005 * Eclipse Public License v2.0 which accompanies this distribution, and is available at 006 * http://www.eclipse.org/legal/epl-v20.html 007 */ 008package net.mdatools.modelant.core.operation.model; 009 010import java.util.ArrayList; 011import java.util.Collection; 012import java.util.IdentityHashMap; 013import java.util.Map; 014import java.util.logging.Level; 015import java.util.logging.Logger; 016 017import javax.jmi.model.Association; 018import javax.jmi.model.Attribute; 019import javax.jmi.model.Classifier; 020import javax.jmi.model.Feature; 021import javax.jmi.model.ModelElement; 022import javax.jmi.reflect.RefAssociation; 023import javax.jmi.reflect.RefAssociationLink; 024import javax.jmi.reflect.RefClass; 025import javax.jmi.reflect.RefObject; 026import javax.jmi.reflect.RefPackage; 027 028import net.mdatools.modelant.core.api.Function; 029import net.mdatools.modelant.core.api.Procedure; 030import net.mdatools.modelant.core.api.model.NameMapping; 031import net.mdatools.modelant.core.api.name.AssociationName; 032import net.mdatools.modelant.core.api.name.ClassName; 033import net.mdatools.modelant.core.name.AssociationNameImpl; 034import net.mdatools.modelant.core.name.ClassNameImpl; 035import net.mdatools.modelant.core.name.FieldNameImpl; 036import net.mdatools.modelant.core.operation.element.PrintModelElement; 037import net.mdatools.modelant.core.util.Navigator; 038 039/** 040 * Transform one mode into another one using a correspondence (mapping) between their metamodels. 041 * Copy a model, represented in one metamodel, as the same model in another metamodel, according to 042 * explicitly provided rules for metamodel mapping rules. For example, if appropriate metamodel mapping rules 043 * are provided, it will copy a model from UML 1.3 to UML 1.4. 044 * Stateless. Thread-safe. 045 * As of JMI-1.0:<ul> 046 * <li> the derived associations are not copied 047 * <li> the non-changeable attributes are not copied 048 * </ul> 049 * @author Rusi Popov (popovr@mdatools.net) 050 */ 051public class CopyToMetaModel implements Function<RefPackage, Map<RefObject, RefObject>> { 052 /** 053 * This is a common logger 054 */ 055 private static final Logger LOGGER = Logger.getLogger( CopyToMetaModel.class.getPackage().getName() ); 056 057 private final NameMapping nameMapping; 058 059 private final RefPackage sourceExtent; 060 061 /** 062 * @param sourceExtent not null extent of the source/old model to compare 063 * @param mapping non-null strategy to map one metamodel to another 064 */ 065 public CopyToMetaModel(RefPackage sourceExtent, NameMapping mapping) { 066 this.nameMapping = mapping; 067 068 if ( sourceExtent == null ) { 069 throw new IllegalArgumentException("Expected a non-null source extent to transform"); 070 } 071 this.sourceExtent = sourceExtent; 072 } 073 074 /** 075 * Convert the model from fromExtent to a model in toExtent considering the metamodel mapping defined 076 * @param targetExtent not null extent of the target/new model to compare 077 * @return the non-null correspondence between the objects from the source to the target model 078 */ 079 public Map<RefObject, RefObject> execute(RefPackage targetExtent) { 080 Map<RefObject, RefObject> result; 081 082 // validate the parameters 083 if ( targetExtent == null ) { 084 throw new IllegalArgumentException("Expected a non-null target extent to copy to"); 085 } 086 087 result = new IdentityHashMap<RefObject, RefObject>(); 088 089 for (RefClass refClass : Navigator.getAllClasses(sourceExtent)) { 090 copyObjects(targetExtent, refClass, result ); 091 } 092 093 for (RefClass refClass : Navigator.getAllClasses(sourceExtent)) { 094 copyAttributes(targetExtent, refClass, result ); 095 } 096 097 for (RefAssociation refAssociation : Navigator.getAllAssociations(sourceExtent)) { 098 copyLinks(targetExtent, refAssociation, result ); 099 } 100 return result; 101 } 102 103 /** 104 * Copy the instances of refClass to the targetExtent with the stated metamodel transformation, 105 * collect the correspondence of original to copy objects in objectsMap 106 * 107 * @param targetExtent not null extent where to copy the instances of refClass 108 * @param originalMetaClass not null source model class 109 * @param objectsMap not null correspondence between an original and copy objects 110 */ 111 private void copyObjects(RefPackage targetExtent, RefClass originalMetaClass, Map<RefObject, RefObject> objectsMap) { 112 Procedure<RefObject> construct; 113 ClassName className; 114 115 className = new ClassNameImpl((Classifier) originalMetaClass.refMetaObject()); 116 construct = nameMapping.mapMetaClass( className, sourceExtent, targetExtent, objectsMap ); 117 118 for (RefObject source : (Collection<RefObject>) originalMetaClass.refAllOfClass()) { 119 try { 120 construct.execute(source); // objectsMap is updated to bind the source to the produced object(s) if any 121 122 } catch (Exception ex) { 123 LOGGER.log(Level.SEVERE, "Copy object "+new PrintModelElement().execute( source )+" caused: ",ex); 124 } 125 } 126 } 127 128 /** 129 * Copy the values of the attributes of the instances of refClass as corresponding values of corresponding attributes in 130 * the corresponding instances 131 * 132 * @param targetExtent not null extent where to copy the instances of refClass 133 * @param originalMetaClass not null source model class 134 * @param objectsMap not null correspondence between an original and copy objects 135 */ 136 private void copyAttributes(RefPackage targetExtent, RefClass originalMetaClass, Map<RefObject, RefObject> objectsMap) { 137 Collection<Procedure<RefObject>> copyAttributesOperations; 138 139 copyAttributesOperations = collectCopyAttributeOperations(originalMetaClass, targetExtent, objectsMap); 140 141 // TODO: Let A extends B, check if A.allOfClass() subclass of B.allOfClass() 142 // TODO: If so, then DO NOT collect superclasses of A to collect their attributes, as this would 143 // TODO: repeat copying the parent attributes for all its subclasses, which is slow 144 145 for (RefObject source : (Collection<RefObject>) originalMetaClass.refAllOfClass()) { 146 for (Procedure<RefObject> copy : copyAttributesOperations) { 147 try { 148 copy.execute(source); 149 } catch (Exception ex) { 150 LOGGER.log(Level.SEVERE, copy+" on "+new PrintModelElement().execute( source )+" caused: ", ex); 151 } 152 } 153 } 154 } 155 156 157 /** 158 * Collect only Attribute features from the instances of the source class (as sourceMetaObject) to copy to the 159 * instances of the target class. 160 * Skip the Reference Features, as they are copied through the associations. 161 * 162 * The produced operations lookup the model element that corresponds to the source object to copy the argument 163 * from and update the correspondent. In case there is no correspondent, they do nothing. 164 * This unifies the interface of the produced operations. 165 * 166 * @param originalModelClass not null source metamodel class 167 * @param targetExtent not null 168 * @param objectsMap not null mapping of the source model elements to the target model elements 169 * @return non-null collection of operations to copy corresponding attribute values from the source to the target (copy) object 170 */ 171 private Collection<Procedure<RefObject>> collectCopyAttributeOperations(RefClass originalModelClass, 172 RefPackage targetExtent, 173 Map<RefObject, RefObject> objectsMap) { 174 Collection<Procedure<RefObject>> result; 175 FieldNameImpl sourceFieldName; 176 Classifier sourceMetaObject; 177 Collection<Classifier> superMetaObjects; 178 179 result = new ArrayList<>(); 180 181 sourceMetaObject = (Classifier) originalModelClass.refMetaObject(); 182 183 superMetaObjects = Navigator.getAllSuperMetaObejcts( sourceMetaObject ); 184 185 for (Classifier superclass : superMetaObjects) { 186 for (ModelElement contents : (Collection<ModelElement>) superclass.getContents()) { 187 188 if ( contents instanceof Attribute ) { 189 sourceFieldName = new FieldNameImpl((Attribute) contents); 190 191 result.add(nameMapping.mapMetaFieldName(sourceFieldName, sourceExtent, targetExtent, objectsMap)); 192 } 193 } 194 } 195 return result; 196 } 197 198 /** 199 * Copy the links from the sourceAssociation as links into the correspondent association 200 * @param targetExtent 201 * @param sourceAssociation 202 * @param objectsMap 203 */ 204 private void copyLinks(RefPackage targetExtent, RefAssociation sourceAssociation, Map<RefObject, RefObject> objectsMap) { 205 Procedure<RefAssociationLink> copyLinkOperation; 206 AssociationName className; 207 208 className = new AssociationNameImpl((Association) sourceAssociation.refMetaObject()); 209 210 copyLinkOperation = nameMapping.mapMetaAssociation(className, sourceExtent, targetExtent, objectsMap); 211 212 for (RefAssociationLink source : (Collection<RefAssociationLink>) sourceAssociation.refAllLinks()) { 213 try { 214 copyLinkOperation.execute(source); 215 } catch (Exception ex) { 216 LOGGER.log(Level.SEVERE, "Copying link "+new PrintModelElement().execute( source )+" caused: ", ex); 217 } 218 } 219 } 220}