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.transform;
009
010import static net.mdatools.modelant.core.name.NameImpl.NO_MAP_NAME;
011
012import java.util.HashMap;
013import java.util.Map;
014import java.util.logging.Level;
015import java.util.logging.Logger;
016
017import javax.jmi.reflect.RefAssociationLink;
018import javax.jmi.reflect.RefObject;
019import javax.jmi.reflect.RefPackage;
020import javax.jmi.reflect.RefStruct;
021
022import net.mdatools.modelant.core.api.Operation;
023import net.mdatools.modelant.core.api.Procedure;
024import net.mdatools.modelant.core.api.model.ConstructOperation;
025import net.mdatools.modelant.core.api.model.ConstructProcedure;
026import net.mdatools.modelant.core.api.model.NameMapping;
027import net.mdatools.modelant.core.api.name.AssociationName;
028import net.mdatools.modelant.core.api.name.ClassName;
029import net.mdatools.modelant.core.api.name.EnumValueName;
030import net.mdatools.modelant.core.api.name.FieldName;
031import net.mdatools.modelant.core.api.name.Name;
032import net.mdatools.modelant.core.api.name.StructName;
033
034/**
035 * Define correspondence between the source and target models either as:<ul>
036 * <li> direct name-to-name mapping
037 * <li> direct parent package name-to-name mapping and deriving the specific name-to-name mapping for nested elements
038 * <li> explicit name-to-transformation mapping
039 * </ul>
040 * @author Rusi Popov (popovr@mdatools.net)
041 */
042public class RenamingMapping implements NameMapping {
043
044  private static final Logger LOGGER = Logger.getLogger( RenamingMapping.class.getName() );
045
046  /**
047   * Maps source to target names for transformation
048   */
049  private final Map<Name<?>, Name<?>> renaming = new HashMap<>();
050
051  /**
052   * Maps source model name to a constructor of procedures to update the target model elements
053   */
054  private final Map<Name<?>, ConstructProcedure<?>> nameToMethodMap = new HashMap<>();
055
056  /**
057   * Maps source model name to a constructor operations to produce the target model elements
058   * Used only for operations on RefStruct
059   */
060  private final Map<StructName, ConstructOperation<RefStruct>> nameToOperationMap = new HashMap<>();
061
062  /*
063   * INVARIANT:
064   * 1. renaming.keySet() intersect nameToMethodMap.keySet() = EMPTY
065   * 2. nameToMethodMap maps different types considering the key:
066   *   key instanceof ClassName - mapped is produce Procedure<RefObject>
067   *   key instanceof AssociationName - mapped is produce Procedure<RefAssociationLink>
068   *   key instanceof FieldName - mapped is produce Procedure<RefObejct>
069   */
070
071  /**
072   * Implement in the subclass to initialize itself by calling the map() methods
073   */
074  protected RenamingMapping() {
075  }
076
077  /**
078   * Register a name-to-name mapping
079   * @param key not null qualified name, not a struct name
080   * @param name not null qualified name
081   */
082  protected final <T extends Name<?>> void set(T key, T name) {
083    assert key != null : "Expected a non-null key provided";
084    assert !(key instanceof StructName) : "Expected "+key+" is not a struct name";
085
086    assert name != null : "Expected a non-null name provided";
087
088    renaming.put(key, name);
089    nameToMethodMap.remove(key);
090  }
091
092  /**
093   * Mark a class,association, field, enum or struct as not mapped
094   * @param key not null
095   */
096  protected final <T extends Name<?>> void unset(T key) {
097    set(key, NO_MAP_NAME);
098  }
099
100  /**
101   * Register a field name transfer method
102   *
103   * As of the requirements of {@link NameMapping#mapMetaFieldName(FieldName, RefPackage, RefPackage, Map)}, the
104   * procedure should NOT write into target attributes, that are NOT CHANGEABLE.
105   *
106   * @param key not null
107   * @param translate not null
108   */
109  protected final void set(FieldName key,
110                           ConstructProcedure<RefObject> translate) {
111
112    assert key != null : "Expected a non-null key provided";
113    assert translate != null : "Expected a non-null translate operation provided";
114
115    nameToMethodMap.put( key, translate );
116  }
117
118  /**
119   * Map an association to a transfer procedure.
120   *
121   * As of the requirements of {@link NameMapping#mapMetaAssociation(AssociationName, RefPackage, RefPackage, Map)}, the
122   * procedure should NOT copy links into target associations, that are DERIVED.
123   *
124   * @param key
125   * @param translate
126   */
127  protected final void set(AssociationName key,
128                           ConstructProcedure<RefAssociationLink> translate) {
129    assert key != null : "Expected a non-null key provided";
130    assert translate != null : "Expected a non-null translate operation provided";
131
132    nameToMethodMap.put( key, translate );
133  }
134
135  /**
136   * Mark an association as mapped to the target one in the same direction of the links
137   * @param key not null qualified name of the original association
138   * @param target not null qualified name
139   */
140  protected final void setForward(AssociationName key, AssociationName target) {
141    assert key != null : "Expected a non-null key provided";
142    assert target != null : "Expected a non-null target provided";
143
144    set( key,
145         target.newForwardLinkProduction());
146  }
147
148  /**
149   * Mark an association as mapped to the target one in the opposite direction of the links
150   * @param key not null qualified name of the original association
151   * @param target not null qualified name
152   */
153  protected final void setBackward(AssociationName key, AssociationName target) {
154    assert key != null : "Expected a non-null key provided";
155    assert target != null : "Expected a non-null target provided";
156
157    set( key,
158         target.newBackwardLinkProduction());
159  }
160
161  /**
162   * Map a class to a conversion procedure
163   * @param key
164   * @param translate
165   */
166  protected final void set(ClassName key,
167                           ConstructProcedure<RefObject> translate) {
168    assert key != null : "Expected a non-null key provided";
169    assert translate != null : "Expected a non-null translate operation provided";
170
171    nameToMethodMap.put( key, translate );
172  }
173
174  /**
175   * Map a struct type to a conversion operation
176   * @param key
177   * @param translate
178   */
179  protected final void set(StructName key,
180                           ConstructOperation<RefStruct> translate) {
181    assert key != null : "Expected a non-null key provided";
182    assert translate != null : "Expected a non-null translate operation provided";
183
184    nameToOperationMap.put( key, translate );
185  }
186
187  /**
188   * @see NameMapping#mapMetaClass(ClassName, RefPackage, RefPackage, Map)
189   */
190  public final Procedure<RefObject> mapMetaClass(ClassName className,
191                                                 RefPackage sourceExtent,
192                                                 RefPackage targetExtent,
193                                                 Map<RefObject, RefObject> objectsMap) {
194    ConstructProcedure<RefObject> mapped;
195
196    mapped  = (ConstructProcedure<RefObject>) lookupName(className);
197
198    return mapped.construct( sourceExtent, targetExtent, objectsMap, this );
199  }
200
201  /**
202   * @see NameMapping#mapMetaAssociation(AssociationName, RefPackage, RefPackage, Map)
203   */
204  public final Procedure<RefAssociationLink> mapMetaAssociation(AssociationName associationName,
205                                                                RefPackage sourceExtent,
206                                                                RefPackage targetExtent,
207                                                                Map<RefObject, RefObject> objectsMap) {
208    ConstructProcedure<RefAssociationLink> mapped;
209
210    mapped  = (ConstructProcedure<RefAssociationLink>) lookupName(associationName);
211
212    return mapped.construct( sourceExtent, targetExtent, objectsMap, this );
213  }
214
215  /**
216   * @see NameMapping#mapMetaFieldName(FieldName, RefPackage, RefPackage, Map)
217   */
218  public final Procedure<RefObject> mapMetaFieldName(FieldName fieldName,
219                                                     RefPackage sourceExtent,
220                                                     RefPackage targetExtent, Map<RefObject, RefObject> objectsMap) {
221    ConstructProcedure<RefObject> mapped;
222
223    mapped  = (ConstructProcedure<RefObject>) lookupName(fieldName);
224
225    return mapped.construct(sourceExtent, targetExtent, objectsMap, this);
226  }
227
228
229
230  /**
231   * @param source not null source metamodel name
232   * @return possibly null target metamodel name mapped to the source name.
233   *         NOTE: It might happen that not all names, that are mapped to procedures through map*(name)
234   *               are mapped to names. Use this method for testing purposes only
235   */
236  public final <T extends Name<?>> Name<T> getName(Name<T> source) {
237    return constructMappedName( source );
238  }
239
240  /**
241   * @param name not null
242   * @return the non-null method to produce the transformation for the name into the corresponding object into the target extent
243   */
244  private ConstructProcedure<?> lookupName(Name<?> name) {
245    ConstructProcedure<?> result;
246    Name<?> mappedName;
247
248    result = nameToMethodMap.get(name);
249
250    if ( result==null ) { // no explicit method mapped
251      mappedName = constructMappedName(name);
252
253      if ( mappedName != null ) {
254        result = mappedName.constructTransfromation();
255      } else {
256        result = name.constructNoTransfromation();
257      }
258    } else { // log the explicit procedure mapping to complete the name mapping log in constructMappedName
259
260      LOGGER.log( Level.FINE, "{0} mapped to {1}", new Object[] {name, result});
261    }
262    return result;
263  }
264
265  /**
266   * @param name not null
267   * @return the non-null method to produce the transformation for the name into the corresponding object into the target extent
268   */
269  private ConstructOperation<RefStruct> lookupNameOperation(StructName name) {
270    ConstructOperation<RefStruct> result;
271    StructName mappedName;
272
273    result = nameToOperationMap.get(name);
274
275    if ( result==null ) { // no explicit method mapped
276      mappedName = constructMappedName(name);
277
278      if ( mappedName != null ) {
279        result = mappedName.constructCopyOperation();
280      } else {
281        result = new ConstructOperation<RefStruct>() {
282          public Operation<RefStruct> construct(RefPackage targetExtent, Map<RefObject, RefObject> objectsMap,
283                                                NameMapping valueMapping) {
284            return new Operation<RefStruct>() {
285              public RefStruct execute(RefStruct argument) throws RuntimeException, IllegalArgumentException {
286                return null;
287              }
288            };
289          }
290        };
291      }
292    } else { // log the explicit procedure mapping to complete the name mapping log in constructMappedName
293
294      LOGGER.log( Level.FINE, "{0} mapped to {1}", new Object[] {name, result});
295    }
296    return result;
297  }
298
299  /**
300   * Use the defaults and existing mappings to construct the name that corresponds to the provided one in the target mode
301   * @param name not null
302   * @return null when no mapping defined
303   */
304  private <P extends Name<?>, T extends Name<P>> T constructMappedName(T name) {
305    T result;
306    Name<?> constructedParent;
307
308    result = (T) renaming.get(name);
309    if ( result == NO_MAP_NAME ) { // stop any mapping
310      result = null;
311
312    } else if ( result == null
313                && name.getOwner() != null ) {
314      constructedParent = constructMappedName((Name<?>) name.getOwner());
315
316      if ( constructedParent != null ) {
317        result = (T) name.constructName((P) constructedParent, name.getName());
318      }
319    }
320
321    LOGGER.log( Level.FINE, "{0} mapped to {1}", new Object[] {name, result});
322
323    return result;
324  }
325
326  /**
327   * @see net.mdatools.modelant.core.api.model.NameMapping#mapEnum(net.mdatools.modelant.core.api.name.EnumValueName)
328   */
329  public final EnumValueName mapEnum(EnumValueName value) {
330    return constructMappedName(value);
331  }
332
333  /**
334   * @see net.mdatools.modelant.core.api.model.NameMapping#mapEnum(net.mdatools.modelant.core.api.name.EnumValueName)
335   */
336  public final Operation<RefStruct> mapStruct(StructName structName, RefPackage targetExtent, Map<RefObject, RefObject> objectsMap) {
337    return lookupNameOperation( structName ).construct( targetExtent, objectsMap, this );
338  }
339}