Jump to: navigation, search

Difference between revisions of "EMF/Recipes"

< EMF
m (Code generation recipes)
(Recipe: Avoiding xsi:type in your serialization)
Line 3: Line 3:
 
== Code generation recipes ==
 
== Code generation recipes ==
  
=== Recipe: Avoiding xsi:type in your serialization===
+
=== Recipe: Exploiting Substitution Groups Without Polluting Your Generated API===
  
 
=== Recipe: Generating Pure API With No Visible EMF Dependencies===
 
=== Recipe: Generating Pure API With No Visible EMF Dependencies===

Revision as of 16:41, 3 December 2007

This topic gathers recipe-style solutions for common needs developers using EMF have.

Code generation recipes

Recipe: Exploiting Substitution Groups Without Polluting Your Generated API

Recipe: Generating Pure API With No Visible EMF Dependencies

Problem

You want to generate code for an EMF model but you don't want any references to EMF types in your API or anything else that reveals that the API is implemented by EMF.

Solution

In your genmodel:

  • Set the 'Suppress EMF Types' property to 'true'; standard Java types will be used rather than EMF types for all accessors and operations. Features of type 'EObject' will be surfaced as 'java.lang.Object' instead. If the model includes feature maps, you will need to use the properties 'Feature Map Wrapper Class', 'Feature Map Wrapper Interface', and 'Feature Map Wrapper Internal Interface' to provide an alternative API. You can look as the support for SDO's Sequence API as the example.
  • Clear or set the value of the 'Root Extends Interface' generator model property. If cleared, the generated domain API will not depend on anything, or it can be set to an interface of your choosing so that root interfaces will extend this specified interface.
  • Set 'Suppress EMF Metadata' to 'true' so that only a package implementation class is generated, but no interface, and so that the generated factory interface will not extend EFactory and will have an INSTANCE instead of an eINSTANCE field. Alternatively, set the generator package's 'Metadata' property to redirect the package and factory interfaces to a different Java subpackage.
  • Set the 'Suppress EMF Model Tags' property to 'false' to eliminate the generation of the @model tags in the Javadoc.
  • Set the 'Root Extends Class' and 'Root Implements Interface' properties to control the generation of the implementation, but if you clear the first one or set it so the generated implementation is not a subclass of EObjectImpl, the generated code will be invalid as it will have unresolved references to inherited methods that will not be available, e.g., eGet, eSet, eUnset, eIsSet and eIsProxy. Generating an implementation that is pure Java is not possible with the default templates but can be achieved with dynamic templates.

Related recipes

None so far.

References



Recipe: Using Multiple Namespaces in the Generated EMF Editor

Problem

You want to use multiple namespaces in your generated EMF editor: say you have metamodel A with namespace mm.A which is a superset of legacy metamodel B with namespace mm.B .

If you generate an EMF editor using namespace mm.A, it can by default not read files which were serialized using namespace mm.B, unless you manually edit the files to point to namespace mm.A. But all files using metamodel B must be compatible with some legacy tool so they cannot be changed.

Now you could rename namespace mm.A to mm.B and regenerate the editor, but that forces you to adapt all your manual code and all your new files using metamodel A.

Solution

  • You can change your plugin.xml to let the editor recognize both namespaces:
<extension point="org.eclipse.emf.ecore.generated_package">
  <package
     uri = "mm.B"
     class = "mm.APackage"
</extension>
<extension point="org.eclipse.emf.ecore.generated_package">
  <package
     uri = "mm.A"
     class = "mm.APackage"
     genModel = "model/A.genmodel" />
</extension>
  • If you save a model in your generated EMF editor, it will by default use namespace mm.A. You can change it to save using namespace mm.B by changing the String eNS_URI in src/mm.APackage (and changing the @generated tag).

Related recipes

None so far.

References

Notification Framework Recipes

Recipe: Use EContentAdapter to receive notifications from a whole containment tree

Problem

You want to observe changes in a whole containment tree.

