2010-02-14

Using Adapters in Handlers to control functionality

In my last blog post about the use of Dependency injection in e4, I used adapters in handlers with functional interfaces to control the enablement of functionality in an application. By request, here is an extended version of that with some additional explanations. [Incidentally, this was proposed as a talk for EclipseCon ´10, but unfortunately got rejected...]

Note that this is only for users in larger applications - if you have a command with a single handler and simple expressions, then this is not needed at all...

Why?

When you implement a larger Eclipse RCP based application, you can run into a number of problems with handlers and menus as you add more and more objects to the domain model of your application model and more and more commands for the objects in the UI.

In order to control the visibility of menu items in menus - most notable the popup menu - you have the visibleWhen construct for the commands in the menu. The used expression often is an or-ed together version of all the activeWhen expressions for the handlers for the command in question. E.g. if you have a command C with two handlers h1 and h2, each with the activeWhen expression aw1 and aw2, then you often want the command to use the visibleWhen expression (or aw1 aw2).

But... as new objects can be added by new plug-ins and the number of commands are enlarged for the existing objects over time, how do you maintain the visibleWhen expression?

Also, very often, you have a lot of common implementation for a specific command in the different handlers for the command - this is not so surprising as the command is an abstract description of some functionality - e.g. "cut", "print", "connect", etc - and some common code must be expected no matter which object that is operated on. E.g. consider the "print" command: a lot of the code for selection of printer and print options will be common no matter which object is printed.

Finally, very often, you really don't want to introduce all the different UI functionality in the domain model itself - maybe for principle reasons or maybe because you don't "own" the code of the model and cannot modify it. You might therefore want to use adapters to handle the functionality in the cases where you cannot change model and direct methods where you can change the model.

For these different reasons I often use a common design pattern in my applications that incorporate functional interfaces for the different commands, adapters for the implementations and some very simple handlers on top - and not least some very easy-to-maintain menu and handler expressions.

In the following, I have used a very simple RCP application as the example with just a single view that shows a table with a number of objects. The objects are Contacts - with name and address -, Customers - extends Contact with a segment - and Segment - with just a segment. [Yes, we could have used a set of model interfaces in this example as well, as we would if we had just used EMF for the model, but in many domain models, that is just not possible...] The table have a popup menu with just a single command "Show Segment" that should be shown for all Customers and Segments, but not for the Contacts. The command must show the segment of the selected object if relevant.

The complete test application can be found here.

How...

First, I use a functional interface for each of the domain model commands in the application. These interfaces have a number of methods used by the command handler to execute the wanted operation. Often this is just a single method, but there can be multiple methods for more complex scenarios.

In this case, the functional interface could be as follows:

public interface IDoShowSegment {
  void doShowSegment(Shell shell);
}

The doShowSegment method could also have been getSegmentName...

In my example, the Customer class implements the interface directly, whereas the Segment class does not, but have an adapter factory instead. The relevant part of the Command class looks as follows:

public class Customer extends Contact implements IDoShowSegment {
  // ...

  @Override
  public void doShowSegment(Shell shell) {
    MessageDialog.openInformation(shell, "The customer segment is...", getSegment());
  }
}

And the relevant part of the adapter factory looks as follows:

public class SegmentAdapterFactory implements IAdapterFactory {
  @Override
  public Object getAdapter(Object adaptableObject, Class adapterType) {
    if (!(adaptableObject instanceof Segment)) {
      return null;
    }
    final Segment s = (Segment) adaptableObject;
    if (adapterType == IDoShowSegment.class) {
      return new IDoShowSegment() {
        @Override
        public void doShowSegment(Shell shell) {
          MessageDialog.openInformation(shell, "The segment is...", s.getSegment());
        }
      };
    }
    return null;
  }

  // ...
}

The factory is installed using the adapters extension point

