oktober 7, 2010

Power of Lookup

Just as Nodes are one very important building block of Netbeans RCP applications Lookup are almost even more important. At first the concept can be confusing and you might not know what usage you have of them. I only have a basic knowledge of Lookups and had one of those A-ha! moments a few days ago. As I'm still learning Netbeans I probably missunderstand things and everything written in this post might not be the correct way to do things.

Scenario

Lets look at a scenario where Lookup can help us a lot to create a much better design of our code. Lets say that you are building an application that only consists of one module in a suite. You have a menu (maybe a JXTaskpane shown earlier in my blog) and when you select one item the content in a Topcomponent that contains an Outlineview showing nodes are changes to a different set of nodes. How would the communication between the menu and the Outlineview be done?

The easiest way would seem to be a call to a public method like this: WindowManager.getDefault.findTopComponent("YourTopComponent").switchViewToB(), WindowManager.getDefault.findTopComponent("YourTopComponent").switchViewToC(), WindowManager.getDefault.findTopComponent("YourTopComponent").switchViewToD(). And this method would take care of updating the nodes. This is a working solution and would probably work fine for a one menu with one topcomponent application. But what if you want to bring up another Topcomponent when you switch to D? Where should you do this? In the menu action calling the other topcomponent? Or in the switchViewToD() method?

Adding this to the SwitchViewToD() method also works but now you need to make sure that you are showing and hiding a bunch of topcomponents from the method. When you add one more TopComponent you need to go trough all others and make sure that they are opened and closed correctly. Starts to feel like a small nightmare doesn't it?

Now lets say that your boss wants to earn more money. He wants to sell an enterprise edition of your application that only should be distributed to customers paying extra. This enterprise application contains a few more panels that should be opened up on certain views. The best way to implement this part is by creating an Enterprise module to your suite. But now things starts to get complicated..  How will you bring up the extra panels? You can't have calls to TopComponents that only exists in this module since this would cause Nullpointers for customers without the Enterprise module. You could add if statements that checks if the enterprise module is installed and only call the TopComponents then..   or maybe...

No!  stop thinking now! This has gone the completely wrong path from the beginning. There is a much much better ways than creating dependency hell between the TopComponents. If we do this correctly TopComponentA would not know that TopComponentB even exists. This is something called decoupling in the software development world.

The solution is by using Lookup

So how could this Lookup thing help us? As always.. lets dive into some source code.

First of all I want to create some kind of object that tells my TopComponents what view I want to switch to. This object will then be sent out into the Lookup. So let's create a very basic class.

public class TaskSwitch {
    private final GUITask task;
    public TaskSwitch(GUITask task) {
        this.task = task;
    }
    public GUITask getTask() {
        return task;
    }
}

What is GUITask? It's an enum and it's created like this:

public enum GUITask {
    myFirstView,
    mySecondView,
    myThirdView
}

Ok.. so now we a class and an enum. How can we use them? First by adding this fields to our menu (I'm using JXTaskPane in a separate TopComponent for switching views).


private InstanceContent taskContent = new InstanceContent();
private Lookup lookup = new AbstractLookup(taskContent);
private TaskSwitch taskSwitch;

This will create a Lookup based on InstanceContent which is a builtin class in Netbeans that provides the Lookup with content where we can add instances of our classes.

InstanceContent works as a list and instances can be added and removed but there exists now removeAll() method so we need to keep track of our instance added to it. For this I created a small method that will remove the previous TaskSwitch instance from the content and add the new one.


private void switchTask(TaskSwitch taskSwitch) {
    try {
        taskContent.remove(this.taskSwitch);
    } catch (Exception e) {
    }
    this.taskSwitch = taskSwitch;
    taskContent.add(this.taskSwitch);
}

Now I can use this method in my menu actions to add a new TaskSwitch instance to my Lookup.

switchTask(new TaskSwitch(GUITask.myFirstView));

We also need to make our Menu to implement Lookup.Provider and a getLookup method that returns our lookup field.

public final class MyMenu extends TopComponent implements Lookup.Provider
public Lookup getLookup() {
    return lookup;
}

What have we done? We have created an InstanceContent instance which is a list of instances of any type. We have created a Lookup that monitors this List and acts when something has been added to it. Each time a menu item is selected I'm adding a new Instance of my TaskSwitch class that contains an enum value of the view I want to have.

Now lets switch to my TopComponent where I have my OutlineView.

[sourcecode language="java"]public final class MyOutlineTopComponent implements LookupListener
private final Result taskLookupResult;
[/sourcecode]

Here Im making my TopComponent to be a LookupListener. Sounds like a good idea. I want to listen to changes in the Lookup.
In my constructor I have to tell what I want to listen to.

taskLookupResult = WindowManager.getDefault().findTopComponent("MyMenuTopComponent").getLookup().lookupResult(TaskSwitch.class);
taskLookupResult.addLookupListener(this);

Ok.. now I have told what I want to listen to all changes where instances of TaskSwitch is involved into.

Now we need to do something when we get notified about a change. This is done in a method called resultChanged that contains a collection of all the changes. It can ben one or it can be many.

[sourcecode language="java"][/sourcecode]

So each time somethings happens that involves an instance of TaskSwitch.class the resultChanged method will be called. We will get a collection of all the instances that has something to do with TaskSwitch.class. In our case there should only be one. I prefer to do an extra type check by checking if Object is an instance of TaskSwitch and if it is I'm calling a method called changeView();

And this is how my changeView() method looks like:

@Override
    public void resultChanged(LookupEvent le) {
        Lookup.Result r = (Lookup.Result) le.getSource();
        Collection c = r.allInstances();

        if (!c.isEmpty()) {
            for (Object object : c) {
                if (object instanceof TaskSwitch) {
                    changeView((TaskSwitch) object);
                }
           }
      }
}

And we are done. Now I have switched the content in my TopComponent without the menu knowing where the changes occur. I can now do the same thing in every other TopComponent in my suite and it doesn't even have to be in the same Module.

This is just an example of what can be done. Other usages could be if you have a background thread that checks for new data from a web service that needs to be added to your node views. Each time the background thread finds new data it could send the data over the lookup and the classes that are interested if there is new data can pick it up and display it, for example refresh the nodes list without knowing anything about the background thread and how the new data was added into the Lookup. It just knows the data is there and what to do with it.

And if you want to show or hide certain TopComponents depending on which view that is selected just call this.open() and this.close() in the changeView()/resultChanged method. Just make sure that you have registered your lookup in the constructor and not in componentOpened() since it would never open the first time. Also don't deregister the lookup in componentClosed().

So this is one of the many possibilities of using Lookups in your Netbeans RCP application.