Solution

  • Extend org.eclipse.emf.ecore.util.EContentAdapter (MyContentAdapter) and add that extension as an Adapter to the Root EObject of the containment hierarchy that you want to observe.
  • Override the method 'notifyChanged(Notification n)'. Inside the method´s body your first call must be super.notifyChanged(n) which adds MyContentAdapter to any new elements in the hierarchy and removes MyContentAdapter from any removed EObjects in the hierarchy. After finding out the type of the notifier Object (Remember: this might now be any EObject in the containment hierarchy, not just the EObject you initially added MyContentAdapter to - so it could be of any type that occurs in the containment hierarchy) you can go on by writing the usual notification code to find out about what feature changed, the type of the notification, etc.

Example

A simple example model org.example.library consists of an EClass 'Library' which has a containment reference 'books' of type 'Book'. The EClass 'Book' has a String attribute 'title' and a boolean attribute 'available'. Now you want to be notified about a) new Books in the Library and b) about changes of any book´s availability (the title of a book usually won´t change so we exclude this feature). Using the Solution described above we will do the following:

class MyContentAdapter extends org.eclipse.emf.ecore.util.EContentAdapter {

    // start observing a Library model
    public void observeLibrary(org.example.library.Library l){
        l.eAdapters().add(this);
    }

    //override the notifyChanged method
    public void notifyChanged(Notification n){
        
        super.notifyChanged(n); // the superclass handles adding/removing this Adapter to new Books
        
        // find out the type of the notifier which could be either 'Book' or 'Library'
        Object notifier = Notification.getNotifier();
        if (notifier instanceof Library) {
            handleLibraryNotification(n);
        } else if (notifier instanceof Book) {
            handleBookNotification(n);
        }
    }

    // output a message about new books
    private void handleLibraryNotification(Notification n){
        int featureID = n.getFeatureID(org.example.library.Library.class);
        if (featureID = org.example.library.LibraryPackage.LIBRARY__BOOKS){
            if (n.getType == Notification.ADD){
                Book b = (Book) n.getNewValue(); 
                System.out.println("New Book was added to the Library: + " b.getTitle());
            }
        }
    }

    // output a message about a book´s availability
    private void handleBookNotification(Notification n){
        int featureID = n.getFeatureID(org.example.library.Book.class);
        if (featureID == org.example.library.LibraryPackage.BOOK__AVAILABLE){
                Book b = (Book) n.getNotifier();
                System.out.println("The book " + b.getTitle() + " is now " + (b.isAvailable() ? "available" : "unavailable"));
        }
    }
}

Related Recipes

None so far.

References

  • Thread on the EMF newsgroup: "[1]"

Properties Recipes

Recipe: Create your own property editor in a generated application

Problem

You want to edit your model properties using your own cell editor, rather than using EMF default one.

Solution

  • Create your own CustomizedPropertyDescritor that extends 'org.eclipse.emf.edit.ui.provider.PropertyDescriptor' and overrides 'CellEditor createPropertyEditor(Composite)'. In that method create your own CellEditor. Please refer to super implementation to know, how to enable your editor for particular types.
  • Create your CustomizedPropertySource that extends 'org.eclipse.emf.edit.ui.provider.PropertySource.class' and overrides 'IPropertyDescriptor createPropertyDescriptor(IItemPropertyDescriptor)'. The overidden method should return CustomizePropertyDescriptor described in the previous paragraph.
  • Create your CustomizedAdapterFactoryContentProvider that extends 'org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider' and overrides 'IPropertySource createPropertySource(Object, IItemPropertySource)' method. The overidden method should return CustomizedPropertySource.
  • In your generated .editor find the XyzEditor, and then, in 'IPropertySheetPage getPropertySheetPage()' replace the line

propertySheetPage.setPropertySourceProvider(new AdapterFactoryContentProvider(adapterFactory)); with a line that will set your CustomizedAdapterFactoryContentProvider.

