Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Modeling » EMF » Cannot instantiate generics in tree editor
Cannot instantiate generics in tree editor [message #1843062] Tue, 13 July 2021 20:25 Go to next message
Elie Richa is currently offline Elie RichaFriend
Messages: 72
Registered: February 2016
Member
Hello folks,

I'm trying to use generics in Ecore but I ended up with a metamodel where the tree editor refuses to offer creating children that I think are valid.

I'm attaching the metamodel and an instance of it that can be opened in the reflective editor if the files are in the same directory.

My goal is to model objects that have variants. The variants would be children of the object. There are two kinds of variants: either based on a range, or on a pattern. And each variant would have as a child, another instance of the parent object with a different content specific to that variant.

Because I intend to have multiple classes with such variability, I decided to use generics to instantiate the same pattern multiple times.

A class T requiring variability inherits WithVariability<T> . WithVariability<T> defines a list of contained Variant<T>. Variant<T> has a child variantObject of type T. Variant<T> is itself abstract and has two concrete sub-classes RangeVariant<T> and ConcreteVariant<T>.

I instantiate this pattern twice for these classes where I want variability:
- ClassAWithVariability which inherits WithVariability<ClassAWithVariability>
- ClassBWithVariability which inherits WithVariability<ClassBWithVariability>

I created a Root class that can contain instances of ClassAWithVariability and ClassBWithVariability for demonstration.

My expectation in the tree editor is that right-clicking a ClassAWithVariability would allow me to create children RangeVariants and PatternVariants. But it's not the case. It doesn't offer anything.

When I change Variant from abstract to non-abstract, the editor starts offering to create Variant instances, but not RangeVariant and PatternVariant.

With some debugging I found that org.eclipse.emf.edit.provider.ItemProviderAdapter at line 837 is removing the child descriptors for RangeVariant and PatternVariant because it judges that they are not compatible with the expected reified type Variant<ClassAWithVariability>. I suspect for example that the child descriptor provides a child instance of RangeVariant, but the instance cannot hold information about the generic instantiation. So it is judged incompatible with the expected reified type Variant<ClassAWithVariability> which does have the information about the generic instantiation.

Is there something wrong in my metamodel? A workaround? Or is EMF lacking something here?

Thanks in advance for any help! :)
  • Attachment: generics.ecore
    (Size: 2.66KB, Downloaded 693 times)
  • Attachment: NewRoot.xmi
    (Size: 0.30KB, Downloaded 353 times)


