Bug 129178 - NPE on MSLUtil.destroy(...) when using FeatureMaps
Summary: NPE on MSLUtil.destroy(...) when using FeatureMaps
Status: RESOLVED FIXED
Alias: None
Product: GMF-Runtime
Classification: Modeling
Component: General (show other bugs)
Version: unspecified   Edit
Hardware: PC Windows XP
: P1 critical
Target Milestone: 1.0   Edit
Assignee: Chris McGee CLA
QA Contact:
URL:
Whiteboard:
Keywords: contributed
Depends on:
Blocks: 123683
  Show dependency tree
 
Reported: 2006-02-23 11:34 EST by Daniel Berg CLA
Modified: 2010-07-19 12:24 EDT (History)
1 user (show)

See Also:


Attachments
Patch to delegate to EcoreUtil.remove() (4.46 KB, patch)
2006-03-09 14:42 EST, Chris McGee CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Daniel Berg CLA 2006-02-23 11:34:33 EST
The destroy(MSLEditingDomain, EObject, int) method will throw an NPE if the EObject is part of a substitution group reference which means that it uses a FeatureMap in EMF.  The problem is on line 220.

container.eSet(reference, null);

The code block is

if (reference.isMany())
	((Collection) container.eGet(reference)).remove(eObject);
else
	container.eSet(reference, null);

This needs to be set to the following:


if (reference.isMany())
	((Collection) container.eGet(reference)).remove(eObject);
else
	container.eUnset(reference);

The eUnset will do the correct action when using FeatureMaps.  Passing a null value into a FeatureMap eSet(...) is a bad thing.  :)
Comment 1 Chris McGee CLA 2006-02-24 10:27:58 EST
I'm looking for a test case for the fix to the bug. Is it possible for you to provide an example attached to this bugzilla?
Comment 2 Daniel Berg CLA 2006-02-24 11:59:18 EST
Part of the problem here is that I forgot to mention which class has the problem. :)

The problem is with org.eclipse.gmf.runtime.emf.core.util.EObjectUtil#destroy(...).