Example

  /**
   * This accesses a cached version of the property sheet.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated NOT
   */
  public IPropertySheetPage getPropertySheetPage()
  {
    if (propertySheetPage == null)
    {
      //...

      propertySheetPage.setPropertySourceProvider
        (new AdapterFactoryContentProvider(adapterFactory)
         {
           @Override
           protected IPropertySource createPropertySource(Object object, IItemPropertySource itemPropertySource)
           {
             return 
               new PropertySource(object, itemPropertySource)
               {
                 @Override
                 protected IPropertyDescriptor createPropertyDescriptor(IItemPropertyDescriptor itemPropertyDescriptor)
                 {
                   return 
                     new PropertyDescriptor(object, itemPropertyDescriptor)
                     {
                       @Override
                       public CellEditor createPropertyEditor(Composite composite)
                       {
                         // Test for your case based on the feature or the type of the feature.
                         // See the super method for details.
                         //
                         Object feature = temPropertyDescriptor.getFeature(this.object);
                         return super.createPropertyEditor(composite);
                       }
                     };
                 }
               };
           }
         });
    }

    return propertySheetPage;
  }


Related Recipes

None so far.


References

  • Thread on the search result: [2]
  • Blog on using Date control: [3]


Recipe: Create an Eclipse Forms editor with widgets for your properties

Problem

You want to edit your model properties using a custom non-tree-based forms editor, rather than using EMF default one.

Solution

  • You need the following components to get this working:
* ComposedAdapterFactory : this adapts all the ItemProvider stuff from the EMF-generated .edit plugin to suit your needs
* AdapterFactoryEditingDomain : this is the read/write interface to your model's resource, it needs the adapter factory and a command stack
* AdapterFactoryItemDelegator : delegates from a model element (item) to the respective item provider from the .edit plugin
* AdapterFactoryLabelProvider : gets text for your model elements by asking the item provider
* BasicCommandStack : well...a basic command stack with some undo/redo support
  • To start implementing, open the default multi-page editor generated by EMF from your model. You need to merge parts of this into the forms editor, among them:
* resource management code (changed, saved and removed resources as well as the resourceChangeListener and partListener)
* editor state management code (isDirty() and all save-related methods)
* methods which handle events related to the above mentioned stuff
* the 3 methods documented below
  • initializeEditingDomain() : This method glues most of the above-mentioned components together. Call it in the editor's constructor. The XYZItemProviderAdapterFactory (generated by EMF in the .edit plugin) should already be added to the ComposedAdapterFactory (if not, add in the one you need).
	protected void initializeEditingDomain() {
		// Create an adapter factory that yields item providers.
		// For this to work this plugin needs the dependency on the 
		// EMF edit plugin generated from the ThetaML .ecore and .genmodel
		this.adapterFactory = new ComposedAdapterFactory(
				ComposedAdapterFactory.Descriptor.Registry.INSTANCE);

		this.adapterFactory
				.addAdapterFactory(new ResourceItemProviderAdapterFactory());
		this.adapterFactory
				.addAdapterFactory(new ThetamlItemProviderAdapterFactory());
		this.adapterFactory
				.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());

		// command stack that will notify this editor as commands are executed
		BasicCommandStack commandStack = new BasicCommandStack();

		// Add a listener to set the editor dirty of commands have been executed
		commandStack.addCommandStackListener(new CommandStackListener() {
			public void commandStackChanged(final EventObject event) {
				getContainer().getDisplay().asyncExec(new Runnable() {
					public void run() {
						editorDirtyStateChanged();
					}
				});
			}
		});

		// Create the editing domain with our adapterFactory and command stack.
		this.editingDomain = new AdapterFactoryEditingDomain(adapterFactory,
				commandStack, new HashMap<Resource, Boolean>());
		
		// These provide access to the model items, their property source and label
		this.itemDelegator = new AdapterFactoryItemDelegator(adapterFactory);
		this.labelProvider = new AdapterFactoryLabelProvider(adapterFactory);
	}
  • init() : Editor initialization comprises registering for part and resource events and creation of your model (see below).
	public void init(IEditorSite site, IEditorInput editorInput)
			throws PartInitException {
		super.init(site, editorInput);
		setPartName(editorInput.getName());
		site.getPage().addPartListener(partListener);
		createModel();
		ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceChangeListener,
				IResourceChangeEvent.POST_CHANGE);
	}
  • createModel() : This loads your model's resource into the editing domain. If you don't need the diagnostic stuff you may skip that as I did. After the resource is loaded, your model instance can be created and used.
	public void createModel() {
	
		URI resourceURI = EditUIUtil.getURI(getEditorInput());
		Resource resource = null;
		try {
			// Load the resource through the editing domain.
			//
			resource = editingDomain.getResourceSet().getResource(resourceURI,
					true);
		} catch (Exception e) {
			resource = editingDomain.getResourceSet().getResource(resourceURI,
					false);
		}

		if (resource != null) {
			this.modelRoot = (Your_Model_Root_Element) resource.getContents().get(0);
		}
	}
  • Create a new class extending FormPage and overload the constructor to give the class access to all components you need. This may be the model or parts of it, references to the form editor, the editing domain etc.
  • Implement the createFormContent(IManagedForm managedForm) method of the form page by creating form widgets as you like. For instance, you may traverse your model and create a text field for every property.
  • To fetch an element's property, instruct the above-mentioned ItemDelegator to fetch so-called "property descriptors" for this element. Just iterate over this list of IItemPropertyDescriptor's ("descriptor" is the loop variable here, it has to be final) and call
