Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 350281

Summary: EcoreUtil.EqualityHelper.equals(List<EObject>, List<EObject>) too strict
Product: [Modeling] EMF Reporter: Richard Kulp <richkulp>
Component: CoreAssignee: Ed Merks <Ed.Merks>
Status: CLOSED FIXED QA Contact:
Severity: normal    
Priority: P3 CC: richkulp
Version: unspecified   
Target Milestone: ---   
Hardware: All   
OS: All   
Whiteboard:

Description Richard Kulp CLA 2011-06-24 11:05:28 EDT
Build Identifier: 2.5.0

The problem is that if you have a class A that extends EObject then if you have a list List<A> then this is not compatible with List<EObject> and the equals method will not be selected.

This fails to compile (where X implements EObject):

		List<X> lx = new ArrayList<X>();
		EqualityHelper equalityHelper = new EcoreUtil.EqualityHelper();
		equalityHelper.equals(lx, lx);

Reproducible: Always
Comment 1 Richard Kulp CLA 2011-06-24 11:08:33 EDT
The solution is to change it to:

equals(List<? extends EObject>, List<? extends EObject>)
Comment 2 Richard Kulp CLA 2011-06-24 11:10:57 EDT
And I'm pretty sure that would not break binary compatibility because it is an expansion and doesn't conflict with any other equals() methods on EqualityHelper.
It would still pick up any old List<EObject> references unchanged.
Comment 3 Ed Merks CLA 2011-06-24 12:07:05 EDT
It certainly breaks source compatibility for clients who override it.

It's quite common that folks suppress EMF from their APIs in which case they can't call this method directly without casting anyway.  As such, I'm not sure the small advantage of changing the signature (I agree it would have been better to have done it that way in the first place) outweighs the disadvantage of introduce source incompatibility...

An alternative would be to add a new method, e.g., equalLists.  Perhaps it could accept any type of list as an argument (to deal with lists that don't explicitly extend EObject). Perhaps it could do instanceof EObject tests to decide when to call equals(EObject, EObject) verses just using .equals...

The important point is that there are ways to provide the utility you want without adversely impacting any existing client, so that might be a better approach...  What do you think?
Comment 4 Richard Kulp CLA 2011-06-24 12:32:39 EDT
I don't care. But it isn't just simple casting.

This is invalid:

List<X> x = ...
h.equals((List<EObject>) x, ...)

It will not compile this way. It refuses saying that List<X> cannot be cast to List<EObject>.

You need to do:

@Suppresswarnings("unchecked")
public void yourmethod() {
.
.
.
List<X> x = ...
h.equals((List) x, ...

You need to not use the generics and go to raw types.

I've done that in the code but it is annoying.

I see your point about someone extending EqualityHelper. But since no one has noticed this yet my assumption is that actually no one is using it yet.

It would be very obvious if someone had tried this before because EMF objects return List<X> not List<EObject> for their multi-value features. And this would fail, in fact that is what I was trying to do. Compare two feature lists.
Comment 5 Richard Kulp CLA 2011-06-24 12:39:12 EDT
You could try marking equals(List<EObject>,... as deprecated and
create a equalLists(List<? extends EObject>, ...

and then have equals(List<Eobject> simply redirect to equalsList internally.

I don't like the instance of testing inside because if it could be non-EObject then the caller would of probably NOT of used the helper.
Comment 6 Ed Merks CLA 2011-06-24 12:47:10 EDT
Yes, I'm quite sure it's not a binary incompatibility because the erasure stays the same.

No you don't need to use raw types, you can use this

  (List<EObject>)(List<?>)x 

Clients who suppress EMF from their APIs will always need to do anyway.

Yes, no one has complained about wanting to call equals directly, but that doesn't mean no one has overridden the method...

I don't see a real advantage of deprecation.  If we add a new method, we could make it final and delegate to what we already have. That won't prompt anyone to change anything.  There's no advantage for the new signature to the overriding client, only to the caller, which suggests that yet another solution would be to add a static utility method.  That would be even more convenient for what you're describing with the added advantage that it breaks no one...
Comment 7 Ed Merks CLA 2011-10-25 09:57:49 EDT
I added this:

  /**
   * Returns <code>true</code> if <code>eObjectList1</code> and <code>eObjectList2</code> contain {@link #equals(EObject, EObject) equal} objects.
   * <code>false</code> otherwise.
   * @return whether <code>eObjectList1</code> and <code>eObjectList2</code> contains equal objects
   * @see EqualityHelper
   * @see #equals(EObject, EObject)
   * @since 2.8.0
   */
  @SuppressWarnings("unchecked")
  public static boolean equals(List<? extends EObject> eObjectList1, List<? extends EObject> eObjectList2)
  {
    EqualityHelper equalityHelper = new EqualityHelper();
    return equalityHelper.equals((List<EObject>)eObjectList1, (List<EObject>)eObjectList2);
  }
Comment 8 Ed Merks CLA 2011-11-22 05:26:49 EST
The changes are available in builds.