Elie Richa, Ph.D
Software Engineer, AdaCore
https://www.adacore.com
Re: Cannot instantiate generics in tree editor [message #1843071 is a reply to message #1843062] Wed, 14 July 2021 10:24 Go to previous messageGo to next message
Ed Willink is currently offline Ed WillinkFriend
Messages: 7679
Registered: July 2009
Senior Member
Hi

EMF generics are far from obvious. They have far more similarities to Java generics than UML templates, but the ability of EGenericType to fulfill many roles can be confusing. I eventually cracked it by empirical discovery and by studying the OCL.ecore and OCLEcore.ecore models that were perhaps the first real usages.

The results of my empirical discovery are captured in the OCLinEcore editor that offers a syntax much more in keeping with Java although the underlying OCL Pivot is rather close to UML.

Give the OCLinEcore editor a try.

You may also give the Xcore editor a go,. You may find that it's more direct coupling to Ecore avoids or just replicates the confusion.

You should beware that 'EGenericType' in many ways obsoletes 'EType' except that to maintain compatibility with the pre-Generic Ecore, the non-generic API is preserved unchanged. Ecore is an interesting example of how a fairly breaking metamodel change can be formulated as an evolution rather than a breaking change.

Regards

Ed Willink
Re: Cannot instantiate generics in tree editor [message #1843099 is a reply to message #1843071] Thu, 15 July 2021 12:51 Go to previous messageGo to next message
Elie Richa is currently offline Elie RichaFriend
Messages: 72
Registered: February 2016
Member
Indeed EMF generics seem to be a tricky beast. But the odd thing is that I got what I want in the generated API. Generic types get declared and bound the way I want them. I can also instantiate models with the API and serialize them to XMI.

It's only in the editor that I'm not seeing the create commands as expected.

Thanks for the suggestion of OCLinEcore, I might give it a try. But I feel my direct Ecore modeling was already correct since I got what I wanted in the generated Java API.


Elie Richa, Ph.D
Software Engineer, AdaCore
https://www.adacore.com
Re: Cannot instantiate generics in tree editor [message #1843100 is a reply to message #1843099] Thu, 15 July 2021 13:15 Go to previous messageGo to next message
Ed Willink is currently offline Ed WillinkFriend
Messages: 7679
Registered: July 2009
Senior Member
Hi

If it's only the edit commands then it's probably a bug. I'm sure that a Bugzilla complete with patch will be welcome.

Regards

Ed Willink
Re: Cannot instantiate generics in tree editor [message #1843157 is a reply to message #1843100] Fri, 16 July 2021 17:26 Go to previous messageGo to next message
Elie Richa is currently offline Elie RichaFriend
Messages: 72
Registered: February 2016
Member
Hello again,

I managed to create a small self-contained JUnit reproducer. I can create a Bugzilla if we confirm that this is a bug.

I create 4 classes:
- non-abstract GenericParent<T>
- non-abstract GenericChild<T> extending GenericParent<T>
- SomeConcreteClass
- ClassWithReference having a containment reference of type GenericParent<T>

Then I instantiate GenericParent and GenericChild. It's no use specifying the type bound to the generic type T because that information is not stored anyway on runtime in Java.

Next I ask EMF if the instances can be stored in the containment reference in the same way the EMF editor does:
ref.getEGenericType().isInstance(instanceObj)


EMF answers yes for the GenericParent and no for the GenericChild which seems to me like an unexpected discrepancy. Either both should answer yes, or both should answer no.

With some debugging I found that the discrepancy comes from EGenericTypeImpl.java:700 where if the EClass of the instance is equal to the EClass of the type then we immediately accept the instance without inspecting the instance EType arguments, while in the else branch we do inspect them and cannot find a match because the instance EType generics are not bound since there's no way to get the runtime generic binding of an object in Java.

I hope my explanation is understandable - It's very hard to find the unambiguous words to refer to these complex concepts...

Here's the test:

    @Test
    public void testEGenericType() {
        final EcoreFactory f = EcoreFactory.eINSTANCE;
        final EPackage pkg = f.createEPackage();

        final EClass genericParent = f.createEClass();
        pkg.getEClassifiers().add(genericParent);
        /*
         * GenericParent<T>
         */
        genericParent.setName("GenericParent");
        {
            final ETypeParameter parentTypeParam = f.createETypeParameter();
            genericParent.getETypeParameters().add(parentTypeParam);
            parentTypeParam.setName("T");
        }

        /*
         * GenericChild<T> inherits GenericParent<T>
         */
        final EClass genericChild = f.createEClass();
        pkg.getEClassifiers().add(genericChild);
        genericChild.setName("GenericChild");
        {
            final ETypeParameter typeParam = f.createETypeParameter();
            genericChild.getETypeParameters().add(typeParam);
            typeParam.setName("T");

            final EGenericType eGenericSuperType = f.createEGenericType();
            {
                eGenericSuperType.setEClassifier(genericParent);
                final EGenericType eTypeArg = f.createEGenericType();
                eTypeArg.setETypeParameter(typeParam);
                eGenericSuperType.getETypeArguments().add(eTypeArg);
            }
            genericChild.getEGenericSuperTypes().add(eGenericSuperType);
        }

        final EClass someConcreteClass = f.createEClass();
        pkg.getEClassifiers().add(someConcreteClass);
        someConcreteClass.setName("SomeConcreteClass");

        final EClass classWithRef = f.createEClass();
        pkg.getEClassifiers().add(classWithRef);
        classWithRef.setName("ClassWithReference");
        EGenericType eRefType = f.createEGenericType();
        {
            final ETypeParameter parentTypeParam = f.createETypeParameter();
            classWithRef.getETypeParameters().add(parentTypeParam);
            parentTypeParam.setName("T");

            /*
             * ref : GenericParent<SomeConcreteClass>
             */
            final EReference eRef = f.createEReference();
            eRef.setName("ref");
            eRef.setContainment(true);
            eRefType.setEClassifier(genericParent);
            EGenericType eTypeArg = f.createEGenericType();
            eTypeArg.setEClassifier(someConcreteClass);
            eRefType.getETypeArguments().add(eTypeArg);
            eRef.setEGenericType(eRefType);
        }

        /*
         * Create an instance of GenericChild<?>
         */
        final EObject parentInstance = EcoreUtil.create(genericParent);
        final EObject childInstance = EcoreUtil.create(genericChild);

        /*
         * Ask if we can put the GenericParent<?> instance in the ref of type
         * GenericParent<SomeConcreteClass>.
         *
         * EMF answers yes.
         */
        assertTrue(eRefType.isInstance(parentInstance));

        /*
         * Ask if we can put the GenericChild<?> instance in the ref of type
         * GenericParent<SomeConcreteClass>.
         *
         * This fails because EMF answers no. So I see a discrepancy. Either
         * both cases should yield yes or both should yield no.
         */
        assertTrue(eRefType.isInstance(childInstance));
    }


Elie Richa, Ph.D
Software Engineer, AdaCore
https://www.adacore.com
Re: Cannot instantiate generics in tree editor [message #1843164 is a reply to message #1843157] Sat, 17 July 2021 07:40 Go to previous messageGo to next message
Ed Merks is currently offline Ed MerksFriend
Messages: 33252
Registered: July 2009
Senior Member
You should note that all the logic in org.eclipse.emf.ecore.impl.EGenericTypeImpl.isInstance(Object) is focused on reified types, i.e., on what can we can determined about the type bounds based on what's explicitly specified in the inheritance hierarchy.

https://bugs.eclipse.org/bugs/show_bug.cgi?id=382774

As such, when we get to line 702, we can know nothing further about the type bounds that were used when the instance was created. So what to do? Be pessimistic and return false? There is no other choice of values to return, and returning false will not help solve your problem will it? (And I'm sure changing it will break many things .) Conversely, if we return true on the latter case (to make it consistent), we might as well not bother with any of this logic and just use EClassifier.isInstance which defeats the purpose of implementing 382774. Or perhaps you're suggesting some other/different logic that does break 382774 but still returns true.


Ed Merks
Professional Support: https://www.macromodeling.com/
Re: Cannot instantiate generics in tree editor [message #1843189 is a reply to message #1843164] Mon, 19 July 2021 14:41 Go to previous messageGo to next message
Elie Richa is currently offline Elie RichaFriend
Messages: 72
Registered: February 2016
Member
Hello Ed,

Thank you for the explanation and the link to 382774. The initial use case described by Jean-Francois Brazeau is exactly the one I have and where I don't get a satisfactory behavior in the editor. As the last message by Jean-François is "I will test it as soon as possible.", I suspect that he didn't get around to test it because I think he would have gotten the same behavior I'm seeing: inability to create children objects in the tree editor.

Now regarding the API/code, I understand and agree that we shouldn't break existing behavior. So I came up with the following attempt at detecting the situation I have and returning an optimistic result for lack of accurate typing information.

I ran all of "Test EMF Core" which includes ReificationTest and didn't get any regressions. It's likely that Reification.ecore does not include a case similar to mine. So if this change seems roughly reasonable, I can open a bug and submit the patch + corresponding test cases on Gerrit.

diff --git a/plugins/org.eclipse.emf.ecore/src/org/eclipse/emf/ecore/impl/EGenericTypeImpl.java b/plugins/org.eclipse.emf.ecore/src/org/eclipse/emf/ecore/impl/EGenericTypeImpl.java
index 8385bbb7f..a687bb713 100644
--- a/plugins/org.eclipse.emf.ecore/src/org/eclipse/emf/ecore/impl/EGenericTypeImpl.java
+++ b/plugins/org.eclipse.emf.ecore/src/org/eclipse/emf/ecore/impl/EGenericTypeImpl.java
@@ -708,41 +708,55 @@ public class EGenericTypeImpl extends MinimalEObjectImpl.Container implements EG
               for (EGenericType eGenericSuperType : instanceEClass.getEAllGenericSuperTypes())
               {
                 if (eGenericSuperType.getEClassifier() == eClass)
                 {
                   // If it's specified as a raw type, we're done.
                   //
                   EList<EGenericType> instanceETypeArguments = eGenericSuperType.getETypeArguments();
                   if (instanceETypeArguments.isEmpty())
                   {
                     return true;
                   }
                   else
                   {
                     // Look for a contradiction between the type arguments of this generic type and the type arguments specified for the instance.
                     //
                     for (int i = 0; i < size; ++i)
                     {
                       EGenericType instanceETypeArgument = instanceETypeArguments.get(i);
                       EGenericType eTypeArgument = eTypeArguments.get(i);
                       EGenericType reifiedType = EcoreUtil.getReifiedType(instanceEClass, instanceETypeArgument);
-                      if (eTypeArgument.getETypeParameter() == null ? !isCompatibleArgument(reifiedType, eTypeArgument) : !isSuperType(reifiedType, eTypeArgument))
+                      if (eTypeArgument.getETypeParameter() == null)
+                      {
+                        if (reifiedType.getETypeParameter() != null && reifiedType.getETypeParameter().getEBounds().isEmpty())
+                        {
+                          // The reified type has a type parameter that was not resolved and that has no bound information
+                          // so we cannot determine any relevant compatibility information. Return an optimistic result.
+                          //
+                          return true;
+                        }
+                        else if (!isCompatibleArgument(reifiedType, eTypeArgument))
+                        {
+                          return false;
+                        }
+                      }
+                      else if (!isSuperType(reifiedType, eTypeArgument))
                       {
                         return false;
                       }
                     }
 
                     return true;
                   }
                 }
               }
 
               // We should not get here.
               //
               return false;
             }
           }
         }
       }
     }
   }
 


Elie Richa, Ph.D
Software Engineer, AdaCore
https://www.adacore.com
Re: Cannot instantiate generics in tree editor [message #1843201 is a reply to message #1843189] Tue, 20 July 2021 06:37 Go to previous messageGo to next message
Ed Merks is currently offline Ed MerksFriend
Messages: 33252
Registered: July 2009
Senior Member
Well, I'm a little doubtful of the change. Note specifically that the loop that you terminate early/optimistically with a return true, would normally return true only if none of the iterations return false. So I don't think it's correct to return true from within the loop but rather to continue the loop until no other contradictions are found. Also, the code at line 883 is very similar, so it makes me wonder why that doesn't also need attention, or maybe it does, but the next person will find that out. Without fully and personally understanding the scenarios involved, I'm not so comfortable making "arbitrary" changes...

Ed Merks
Professional Support: https://www.macromodeling.com/
Re: Cannot instantiate generics in tree editor [message #1843223 is a reply to message #1843201] Tue, 20 July 2021 15:31 Go to previous messageGo to next message
Elie Richa is currently offline Elie RichaFriend
Messages: 72
Registered: February 2016
Member
Ah indeed, I did terminate the loop prematurely...

More generally I agree that it's a tricky topic where you are the best placed to intervene, and I don't feel it's a blocking enough problem I have a course of action in mind below. Would it help if I submit a reproducer on Gerrit as part of Reification.ecore and ReificationTest for future analysis?

For my immediate work, I plan to simplify the design and avoid generics combined with inheritance. I'll use EObject or some reasonable superclass from my metamodel and add runtime type validation to replace generics. I will use generics only on some EOperations where it will be handy to cast results into the type expected by the client.


Elie Richa, Ph.D
Software Engineer, AdaCore
https://www.adacore.com
Re: Cannot instantiate generics in tree editor [message #1843267 is a reply to message #1843223] Thu, 22 July 2021 06:36 Go to previous message
Ed Merks is currently offline Ed MerksFriend
Messages: 33252
Registered: July 2009
Senior Member
Yes, please open a Bugzilla so that the Gerrit commit can include the header in the form
[<bugzilla-id>] <description>
A Gerrit review is likely to be lost without a link to a bugzilla.


Ed Merks
Professional Support: https://www.macromodeling.com/
Previous Topic:generate a diagram for a model instance
Next Topic:Identifier specification
Goto Forum:
  


Current Time: Sat Nov 09 01:44:19 GMT 2024

Powered by FUDForum. Page generated in 0.03291 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top