EObject feature = (EObject) descriptor.getFeature(your_element_instance);

Now you may introspect this feature instance and decide what widget you want to create for it. By looking at it's feature ID, you can discover which property the feature represents. The actual property value you get by calling the ItemDelegator (I wrote a getter in my editor class):

Object value = ((EObject) editor.getItemDelegator().getEditableValue(your_element_instance)).eGet(your_feature_instance)

In most cases it will be a String value, so you just have to create a text field for it:

Text text = toolkit.createText(client, valueString, SWT.SINGLE);
  • To update your model when the user modifies the value in the form, you have to add a mofifyListener to handle this:
	if (value instanceof String) {
		text.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent event) {
				for (IItemPropertyDescriptor descriptor2 : variablePropertyDescriptors) {
					if (descriptor2.getId(formula).equals(descriptor.getId(formula))) {
						String newValueText = ((Text) event.widget).getText();
						descriptor2.setPropertyValue(formula, newValueText);
					}
				}
			}
		});
	}

That's basically it. Your mileage may vary. Things left to do include code to update the form upon resource changes.

Explanations

The important concept to grasp is that you interact with your model in a very indirect way. To utilize all the nice things the EMF framework offers out-of-the-box, you have to use its mechanics. I'm referring to the generated XYZItemProvider classes and the accompanying adapter factory. If you look at these you find they implement the interfaces IItemPropertySource and IItemLabelProvider. This enables the AdapterFactoryItemDelegator and AdapterFactoryLabelProvider to fetch properties and label text for each item, i.e., element of your model. They also implement IEditingDomainItemProvider which enables the AdapterFactoryEditingDomain to delegate command creation to the EMF-generated ItemProviderAdapter (all XYZItemProvider classes extend this). The line where setPropertyValue is called is the important one - here the magic happens. This call is delegated to the ItemProvider for the element you used and a SetCommand is created, added to the command stack we setup for our editing domain, and executed. All editor state management like mark dirty, save, undo/redo is handled for you by EMF.

Credit

Most of the code was written by Eclipse EMF and Forms people, I just merged it to get a basic forms editor working. There may be easier and/or cleaner ways to get this done. I encourage you to add to this recipe or correct me if I got something wrong.


Related Recipes

None so far.


References

  • Article on Eclipse Forms: [4]
  • Example for a generic EMF Eclipse Forms editor : [5]
  • EMF.Edit framework overview : [6]

EMF.Edit Recipes

Recipe: Custom Labels

Problem

You want to provide customized text for an object's label in an EMF-generated editor. Further, if the customized text is derived from other objects and their attributes, you want the label to be updated whenever those attribute values may change.

Solution

  • Modify the generated ItemProvider.getText() method to compose the value you wish.
  • If your label's value depends on other objects and their attributes:
    • Take care to test for null references and values.
    • Create a class that implements the org.eclipse.emf.edit.provider.INotifyChangedListener interface
      • An inner class of the target item provider class works well.
      • In the notifyChanged() method, check whether the notification is for one of the attributes upon which your custom label depends. If it is, then fire a new notification event whose notifier/element is your target object. Take care to prevent the new event from causing infinite recursion, remove the listener before firing the event, and add it back afterwards.
    • In the target item provider's constructor, create an instance of the above listener and add it to the adapter factory.
    • Override the dispose() method to remove the listener from the adapter factory.
  • If your label's value depends on attributes or references in the target object, ensure the notifyChanged() method in the target item provider fires a notification event for these cases. The ViewerNotification for these events need to indicate label updating.

