Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 363454 - EcoreEList contains() method returns incorrect result.
Summary: EcoreEList contains() method returns incorrect result.
Status: CLOSED FIXED
Alias: None
Product: EMF
Classification: Modeling
Component: Core (show other bugs)
Version: unspecified   Edit
Hardware: PC Windows 7
: P3 major (vote)
Target Milestone: ---   Edit
Assignee: Ed Merks CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-11-10 06:18 EST by Rimvydas CLA
Modified: 2012-03-31 14:23 EDT (History)
2 users (show)

See Also:


Attachments
Test case (16.93 KB, application/zip)
2011-11-10 06:21 EST, Rimvydas CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Rimvydas CLA 2011-11-10 06:18:22 EST
Build Identifier: M20100909-0800

If the list contains proxies and number of elements is more than 4 then contains() method returns incorrect result.


Reproducible: Always

Steps to Reproduce:
1. Run attached test case.
2.
3.
Comment 1 Rimvydas CLA 2011-11-10 06:21:31 EST
Created attachment 206779 [details]
Test case
Comment 2 Ed Merks CLA 2011-11-11 12:28:08 EST
It's hard to imagine how this model is useful in a meaningful way. You don't resolve proxies automatically for B.x yet that's the end you're serializing and even worse, in your example here, it contains a cross document reference.  How are you ever expecting that proxy to be resolved? Of course if you set it as proxy resolving, the Ecore modeling won't validate because it doesn't make sense to serialize only the single valued side of a bidirectional reference...

Can you explain how your example represents a meaningful use case?
Comment 3 Rimvydas CLA 2011-11-14 04:44:06 EST
Lets say A is a Type and B is a Property.

                                {transient}
[Type]--type------------------typedProperties--[Property]

If I load a resource that contains Type A then I do not want to load
all resources that contains Properties which types are set to Type A.
If one of resources that contains a Property that type is A loaded then
I want that the resource that contains Type A would be loaded. After load 
of resources I fix such bidirectional references.
Comment 4 Ed Merks CLA 2011-11-14 07:33:28 EST
> Lets say A is a Type and B is a Property.

                                {transient}
> [Type]--type------------------typedProperties--[Property]

> If I load a resource that contains Type A then I do not want to load
> all resources that contains Properties which types are set to Type A.

Loading a resource doesn't cause other resources to load.  Only resolving proxies does that. 

> If one of resources that contains a Property that type is A loaded then
> I want that the resource that contains Type A would be loaded.

Again, this happens as proxies are resolved not directly as a result of loading.  You don't need a bidirectional reference for this either.

>  After load of resources I fix such bidirectional references.

What does "fixing" mean?  It doesn't sound like you have proper bidirectional references but rather broken results that need manual fixing.  In the end, I fail to see anything here that justifies the approach as being sound.  If you're manually going to fix proxies that aren't working properly anyway, then I don't see the need to further complicate the framework to deal with what looks pathological.

I'm sure you can work around the current situation so I'll return this report as won't fix unless there is a compelling use case.
Comment 5 Rimvydas CLA 2011-11-14 08:47:55 EST
>> Lets say A is a Type and B is a Property.
>                                  {transient}
>> [Type]--type------------------typedProperties--[Property]
>> If I load a resource that contains Type A then I do not want to load
>> all resources that contains Properties which types are set to Type A.
> Loading a resource doesn't cause other resources to load.  Only resolving
> proxies does that.

[Rimvydas]: I know that.

>> If one of resources that contains a Property that type is A loaded then
>> I want that the resource that contains Type A would be loaded.
> Again, this happens as proxies are resolved not directly as a result of
> loading.  You don't need a bidirectional reference for this either.

[Rimvydas]: All references in my model must be bidirectional. This is a 
requirement.

