NMFCode / NMF

This repository contains the entire code for the .NET Modeling Framework
BSD 3-Clause "New" or "Revised" License
36 stars 15 forks source link

Ecore to NMeta transform creates incorrect names for serialization #28

Closed ChrisH07 closed 6 years ago

ChrisH07 commented 6 years ago

When converting an Ecore model to NMeta (Ecore2Code) the generated classes and properties have the wrong name in the XmlElementNameAttribute attribute. This occurs when the Ecore model is generated from an XML schema. The rules defined in XML Schema To Ecore Mapping specify the serialized name in an eAnnotation where the source is http:///org/eclipse/emf/ecore/util/ExtendedMetaData.

Here is a simplified Ecore model demonstrating the issue:

<?xml version="1.0" encoding="UTF-8"?>
<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="Simulink" nsURI="">
  <eAnnotations source="http:///org/eclipse/emf/ecore/util/ExtendedMetaData">
    <details key="qualified" value="false"/>
  </eAnnotations>
  <eClassifiers xsi:type="ecore:EClass" name="AnnotationDefaultsType">
    <eAnnotations source="http:///org/eclipse/emf/ecore/util/ExtendedMetaData">
      <details key="name" value="AnnotationDefaults_._type"/>
      <details key="kind" value="elementOnly"/>
    </eAnnotations>
    <eStructuralFeatures xsi:type="ecore:EReference" name="p" upperBound="-1" eType="#//PType"
        containment="true" resolveProxies="false">
      <eAnnotations source="http:///org/eclipse/emf/ecore/util/ExtendedMetaData">
        <details key="kind" value="element"/>
        <details key="name" value="P"/>
        <details key="namespace" value="##targetNamespace"/>
      </eAnnotations>
    </eStructuralFeatures>
  </eClassifiers>
  <eClassifiers xsi:type="ecore:EClass" name="PType">
    <eAnnotations source="http:///org/eclipse/emf/ecore/util/ExtendedMetaData">
      <details key="name" value="P_._type"/>
      <details key="kind" value="simple"/>
    </eAnnotations>
    <eStructuralFeatures xsi:type="ecore:EAttribute" name="value" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString">
      <eAnnotations source="http:///org/eclipse/emf/ecore/util/ExtendedMetaData">
        <details key="name" value=":0"/>
        <details key="kind" value="simple"/>
      </eAnnotations>
    </eStructuralFeatures>
  </eClassifiers>
</ecore:EPackage>

Currently in Attribute2Property, Class2Type, and Reference2Property the XmlElementNameAttribute is only added when the input and output names don't match. For the example above the reference to PType in AnnotationDefaultsType will be generated as

[XmlElementNameAttribute("p")]
public IOrderedSetExpression<IPType> P

instead of an uppercase P as specified in the "name" key in the annotation for the reference. Likewise the PType and AnnotationDefaultType classes should be have similar annotations based on their "name" keys (minus the _._type).

ChrisH07 commented 6 years ago

I'm trying to work on a solution to this problem, but I need assistance. It looks like the eAnnotations are lost by the time TransformationEngine.Transform gets called. Should we create a SerializedName property (where?) and assign a value in the appropriate transform (e.g. EReference2Property)? Something like this (not tested):

public class ENamedElement2MetaElement : AbstractTransformationRule<IENamedElement, IMetaElement>
{
    public override void Transform(IENamedElement input, IMetaElement output, ITransformationContext context)
    {
        if (!(input is IEDataType) || (input is IEEnum))
        {
            output.Name = input.Name.ToString();

            var extendedMetaData = input.EAnnotations.FirstOrDefault(o => o.Source.Equals("http:///org/eclipse/emf/ecore/util/ExtendedMetaData"));
            if (extendedMetaData != null)
            {
                var name = extendedMetaData.Details.FirstOrDefault(o => o.Key.Equals("name"));
                if (name != null)
                {
                    output.SerializedName = name.Value.Replace("_._type", "");
                }
            }
        }
    }
}

public class Class2Type : ClassGenerator<IClass>
{
    public override void Transform(IClass input, CodeTypeDeclaration generatedType, ITransformationContext context)
    {
        ....
        if (input.SerializedName != null)
        {
            generatedType.AddAttribute(typeof(XmlElementNameAttribute), input.SerializedName );
        }
        else if (input.Name != generatedType.Name)
        {
            generatedType.AddAttribute(typeof(XmlElementNameAttribute), input.Name);
        }
    }
}
georghinkel commented 6 years ago

Hi Chris,

actually, I am not so sure to add a new property SerializedName to MetaElement (sorry) because I think it is not something describing the type system. I would actually favor using a stereotype, in NMF called extension. After all, in most cases, the property would probably remain unset and it also somehow feels better to map an annotation extension to an extension rather than to a regular property.

Currently, extensions are hardly used, only that classes automatically get an extension called MappedType assigned when their metamodels are loaded. This is used for instance in the code generator 1. knows that these classes already exist and 2. in which namespace. I actually also wanted to get rid of the SystemType property of data types in order for NMeta to be entirely platform-independent.

Either way, whether this property is implemented directly or in an extension, the way to accomplish that is almost the same: Add it to the metamodel (manually to the NMeta-XMI, NMeta itself contains refinements that do not exist in Ecore which is why the NMeta.ecore is not the primary artifact and not well maintained), generate the code using the -x parameter to force generation of existing metamodels and base namespace NMF.Models, copy the generated code to the Meta folder. In this folder, the files for ModelElement and Model are ignored because these classes have a special bootstrapped implementation but all of the rest really is the generated code.

Best,

Georg

ChrisH07 commented 6 years ago

Hi Georg,

I thought there would be a better way to do this since I have limited knowledge of the architecture at this point. An extension would be a nicer way to accomplish this, but I'm not sure that you would want to keep modifying the NMeta metamodel each time a new extension feature is added. Looking at it now, the only reason I considered adding a property to MetaElement was due to the lack of an Annotation concept in NMeta. Would it make more sense to add an Annotation class to NMeta? This would allow an EAnnotation2Annotation transformation then you could test for the right annotation in Class2Type for example. This would limit the coding to the Meta transformation classes. I'm sure there are things I haven't thought about with this approach. Does this make sense?

Regards,

Chris

georghinkel commented 6 years ago

Hi Chris,

I actually do not like the annotations in Ecore very much, basically because you have to guess the URIs that you have to use as keys and you have to encode everything as strings. In my opinion, this is not the right usage of models, even though I acknowledge the flexibility but I just think that stereotypes are a better fit in that case, even though you have to add a new stereotype for each purpose. That stereotype can of course reside in any metamodel, it does not have to be the NMeta.nmeta. However, I added the serialization info stereotype there now, because I think it is useful in a pretty generic way.

I just pushed a series of commits where I included such a support for a serialization info extension. The feature is also tested, but the test simply checks whether the code generated for the metamodel you posted above in this thread compiles without errors.

Regards,

Georg

ChrisH07 commented 6 years ago

Hi Georg,

Thank you for your patience while I learn the code. I greatly appreciate the help. It hadn't occurred to me that you could put the extension in a separate model which does make this the most flexible solution. I too don't like the namespace resolution of EAnnotations and the key/value pairs feel unnatural anyway. I would much rather add a stereotype than write manual parsing!

I will look at adding additional tests for the transform and code generation. Thanks again for all your help.

Chris

ChrisH07 commented 6 years ago

This was fixed by a5b75bbd4fa9741637832bf4403aabc074fd2e0e and #29