Example

In this example, assume you want to customize the label for an instance of EMF class A. The value is derived from an attribute, name, of another class B that is referenced from A.

Modify the getText() method in A's item provider:

public class AItemProvider
	extends OperandItemProvider
	implements	
		IEditingDomainItemProvider,	
		IStructuredItemContentProvider,	
		ITreeItemContentProvider,	
		IItemLabelProvider,	
		IItemPropertySource {

...
	/**
	 * This returns the label text for the adapted class.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated NOT
	 */
	public String getText(Object object) {
		A aObj = (A) object;
		String ref = aObj.getB() == null ? "???" : aObj.getB().getName();
		ref = ref == null ? "???" : ref;
		return getString("_UI_A_type")+" -> "+ref; //$NON-NLS-1$
	}

Create an inner class to listen for change modifications:

	class ChangeListener implements INotifyChangedListener {
		public void notifyChanged(Notification notification) {
			
			if(notification.getNotifier() != null &&  getTarget() != null && notification.getNotifier() == ((A) getTarget()).getB()) {
				((IChangeNotifier) getAdapterFactory()).removeListener(this);
				fireNotifyChanged(new ViewerNotification(notification, getTarget(), false, true));
				((IChangeNotifier) getAdapterFactory()).addListener(this);
			}
		}
	}

The above implementation checks that the modified object is the same instance that the target object references (as opposed to just any instance of B).

The new VewerNotification identifies the instance of A as the changed element, and the true argument signals the change requires a label update.

The removeListener() call prevents infinite recursion from the following fireNotifyChanged(). After the event is fired, the listener is added back to the adapter factory.

Instantiate the listener and add it to the adapter factory in the item provider's constructor:

public class AItemProvider
	extends OperandItemProvider
	implements	
		IEditingDomainItemProvider,	
		IStructuredItemContentProvider,	
		ITreeItemContentProvider,	
		IItemLabelProvider,	
		IItemPropertySource {

	private ChangeListener changeListener;

	/**
	 * This constructs an instance from a factory and a notifier.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated NOT
	 */
	public AItemProvider(AdapterFactory adapterFactory) {
		super(adapterFactory);
		if(adapterFactory instanceof IChangeNotifier) {
			IChangeNotifier cn = (IChangeNotifier) adapterFactory;
			changeListener = new ChangeListener();
			cn.addListener(changeListener);
		}
	}
...
}

Override the dispose() method:

public class AItemProvider
	extends OperandItemProvider
	implements	
		IEditingDomainItemProvider,	
		IStructuredItemContentProvider,	
		ITreeItemContentProvider,	
		IItemLabelProvider,	
		IItemPropertySource {
...
	/* (non-Javadoc)
	 * @see org.eclipse.emf.edit.provider.ItemProviderAdapter#dispose()
	 */
	public void dispose() {
		super.dispose();
		if(changeListener != null) {
			((IChangeNotifier)getAdapterFactory()).removeListener(changeListener);
		}
	}
...
}

Modify the target object's item provider notifyChanged() to fire a change event when the reference to B changes:

	/**
	 * This handles model notifications by calling {@link #updateChildren} to update any cached
	 * children and by creating a viewer notification, which it passes to {@link #fireNotifyChanged}.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated NOT
	 */
	public void notifyChanged(Notification notification) {
		updateChildren(notification);
		switch (notification.getFeatureID(A.class)) {
		case FooPackage.A__B:
			fireNotifyChanged(new ViewerNotification(notification, notification
					.getNotifier(), false, true));
			return;
		}
		super.notifyChanged(notification);
	}

Note that the last parameter for the ViewerNotification constructor indicates the label is to be updated.

Related Recipes

None so far.

References

None so far.