Pluggable Swing View Component

This may appear to be a standard Swing tree component:

However, it's not. It is pluggable. Each of the root nodes shown above comes from a different module, while the view component is loosely coupled from its data sources:

The view module needs only the GenericNodeProvider module, because it will load implementations of the GenericNodeProvider interface via the Java extension mechanism, i.e., via META-INF/service registration entries.

So, the starting point is to create a module containing a GenericNodeProvider:

package my.node.provider;

import java.util.List;

public interface GenericNodeProvider {
    List<String> getData();
}

Then, in the, in this case, three provider modules, we depend on the module providing the above generic provider, by implementing the interface, e.g:

package org.provider.city;

import java.util.ArrayList;
import java.util.List;
import my.node.provider.GenericNodeProvider;
import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service=GenericNodeProvider.class)
public class City implements GenericNodeProvider{

    @Override
    public List<String> getData() {
        List list = new ArrayList();
        list.add("Toronto");
        list.add("San Francisco");
        list.add("London");
        return list;
    }

    @Override
    public String toString() {
        return "City";
    }

}

The @ServiceProvider annotation registers the provider in the META-INF/services folder.

Next, we create a TopComponent containing a BeanTreeView and an ExplorerManager, as always in NetBeans Platform applications. This module, i.e., the view module, depends on the module containing the GenericNodeProvider, but not on any of the other modules.

Then, here's the code. Note especially the usage of Lookup.getDefault to retrieve the registered providers:

private ExplorerManager em = new ExplorerManager();

public PluggableNodeExplorerTopComponent() {

    initComponents();

    setName(NbBundle.getMessage(PluggableNodeExplorerTopComponent.class,
            "CTL_PluggableNodeExplorerTopComponent"));
    setToolTipText(NbBundle.getMessage(PluggableNodeExplorerTopComponent.class,
            "HINT_PluggableNodeExplorerTopComponent"));

    setLayout(new BorderLayout());

    BeanTreeView btv = new BeanTreeView();
    btv.setRootVisible(false);

    em.setRootContext(new AbstractNode(
            Children.create(new FirstLevelChildFactory(), true)));

    add(btv, BorderLayout.CENTER);

    associateLookup(ExplorerUtils.createLookup(em, getActionMap()));

}

private class FirstLevelChildFactory extends ChildFactory<GenericNodeProvider> {

    @Override
    protected boolean createKeys(List list) {
        list.addAll(Lookup.getDefault().lookupAll(GenericNodeProvider.class));
        return true;
    }

    @Override
    protected Node createNodeForKey(GenericNodeProvider key) {
        Node node = new AbstractNode(
                Children.create(new SecondLevelChildFactory(key.getData()), true));
        node.setDisplayName(key.toString());
        return node;
    }

}

private static class SecondLevelChildFactory extends ChildFactory<String> {

    private final List key;

    public SecondLevelChildFactory(List key) {
        this.key = key;
    }

    @Override
    protected boolean createKeys(List<String> list) {
        list.addAll(key);
        return true;
    }

    @Override
    protected Node createNodeForKey(String key) {
        Node node = new AbstractNode(Children.LEAF);
        node.setDisplayName(key);
        return node;
    }

}

And that's all.

Comments:

This feels very similar to the registration style of the Services Tab being loosely coupled from registered root notes using @ServicesTabNodeRegistration .

Posted by Ernest on October 06, 2011 at 01:31 PM PDT #

Would the same thing be possible for a menu?

Posted by Henk on October 07, 2011 at 01:02 AM PDT #

Henk, menus are provided by actions registered in the "Actions" folder. So, this functionality already works out of the box for Actions and there's nothing you need to do for that. If you live somewhere in the Netherlands, let me know, and I'll come and introduce you to the NetBeans Platform via a few demos.

Posted by Geertjan on October 07, 2011 at 03:15 AM PDT #

SecondLevelChildFactory should take a GenericNodeProvider, not its List<String> data - so that the data is retrieved only if and when the node is expanded.

Posted by Jesse Glick on October 11, 2011 at 05:02 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today