>>   After load of resources I fix such bidirectional references.
> What does "fixing" mean?  It doesn't sound like you have proper bidirectional
> references but rather broken results that need manual fixing.  In the end, I
> fail to see anything here that justifies the approach as being sound.  If
> you're manually going to fix proxies that aren't working properly anyway, then
> I don't see the need to further complicate the framework to deal with what
> looks pathological.
>
> I'm sure you can work around the current situation so I'll return this report
> as won't fix unless there is a compelling use case.

[Rimvydas]: Fixing means that
property.type = a
a.typedProperties = {property}

One reference is transient so only one end is saved and because EMF does not have support for such case I have to fix this manually. The model is valid and the code is generated using standard generator so maybe this is not usual case but we can not say that it is pathological.

Let's go back to the problem.  In the test case (see EcoreEListTest.java) index of element b2 is 1.
So if we have 5 elements in the list:
a.getY().indexOf(b2)  -> returns 1
a.getY().contains(b2) -> returns false

if we have 4 elements in the list
(remove the line a.getY().add(b5); from the test case and the test will pass):
a.getY().indexOf(b2)  -> returns 1
a.getY().contains(b2) -> returns true

This looks to me like simple bug.
Comment 6 Ed Merks CLA 2011-11-14 09:16:24 EST
I see no value in fixing this issue.  It will certainly add cost for all clients, i.e., minimally a proxy check, and in the case of a proxy, then what? A full iteration over the list, including a second pass that resolves proxies? As I understand it, this proxy resolution is something you're hoping to avoid. So what is the value in making the changes?  No other client has complained (because they don't have this questionable use case) and yet all clients will pay a cost.  As such, I have to ask, what will be the benefit for that cost?  It seems to me you can and already must work around the issue of manual proxy resolution, so you're certainly in a position to write that logic so that it takes this issue into account.

Another probably more consistent design approach would be to make both ends proxy resolving and make neither end transient and instead specialize XMLSaveImpl.shouldSaveFeature to return false for such cases.
Comment 7 Rimvydas CLA 2011-11-14 10:39:15 EST
Bug fix will add cost but the code will work correctly. Possible fix:
EcoreEList.java line 376 
return ((EObject)object).eGet(getInverseEReference()) == owner;
should be replaced with:


EObject eObject = ((EObject)object);
EObject value = (EObject) eObject.eGet(getInverseEReference());
if (value == null) {
	return false;
}
if (value.eIsProxy()) {
    return owner == EcoreUtil.resolve(value, eObject);
}
else {
    return value == owner;				            	
}

Additional cost for users which do not have non resolving references in their 
models is:

if (value == null)
if (value.eIsProxy())

Is this a significant cost ?

I can use a workaround. Workaround would be to use indexOf(obj) != -1 instead of
contains(obj).

Thanks for the hint about solving the problem with XMLSaveImpl.shouldSaveFeature but
my model also can be saved into DB using CDO. I do not know whether CDO supports similar functionality.
Comment 8 Ed Merks CLA 2011-11-14 11:51:46 EST
I think the following minimizes the overhead for existing clients.

          Object opposite = ((EObject)object).eGet(getInverseEReference());
          if (opposite == owner)
          {
            return true;
          }
          else if (opposite == null || !((EObject)opposite).eIsProxy())
          {
            return false;
          }

The true case has no additional overhead.  The false case now involves additional null pointer check (exceedingly cheap) and a cast as well as a interface method call, both of which are relatively expensive.  I won't consider adding code to resolve the proxy; your model should do that if you want that.  If neither case applies, it does the brute force walk of the contents, including resolving all the proxies, which you probably don't want so you won't be happy with this solution.

I continue to get the impression that you really don't need this problem addressed in order to make your software work.
Comment 9 Rimvydas CLA 2011-11-15 04:26:18 EST
Your fix solves the problem.

As I said in the last comment I can use workaround with indexOf() because I know that current implementation of contains() works incorrectly.
Comment 10 Ed Merks CLA 2012-03-15 02:59:14 EDT
The change is committed to git for 2.8.
Comment 11 Ed Merks CLA 2012-03-31 14:23:53 EDT
The changes are available in the M6 build.