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

Wrong deserialization of multivalued reference properties #69

Closed tankwalker closed 9 months ago

tankwalker commented 10 months ago

Synopsis

Since the version 2.0.190 of the Ecore2Code tool, the deserialization process fails in populating corss-reference properties passing the --collectionsAreElements properties.

Description

To reproduce the problem is sufficient to generate the model code from a generic metamodel having a multivalued reference property by passing the --collectionsAreElements option to the Ecore2Code generation tool. The collection-valued property (i.e., refB in the example below) is not properly deserialized when reading the input instance model which appears to be empty at runtime, even though in the input model instance it's indeed fulfilled with data. Viceversa, by not passing the --collectionsAreElements option to the Ecore2Code generation tool, the component correctly deserialize the multivalued property as expected.

NOTE: The problem does not occur in case of a single-valued property (containment or not) as for the refC in the ClassD class (cfr. the example below).

Example. The Ecore metamodel has a couple of classes that reference to each other. ClassA has a single containment reference to ClassB which, in turn, has a back reference to multiple objects of class ClassA. Further, ClassC have a single containment and opposite reference to ClassD.

Ecore model:

<?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="mwe6" nsURI="http://mwe6" nsPrefix="mwe6">
  <eClassifiers xsi:type="ecore:EClass" name="Root">
    <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
    <eStructuralFeatures xsi:type="ecore:EReference" name="classA" upperBound="-1"
        eType="#//ClassA" containment="true"/>
    <eStructuralFeatures xsi:type="ecore:EReference" name="classC" upperBound="-1"
        eType="#//ClassC" containment="true"/>
  </eClassifiers>
  <eClassifiers xsi:type="ecore:EClass" name="ClassA">
    <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
    <eStructuralFeatures xsi:type="ecore:EReference" name="refB" eType="#//ClassB"
        containment="true"/>
  </eClassifiers>
  <eClassifiers xsi:type="ecore:EClass" name="ClassB">
    <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
    <eStructuralFeatures xsi:type="ecore:EReference" name="refA" upperBound="-1" eType="#//ClassA"/>
  </eClassifiers>
  <eClassifiers xsi:type="ecore:EClass" name="ClassC">
    <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
    <eStructuralFeatures xsi:type="ecore:EReference" name="refD" upperBound="-1" eType="#//ClassD"
        containment="true" eOpposite="#//ClassD/refC"/>
  </eClassifiers>
  <eClassifiers xsi:type="ecore:EClass" name="ClassD">
    <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
    <eStructuralFeatures xsi:type="ecore:EReference" name="refC" eType="#//ClassC"
        eOpposite="#//ClassC/refD"/>
  </eClassifiers>
</ecore:EPackage>

Instance model (original):

<?xml version="1.0" encoding="UTF-8"?>
<mwe6:Root xmi:version="2.0"
    xmlns:xmi="http://www.omg.org/XMI"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mwe6="http://mwe6"
    xsi:schemaLocation="http://mwe6 mwe6.ecore"
    name="Mwe">
  <classA name="A1">
    <refB name="B1"
        refA="//@classA.1 //@classA.2"/>
  </classA>
  <classA name="A2">
    <refB name="B2"
        refA="//@classA.0 //@classA.2"/>
  </classA>
  <classA name="A3">
    <refB name="B3"
        refA="//@classA.0 //@classA.1"/>
  </classA>
  <classC name="C1">
    <refD name="D1"/>
    <refD name="D2"/>
  </classC>
  <classC name="C2">
    <refD name="D2"/>
  </classC>
</mwe6:Root>

Instance model (read by NMF):

<?xml version="1.0" encoding="utf-8"?>
<mwe6:Root xmi:version="2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Mwe" xmlns:xmi="http://www.omg.org/XMI" xmlns:mwe6="http://mwe6">
  <classA name="A1">
    <refB name="B1" />
  </classA>
  <classA name="A2">
    <refB name="B2" />
  </classA>
  <classA name="A3">
    <refB name="B3" />
  </classA>
  <classC name="C1">
    <refD name="D1" />
    <refD name="D2" />
  </classC>
  <classC name="C2">
    <refD name="D2" />
  </classC>
</mwe6:Root>

Additional references

A minimum working example is provided here: Mwe.zip. The command line used for the example project is: ecore2code --collectionsAreElements -f -o src/Models -n Models -m mwe6.nmf mwe6.ecore. The example just reads and save the instance model back to a different file, without touching anything. The Model.xmi is treated as the "original" instance model of the Ecore metamodel.

georghinkel commented 9 months ago

Hi, thanks for submitting the issue and sorry for the delay, I could not take a look at this issue before christmas.

The problem here is that since you specified that collections are serialized as elements, the serializer does not expect the refA collection to be rendered as attributes, it is expected to be rendered as an element. Therefore, the serializer has no idea how to handle the attribute and simply raises an UnkownAttribute event. This carries the name and value of the attribute as well as the context element, but that does not really help since the event is raised in the middle of the parsing when the cross-references cannot be resolved, yet.

What I can do to help here is to extend the information transmitted in the UnknownAttribute event in order to conveniently make it possible to try to treat the attribute as a misplaced element, or alternatively add a serialization setting to search in the element properties if the attribute is not attribute properties.

Best

georghinkel commented 9 months ago

fixed with 3477f24