This method is similar to the ECoreUtil.remove(EObject) method.

  public static void remove(EObject eObject)
  {
    EObject container = ((InternalEObject)eObject).eInternalContainer();
    if (container != null)
    {
      EReference feature = eObject.eContainmentFeature();
      if (feature.isMany())
      {
        ((EList)container.eGet(feature)).remove(eObject);
      }
      else
      {
        container.eUnset(feature);
      }
      ....

Note that when you are intending to remove an EObject from its container you should use eUnset for a single valued reference and not eSet(feature, null).  The second doesn't work when the feature is affiliated with a substitution group.

It looks like the EObjectUtil#destroy(...) method copied some of the ECoreUtil.remove(...) code but it has not been updated.

A test case could be created but it requires the creation of an EMF model from XSD where a type has a reference to a global element using FeatureMaps.  Then define a second model (XSD) that extends the first and defines a substitution for the global element.  Then create an instance of the original object with a reference instance to the substitution type.  Then use EcoreUtil.destroy(...) to destroy it.

This is a lot of work for a simple fix that looks like a miss copy or a change from the original copy code.



Comment 3 Christian Damus CLA 2006-02-24 18:03:28 EST
Why are we not using EcoreUtil.remove()?
Comment 4 Daniel Berg CLA 2006-02-26 21:39:09 EST
That was actually going to be my next question.  :-)
Comment 5 John Swanke CLA 2006-02-27 07:31:15 EST
(In reply to comment #3)
> Why are we not using EcoreUtil.remove()?

EObjectUtil.destroy() works with the MSLEditingDomain and can be undone while EcoreUtil.remove() does not and is a one way trip. 
Comment 6 Chris McGee CLA 2006-02-27 09:43:04 EST
(In reply to comment #3)
> Why are we not using EcoreUtil.remove()?
> 

I'm looking into making that call instead of doing everything ourselves and not being up-to-date with EMF.
Comment 7 Chris McGee CLA 2006-02-27 09:43:56 EST
(In reply to comment #5)
> (In reply to comment #3)
> > Why are we not using EcoreUtil.remove()?
> 
> EObjectUtil.destroy() works with the MSLEditingDomain and can be undone while
> EcoreUtil.remove() does not and is a one way trip. 
> 

This is not true. We can undo a remove as well as a destroy.
Comment 8 Chris McGee CLA 2006-02-27 09:46:43 EST
Daniel, I tried to reproduce the problem as you had said but my XSD knowledge is a little weak. I was wondering if you could provide me with some XSD's (or a single XSD) that I could use to generate EMF code and a test case. I just want to be sure that I'm actually fixing the problem before I deliver any changes.
Comment 9 John Swanke CLA 2006-02-27 10:09:54 EST
(In reply to comment #7)
> (In reply to comment #5)
> > (In reply to comment #3)
> > > Why are we not using EcoreUtil.remove()?
> > 
> > EObjectUtil.destroy() works with the MSLEditingDomain and can be undone while
> > EcoreUtil.remove() does not and is a one way trip. 
> > 
> This is not true. We can undo a remove as well as a destroy.

Could you enlighten me: when I use destroy(), an undo is inserted in the Edit menu. When I use remove(), nothing is. When I look at the destroy() code, it invokes the delete against the resource's edit domain. When I look at the remove()'s code, it seems to just do a mechanical removal of an item from an array without regard to any editing domains. What am I missing?
Comment 10 Chris McGee CLA 2006-02-27 11:16:02 EST
(In reply to comment #9)
> (In reply to comment #7)
> > (In reply to comment #5)
> > > (In reply to comment #3)
> > > > Why are we not using EcoreUtil.remove()?
> > > 
> > > EObjectUtil.destroy() works with the MSLEditingDomain and can be undone while
> > > EcoreUtil.remove() does not and is a one way trip. 
> > > 
> > This is not true. We can undo a remove as well as a destroy.
> 
> Could you enlighten me: when I use destroy(), an undo is inserted in the Edit
> menu. When I use remove(), nothing is. When I look at the destroy() code, it
> invokes the delete against the resource's edit domain. When I look at the
> remove()'s code, it seems to just do a mechanical removal of an item from an
> array without regard to any editing domains. What am I missing?
> 

The way that MSL works is based on a content adapter of sorts. It listens to every event coming from all EObjects that belongs to its domain (or ever did belong to the domain). It is able to undo/redo based on the history of events that it receives.

This is the value added to base EMF where you normally need to construct commands and provide undo/redo logic or you need to create a change recorder and activate it for the session that you wish to undo/redo.
Comment 11 John Swanke CLA 2006-02-27 11:55:41 EST
> The way that MSL works is based on a content adapter of sorts. It listens to
> every event coming from all EObjects that belongs to its domain (or ever did
> belong to the domain). It is able to undo/redo based on the history of events
> that it receives.
> This is the value added to base EMF where you normally need to construct
> commands and provide undo/redo logic or you need to create a change recorder
> and activate it for the session that you wish to undo/redo.

So EObjectUtil.destroy() uses an edit domain, but EcoreUtil.remove() depends on MSL to be listening to EMF model changes and constructing undo/redo commands in the background? I've seen this mechinism before and think it's pretty cool-- unfortunately right now in my editor destroy() works for undo and remove() doesn't--for what ever reason. So it would be really nice if destroy() had this bug fixed...
Comment 12 Chris McGee CLA 2006-02-27 13:42:38 EST
(In reply to comment #11)
> > The way that MSL works is based on a content adapter of sorts. It listens to
> > every event coming from all EObjects that belongs to its domain (or ever did
> > belong to the domain). It is able to undo/redo based on the history of events
> > that it receives.
> > This is the value added to base EMF where you normally need to construct
> > commands and provide undo/redo logic or you need to create a change recorder
> > and activate it for the session that you wish to undo/redo.
> 
> So EObjectUtil.destroy() uses an edit domain, but EcoreUtil.remove() depends on
> MSL to be listening to EMF model changes and constructing undo/redo commands in
> the background? I've seen this mechinism before and think it's pretty cool--
> unfortunately right now in my editor destroy() works for undo and remove()
> doesn't--for what ever reason. So it would be really nice if destroy() had this
> bug fixed...
> 

In both cases, MSL is listening to everything that happens to the EObjects. It can undo/redo in either case. It's just that destroy is an operation that EMF doesn't directly support so we provide a utility to perform the destroy.

It could be that there's a bug in the editor that you are using if you're not seeing an undo/redo for the remove case.
Comment 13 Daniel Berg CLA 2006-02-27 17:58:52 EST
Regardless of whether we should use EcoreUtil.remove(...) or EObjectUtil.destroy(...) the fact still remains that your EObjectUtil.destroy(...) API does not work if the EObject passed has references that are tied to FeatureMaps because internally it is using EObject.eSet(EStructuralFeature, null) and not EObject.eUnset(EStructuralFeature) [similar to the EObject.remove(...) method)].
Comment 14 Chris McGee CLA 2006-03-09 14:42:35 EST
Created attachment 36011 [details]
Patch to delegate to EcoreUtil.remove()
Comment 15 Chris McGee CLA 2006-03-09 14:43:29 EST
Bugzilla 130401 is fixed so EcoreUtil.remove() will handle feature map cases properly. With the attached patch, we will be calling EcoreUtil.remove() instead of removing ourselves. Hopefully this will fix the problem.
Comment 16 Vishy Ramaswamy CLA 2006-03-09 15:15:46 EST
reviewed and applied patch
Comment 17 Richard Gronback CLA 2008-08-13 13:07:23 EDT
[target cleanup] 1.0 M6 was the original target milestone for this bug
Comment 18 Eclipse Webmaster CLA 2010-07-19 12:24:41 EDT
[GMF Restructure] Bug 319140 : product GMF and component
Runtime EMF was the original product and component for this bug