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}