Over a million developers have joined DZone.

Selector Based Lookup

· Java Zone

Discover how AppDynamics steps in to upgrade your performance game and prevent your enterprise from these top 10 Java performance problems, brought to you in partnership with AppDynamics.

You are using the NetBeans Platform or the NetBeans Lookup API? No..? Don't worry, the proposed method can be adapted to different contexts, keep on reading. With this method you can really decouple the actions in your application, together with their enablement. Independently and with low coupling, modules can contribute new action enablers (selectors) and/or new contextually enabled actions.

A an example application and a ready to use NetBeans module are provided.

summary picture not found

 

Context and Problem

Short presentation of lookups? (skip if you know)

This article is illustrated using NetBeans “Lookups”. If you have never heard of lookups or don't really know them, this short section will help you to understand the article.

A “Lookup” is basically a generic container that can be listened to. On a lookup, one can register a listener for objects implementing any interface of their choice. The listeners are then notified when some objects are added in or removed from the lookup. If you are used to Service Oriented Architectures, you can also see it as a generic service repository (that can contain any kind object).

In NetBeans, lookups are used for many things. A typical use is for selection management. For components like lists and trees, the current selection is exposed as a lookup. It is thus possible to monitor the selection and enable or disable some actions accordingly. These actions are named context-aware actions.

NetBeans also has a global lookup that dynamically changes its content to reflect the content of the lookup of the focused component. Global context-aware actions are most of the time listening to this global lookup.

For another introduction on lookups, you can also have a look at these good introductory slides. More details are also available on the NetBeans Platform Learning Trail.

Where the method applies?

The presented method can be seen as an architectural pattern that can be adapted in many contexts where modularity and adaptability are important. It is presented here in the context of the Lookup API of the NetBeans Platform. The presented implementation is readily usable in applications based on the NetBeans Platform (or applications using the Lookup API in a plain old Java application).

What it solves?

Imagine you have a list or tree and, depending on the selection in this list, you want different actions to be enabled. This is pretty simple and can be solved with different solutions, such as using context aware actions or other solutions like the one proposed in one of Geertjan's posts. Now, you want to increase modularity (and the previous methods are not sufficient anymore). Imagine you need the following properties:

  • any module can add a new action,
  • any module can contribute to the “enabling strategy” of any (existing) action type,
  • any module can add a new action that follows any existing “enabling strategy”.

You want to achieve this without direct dependencies between particular “actions” and “enabling strategies”.

Example Use Cases (skip if you are in a hurry)

If you are in a hurry, you can try to skip directly to the proposed solution in section “Approach”.

Original use case: actions in OMiSCID Gui

OMiSCID Gui is a graphical application to monitor and interact with services running in an intelligent environment. The environment contains services like cameras, microphones, light controllers, text to speech services, loud speakers, etc. This set of services depends on the environment and is evolving over time and is expected to grow in number and variety.

The core functionality of OMiSCID Gui is to list the running services and their properties. From this list the users can get a contextual menu. The actions present in this menu depend on both the currently selected services and the modules installed in the application.

Here is a leading example with cameras. We want an action that will take a camera service and open a panel with a live display of this camera. We also want another action that will take a camera and encodes it to as a video file. We can imagine more actions working with cameras. Each of the above actions needs to say “I am enabled when a Service with name 'Camera' is selected in the tree”. Having each action specify this leads to code duplication… but it might be acceptable.

The real challenge appears when you see that someone creates a “VideoFileReaderService” and another person created a “ScreenCaptureService”. These two new kind of services can, as a “Camera” service, provide a stream of images (although they are not exactly cameras). You want all your above actions to work not only with “Camera” services but also with these two new kind of services. Obviously, you want to avoid modifying all these actions with yet more redundant code.

Our method makes it possible to avoid having to modify all actions when a new condition on the selection should enable the actions. The concern of deciding what kind of actions should be enabled (based on the selection) is allocated to objects called “Selectors”. “Selectors” produce some “Tasks” and the actions are actually context-aware actions using these “Tasks” as context.

Sample illustrative project

To provide some simplified code using the method, a simple application is available. This application just displays a list of customers, each having different attributes including a name and a place where he or she lives. The customers are represented by instances of the Customer class. Based on the user selection in this list, we want to enable or disable some actions. The code is actually based on the example of Geertjan's post on modularity.

Quite basically, we first want a first action to be enabled when one and only one Customer is selected (nothing special for this action). This is classical case of context-aware action (formerly CookieAction) which uses the default lookup to enable/disable itself automatically depending on the selection. Such an action can be easily created in NetBeans using the action wizard and just registers the action in the layer.xml file and requires you to implement an ActionListener.

Now we want to go further and, as with OMiSCID Gui, we want to clearly decouple the “when” and the “how”. More precisely, we decouple the “when a message can be produced based on the selection of customers“ from the “how it should be processed” (e.g. showing a message box, writing to a log file, etc.). The method hereafter makes it possible.

