vitruv-tools / Vitruv

View-based Development and Model Consistency Framework
http://vitruv.tools
Eclipse Public License 1.0
14 stars 19 forks source link

Workarounds for metamodels with customized reference implementations #489

Open HeikoKlare opened 2 years ago

HeikoKlare commented 2 years ago

The framework contains several workarounds to deal with metamodels that define custom behavior of specific kinds of EReferences, which are partly derived but not marked as such. Currently, this concerns the UML2 metamodel shipped with EMF.

UML Metamodel Behavior

The UML metamodel implements some EReferences using some weird manually and partly derived collections. For example, the InterfaceRealization.clients are a set of manually added elements combined with the InterfaceRealization.implementingClassifier, which, in turn, is just the eContainer of the InterfaceRealization. This behavior is implemented by the SubsetSupersetEObjectResolvingEList, which is used to implement the clients reference of InterfaceRealization as can be seen in the implementation. These kinds of references partly behave as if there were derived, because changing some other reference (such as the implementingClassifier) also changes that reference (such as clients), but they are not marked as derived, in particular because they also contain manually added elements in addition to the derived elements. The clients case is only an example for a pattern used all over the references in the UML metamodel.

Consequence: Ambiguous Changes

Using these kinds of EReferences leads to problematic change events. For example, setting the implementingClassifier also produces a change of the clients. If you want to apply these changes to another instance of the same model, the change to clients must not be applied, because setting the implementingClassifier already implicitly adds the element to clients and otherwise the same element would be added into clients twice. But this only applies if the element to be added to clients is also contained in another reference from which it is derived, but the change can also affect an element that was manually added to a client. These two situation can, however, not be distinguished.

Issue 1: Copying Models

This behavior does not only affect our implementations but also basic EMF mechanisms. Our implementation for creating views uses the EcoreUtil functionality to copy models EcoreUtil$Copier, which is not able to properly handle these partly derived references. In case of the clients example, the implementingClassifier is contained twice in the clients reference after using the EcoreUtil$Copier, which is why we use a different mechanism for copying UML models in the view generation (see the implementation). This is, however, a UML-specific hack, which should actually not be embedded into the framework.

Issue 2: Applying Recorded Changes

A similar problem is specific to our implementation of change recording. Our change recorder properly handles derived features, but due to such EReferences not being derived, it handles them as an ordinary reference. Just like the EcoreUtil$Copier, it creates a change for the clients modification and, if applying these changes, adds the element to clients although it was already added when setting the implementingClassifier. When removing elements, it even leads to the situation that remove changes are generated for elements that have already implicitly been removed, such as removing implementingClassifier implicitly removing the one of the clients but still producing a change that removes that client explicitly. To deal with that problem, we have a relaxed implementation of change application. In that implementation, we accept failures in change application, which should actually not occur and thus throw an exception but are a result of the UML metamodel references implementation and thus are only logged as a warning.

Issue 3: Executing EcoreUtil.delete on UML elements

Even executing EMF-internal functionality provided by the EcoreUtil class does not work properly with the UML metamodel. Performing a delete on a UML element that is also contained in such a problematic reference least to an exception because removing it from one reference (either the actual one or the "derived" one) also removes it from the other and the later removal by the delete operation fails afterwards.

Action Item

These problems with the UML metamodel lead to domain-specific workarounds in our framework, which should actually not be part of it. Although the best solution would be to somehow fix the UML metamodel, these problems may not be restricted to the UML metamodel, but custom implementations of metamodel behavior can also occur with other metamodels (see also our discussion in the PR introducing the copy workaround). Thus, we should consider providing some mechanism to dynamically attach specific functionality for specific kinds of resources where the ordinary functionality does work properly without implementing such domain-specific functionality in the framework. I do not have yet a concrete proposal though.

JanWittler commented 2 years ago

Issue 4: EMF Compare Diffs

When using a state-deriving view (in cases changes cannot be recorded), we use a StateBasedChangeResolutionStrategy to derive the changes for the changed view. In its default implementation (DefaultStateBasedChangeResolutionStrategy), we use EMF Compare to get the list of differences between the original and changed model and then replay those differences on an observed copy of the original model, thus being able to use our change recorder and convert the differences to our change format. Since the problematic references of UML are not marked as derived, EMF Compare generated differences for them. However, trying to replay those differences results in the same problems as described in Issue 2.