Dr. Maximilian Koegel is deeply involved with the Eclipse community. He is project lead and committer on several open-source projects including EMF Cloud, JSON Forms and …
EMF ItemProvider Magic
January 29, 2013 | 4 min ReadIn EMF you can generate Java Classes not only for your entity objects, but also classes that support viewing and editing these entities. ItemProviders are generated into the EMF edit plugin for each entity class and implement label and content providers. To implement a tree view of your entities you can use the generated label and content providers as follows.
ComposedAdapterFactory composedAdapterFactory =
new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
AdapterFactoryLabelProvider labelProvider =
new AdapterFactoryLabelProvider(composedAdapterFactory);
AdapterFactoryContentProvider contentProvider =
new AdapterFactoryContentProvider(composedAdapterFactory);
treeViewer.setLabelProvider(labelProvider);
treeViewer.setContentProvider(contentProvider);
treeViewer.setInput(myInputEObject);
We instantiate a composed adapter factory that contains all adapter factories from the adapter factory registry. The composed adapter factory delegates factories to its children based on the type of the argument to adapt. With the factory as parameter we can create a label and a content provider and pass it to the tree view. After setting an input EObject the tree viewer will display the EObject and its children using labels provided by the generated label providers and children according to the generated content providers. For details see our EMF Tutorial.
The big advantage of the generated label and content providers is that they will automatically update their viewers if a label or the children change - without needing additional code. This works on the assumption that a change on the EObject might change its label and a change on its containment references that are shown as children will change its children in the tree. While this assumption works for most use cases, it does not work if the information displayed in a label depends on information from another EObject.
Let us assume we have an EObject of EClass Proxy with an EReference to an EObject of EClass RealItem. For a Proxy we want to display the name of the RealItem that it references (if any). If we just use the generated LabelProvider, it will not update its viewers for the Proxy objects when a referenced RealItem changes its name. With the following code however, you can make the item provider of Proxy notify its viewers even when only the name of its referenced RealItem changes.
@Override
public String getText(final Object object) {
final Proxy proxy = (Proxy) object;
RealItem realItem = proxy.getRealItem();
if (realItem == null) {
// here it could also make sense to register for changes on the
// realItem reference to install the same adapter as below if
// a realItem is added later to the proxy
return "Empty Proxy";
}
RealItemItemProvider realItemItemprovider = getRealItemItemProvider(
proxy, realItem);
return realItemItemprovider.getText(realItem);
}
private Map> realItemMap = new HashMap>();
private RealItemItemProvider getRealItemItemProvider(final Proxy proxy,
RealItem realItem) {
RealItemItemProvider realItemItemprovider = (RealItemItemProvider)
adapterFactory.adapt(realItem, RealItemItemProvider.class);
if (!realItemMap.containsKey(realItem)) {
realItemMap.put(realItem, new HashSet());
}
if (!realItemMap.get(realItem).contains(proxy)) {
realItemItemprovider.addListener(new INotifyChangedListener() {
@Override
public void notifyChanged(Notification notification) {
if (notification.getNotifier()==realItem) {
fireNotifyChanged(new ViewerNotification(notification,
proxy, false, true));
}
}
});
realItemMap.get(realItem).add(proxy);
}
return realItemItemprovider;
}
In the getText() method we first check if the proxy actually has a real item. If not, we return a static String. At this point we could also register an EAdapter to listen to changes on the realItem EReference. If we have a real item for the proxy, we will retrieve an ItemProvider for it. For the sake of simplicity, the above code makes the assumption that RealItem and Proxy share the same AdapterFactory - that is, that they are in the same EPackage. We simply install a listener on the item provider of the RealItem. This listener is triggered whenever the item provider wants to update its viewers. We pass on the notification, if it is about our current real item, but replace the notifier with the proxy, so that viewers of the proxy’s item provider know which item to update. We only need to install the listener on the item provider once for each of the Proxy and RealItem instances.
The life cycle of the item providers are controlled by the life cycle of their adapter factory. When the adapter factory is removed, all its item providers are removed as well.
In conclusion, even if the generated item providers are not a perfect fit for your problem, they are a good starting point and can be reused to craft a custom solution for your requirements. By the way, don´t forget to add a @generated NOT annotation to your getText() method, otherwise it will disappear with the next code regeneration of EMF…;)