Approach: Selector Based Lookup

Our approach consists in taking a base lookup (e.g. the lookup that would be associated with a list or with a tree) and in enriching it with derived content. The enriching lookup is what we call a “Selector Based Lookup”. The lookup that will eventually be used by the context-aware actions is the union of the base lookup. The union lookup contains both the content of the base lookup and the derived content.

overview not found

The Selector Based Lookup is the key to extensibility in our approach. Any module can contribute new “Selectors” to the Selector Based Lookup. Each selector is responsible for interpreting the current selection (the content of the base lookup) and possibly creating derived objects that we name “Tasks” as a convention. At any instant, the content of the selector based lookup is just the fusion of the tasks returned by all selectors.

Taking a step back, we can see the selectors (or the selector based lookup) as an adaptation layer. This layer is responsible for adapting/transforming/converting the base lookup to a form that can be interpreted by actions. Materializing this adaptation layer makes it possible to make it highly extensible as in the proposed approach.

As shown below, in the example application, the base lookup is the selection in the customer list. The union lookup is associated to the list component so, when the list has focus, the global actions end up automatically listening to the content of the union lookup. Code examples for different elements (particularly selectors) are given in next section.

overview-example not found

 

Code Fragments

The complete code of the example application is available. It is split in many modules to illustrate the fact that new tasks, actions and selectors can be added easily. This example is really simple and features:

  • a single kind of Task: MessageTask which just encapsulate a java.lang.String;
  • two context aware actions for this task:
    • the MessagesAsDialog action that pops up a dialog displaying all messages (from all current MessageTasks),
    • the WriteMessagesToStderr action that writes the messages to the standard error output.
  • two selectors producing such tasks:
    • a generic “DefaultHelloMessage” selector that just creates a MessageTask saying hello to any selected Customer,
    • a more specific “SalutMessage” selector creating an additional message by reasoning on the selected Customer's city.

Code for tasks, selectors and actions

No constraints are put on the tasks, here it is a plain Java object:

public class MessageTask {
    private final String text;
    public MessageTask(String text) {
        this.text = text;
    }
    public String getText() {
        return text;
    }
}
            
Creating a new selector is really simple using the convenient AbstractSelector base class.
public class DefaultHelloMessageSelector extends AbstractSelector<Customer> {
    public DefaultHelloMessageSelector() {
        super(Customer.class);
    }
    protected void getTasks(ArrayList result, Collection<Customer> context) {
        for (Customer customer : context) {
            result.add(new MessageTask("From DefaultHelloMessageSelector:    Hi "
                                       + customer.getCustomerName()));
        }
    }
}
            
Then the selector needs to be register in the layer.xml file. The location where to register it depends on where our “Customer list” component is configured to look. Here it is /Demo/Selectors:
<filesystem>
    <folder name="Demo">
        <folder name="Selectors">
            <file name="fr-prima-...-DefaultHelloMessageSelector.instance"/> </folder> </folder> </filesystem>
Actions are just normal context-aware actions. They are just using tasks as context, here MessageTask. As other actions, they must also be register appropriately in the layer.xml file. Here is the action that pops up a dialog:
public final class MessagesAsDialog implements ActionListener {
    private final List<MessageTask> context;
    public MessagesAsDialog(List<MessageTask> context) {
        this.context = context;
    }
    public void actionPerformed(ActionEvent ev) {
        StringBuilder msg = new StringBuilder();
        for (MessageTask messageTask : context) {
            msg.append(messageTask.getText()).append("\n");
        }
        Message d = new Message("MessageAsDialog:\n\n" + msg);
        DialogDisplayer.getDefault().notify(d);
    }
}
            

Code for when you want to create a Selector Based Lookup

Using the provided SelectorBasedLookup netbeans module, a selector based lookup is not only easily to extend but it is also easy to create. In the example, it is done by this code:

 Lookup base = ExplorerUtils.createLookup(em, getActionMap());
 // with classical lookup, just:
 //   associateLookup(base);

 // with Selector Based Lookup:
 Lookup sbl = SelectorBasedLookup.createLookup(base, "Demo/Selectors");
 associateLookup(new ProxyLookup(base, sbl));
            

Conclusions

This article has proposed an architectural pattern for fully decoupling actions and “action enabling strategies”. This pattern is illustrated with the NetBeans Platform Lookup API. A ready to use NetBeans module is available if you want to apply this method with minimal implementation effort.

Any questions? Something not clear? Any comments?

 

The Java Zone is brought to you in partnership with AppDynamics. AppDynamics helps you gain the fundamentals behind application performance, and implement best practices so you can proactively analyze and act on performance problems as they arise, and more specifically with your Java applications. Start a Free Trial.

Topics:

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}