<extension point="org.eclipse.core.runtime.adapters">
  <factory
      adaptableType="com.rcpcompany.ex.adapterhandlers.model.Segment"
      class="com.rcpcompany.ex.adapterhandlers.adapters.SegmentAdapterFactory">
    <adapter type="com.rcpcompany.ex.adapterhandlers.functionality.IDoShowSegment" />
  </factory>
</extension>

[The used functional interface isn't perfect, as it forces the adapter factory to construct a new object for each call to the getAdapter method. It can be helped by changing the method to doShowSegment(Object,Segment), but... for the sake of simplicity...]

To invoke the "Show Segment" functionality for a specify object, we when have to do the following:

final IDoShowSegment showSegment = (IDoShowSegment)Platform.getAdapterManager().getAdapter(element, IDoShowSegment.class);
if (showSegment != null) {
  final Shell shell = ...;
  showSegment.doShowSegment(shell);
}

Note that we here just ignore the case where the object cannot be adapted to our functional interface.... Also note that the getAdapter method of the adapter manager automatically checks if 1) the object directly implements the interface, 2) the object implements the IAdaptable interface - and then uses this, or 3) if an adapter factory is installed for the interface and object class.

In the context of the handler, this is as follows - with the obvious declarations for the handler.

public class ShowSegmentHandler extends AbstractHandler implements IHandler {
  @Override
  public Object execute(ExecutionEvent event) throws ExecutionException {
    final ISelection selection = HandlerUtil.getCurrentSelectionChecked(event);
    if (!(selection instanceof IStructuredSelection)) {
      return null;
    }
    final IStructuredSelection ss = (IStructuredSelection) selection;
    final Object element = ss.getFirstElement();
    final IDoShowSegment showSegment = (IDoShowSegment) Platform.getAdapterManager().getAdapter(element,
        IDoShowSegment.class);
    if (showSegment == null) {
      return null;
    }
    final Shell shell = HandlerUtil.getActiveShellChecked(event);
    showSegment.doShowSegment(shell);
    return null;
  }
}

With the above code and a menus extension, the "Show Segment" command is shown as enabled in the popup menu for all items - even the Contact objects.

To make the handler only active when the selection is an object that supports the functional interface, use the following handlers declaration:

<extension point="org.eclipse.ui.handlers">
  <handler
        class="com.rcpcompany.ex.adapterhandlers.handlers.ShowSegmentHandler"
        commandId="com.rcpcompany.ex.adapterhandlers.commands.ShowSegment">
    <activeWhen>
      <iterate ifEmpty="false" operator="and">
        <adapt type="com.rcpcompany.ex.adapterhandlers.functionality.IDoShowSegment" />
      </iterate>
    </activeWhen>
  </handler>
</extension>

With this declaration the command is only enabled in the popup menu when relevant, though it is still shown for the Contact objects.

To show the command only when an active handler exists, use the following visibleWhen expression:

<extension point="org.eclipse.ui.menus">
  <menuContribution locationURI="popup:org.eclipse.ui.popup.any">
    <command commandId="com.rcpcompany.ex.adapterhandlers.commands.ShowSegment" style="push">
      <visibleWhen checkEnabled="false">
        <iterate ifEmpty="false" operator="and">
          <adapt type="com.rcpcompany.ex.adapterhandlers.functionality.IDoShowSegment" />
        </iterate>
      </visibleWhen>
    </command>
  </menuContribution>
</extension>

Note that this only use the activeWhen expression to control the visibility of the command in the menu. This is slightly different from the visibleWhen with checkEnabled=true, which is based on the enabledWhen expression.

The End Result

The end result is that if you want to implement the command for a new or existing object you must either extend the object with the functional interface or add an adapter for the object class to the functional interface - I usually prefer the later.

To find the objects that supports the command, just look for implementations of the interface - which by the way I normally name "IDo<command-id>" - here IDoShowSegment.

Useful? In larger applications, yes - in small applications, this is probably like shooting flies with canons. Is it worth the added code and the redirection in the handler? That can only be decided by you...
Post a Comment