Over a million developers have joined DZone.

NetBeans Platform Idioms: Pluggable TopComponent (Part 4)

· Java Zone

Navigate the Maze of the End-User Experience and pick up this APM Essential guide, brought to you in partnership with CA Technologies

Let's go on with the analysis of the Pluggable TopComponent idiom. Today we're going to look at the main class, EnhancedTopComponent, that acts as the connection point between the idiom participants and the NetBeans Platform runtime, as well as a few more reusable roles.

This is the fourth part of this series. I'll make many references to portions of the API described in the previous  parts, that you should read before going on: 1, 2, 3. I'm also happy to say that the related code has been moved to the OpenBlueSky project at Kenai, and most of it is ready for inclusion in form of Maven artifacts.

The core of EnhancedTopComponent is very simple: each instance contains an instance of RoleSet (that has been described in previous articles) and that gets populated from the XML FileSystem (layer.xml) - for instance with a XML fragment such as:

<filesystem>
<folder name="Roles">
<folder name="it.tidalwave.geo.explorer.GeoExplorerPresentation">
<file name="DataLoader.instance">
<attr name="instanceClass" stringvalue="it.tidalwave.geo.explorer.impl.role.DefaultGeoCoderDataLoader"/>
</file>
<file name="SelectionStrategy.instance">
<attr name="instanceClass" stringvalue="it.tidalwave.geo.explorer.impl.role.AutoSelectionStrategy"/>
</file>
...
<file name="NodeView.instance">
<attr name="instanceCreate" methodvalue="it.tidalwave.netbeans.role.util.BeanFactory.createInstance"/>
<attr name="class" stringvalue="it.tidalwave.netbeans.explorer.view.EnhancedBeanTreeView"/>
<attr name="rootVisible" boolvalue="false"/>
<attr name="dropTarget" boolvalue="false"/>
<attr name="dragSource" boolvalue="false"/>
</file>
</folder>
</folder>
...
<filesystem>

It's an excerpt of the real configuration of the forceTen project, and as you can see the roles can be instantiated in multiple ways (in the most sophisticated case some property values are initialized thanks to the BeanFactory utility that has been described in the previous post). I'm using a naming convention where the roles are instantiated from the path "/Roles/<topComponentId>".

package it.tidalwave.netbeans.windows;

public abstract class EnhancedTopComponent extends TopComponent
{
@Nonnull
private final Lookup lookup;

private final RoleSet roleSet = new RoleSet();

@CheckForNull
private final String id;

public EnhancedTopComponent()
{
this(null);
}

public EnhancedTopComponent (@CheckForNull final String id)
{
this.id = id;
lookup = new ProxyLookup(roleSet.getLookup(),
Lookups.fixed(this), // needed by roles
super.getLookup());
setLayout(new BorderLayout());
}

public void addRole (@Nonnull final Object role)
{
roleSet.addRole(role);
}

public void removerole (@Nonnull final Object role)
{
roleSet.removeRole(role);
}

private void installRoles()
{
final String path = "/Roles/" + ((id != null) ? id : preferredID());
logger.finer(">>>> looking up default roles from path %s...", path);

roleSet.setInjectedLookup(getLookup()); // a subclass might have enhanced it
roleSet.setStaticRoles(Lookups.forPath(path).lookupAll(Object.class));
roleSet.initialize();

final GUIBuilder guiBuilder = roleSet.getLookup().lookup(GUIBuilder.class);

if (guiBuilder != null)
{
add(guiBuilder.createGUI(), BorderLayout.CENTER);
}
}
}

The initialization of roles is performed by installRoles(). An interesting point is that it manages in a special way a role, named GUIBuilder:

public interface GUIBuilder 
{
@Nonnull
public JComponent createGUI();
}

     
It's a simple factory to which the creation of the Swing user interface is delegated. In this way, any Swing responsibility is moved away from EnhancedTopComponent, that becomes a mere controller. This also means that any specific TopComponent in a desktop application can just inherit from EnhancedTopComponent and specify the UI in a separate class; it also means that an existing UI can be replaced by an alternate one, by just overriding the configuration of a GUIFactory.

As I wrote in the preamble of this article, EnhancedTopComponents acts as the bridge between the Platform runtime and the roles. On this purpose, all the life-cycle methods have been implemented in the following way:

interface RoleRunner
{
public void run (final @Nonnull TopComponentRole role);
}

@Override
protected void componentActivated()
{
super.componentActivated();

runRoles(new RoleRunner()
{
@Override
public void run (final @Nonnull TopComponentRole role)
{
role.notifyActivated();
}
});
}

private void runRoles (final @Nonnull RoleRunner roleRunner)
{
if (!rolesInstalled)
{
installRoles();
RoleInjector.injectLookup(this, getLookup());
PostConstructorCaller.callPostConstructors(this);
rolesInstalled = true;
}

assert rolesInstalled : "roles not initialized";

for (final TopComponentRole role : getLookup().lookupAll(TopComponentRole.class))
{
roleRunner.run(role);
}
}

Each method makes sure that the roles have been loaded and initialized, and then calls a method with the same name on all the registered roles that implement TopComponentRole:

public interface TopComponentRole
{
public void notifyActivated();

public void notifyDeactivated();

public void notifyOpened();

public void notifyClosed();

public void notifyShowing();

public void notifyHidden();
}

In this way, multiple behaviours can be plugged from different modules and take part in the life cycle of the TopComponent. A companion abstract class, TopComponentRoleSupport, implements all the methods with empty body, so one can subclass it and only implement the ones he needs.

A typical operation that must be bound to the TopComponent life-cycle is loading the data that are going to be rendered. On this purpose, we can define an interface and a few classes:

public interface DataLoader
{
public void loadData();
}

public abstract class TopComponentDataLoaderStrategy extends TopComponentRoleSupport implements DataLoader
{
@Inject
private DataLoader dataLoader;

@Override
public final void loadData()
{
dataLoader.loadData();
}
}

@NotThreadSafe
public final class EagerDataLoaderStrategy extends TopComponentDataLoaderStrategy
{
private boolean initialized;

@PostConstruct
private void initialize()
{
if (!initialized)
{
loadData();
initialized = true;
}
}
}

@NotThreadSafe
public final class LazyDataLoaderStrategy extends TopComponentDataLoaderStrategy
{
private boolean initialized;

@Override
public void notifyShowing()
{
if (!initialized)
{
loadData();
initialized = true;
}
}
}

The concrete code for loading the data must be provided in an implementation of DataLoader; then, either EagerDataLoaderStrategy or LazyDataLoaderStrategy must be configured in layer.xml. The following layer.xml fragment is taken again from the forceTen project:

<filesystem>
<folder name="Roles">
<folder name="it.tidalwave.geo.explorer.GeoExplorerPresentation">
<file name="DataLoader.instance">
<attr name="instanceClass" stringvalue="it.tidalwave.geo.explorer.impl.role.DefaultGeoCoderDataLoader"/>
</file>
<file name="DataLoaderStrategy.instance">
<attr name="instanceClass" stringvalue="it.tidalwave.netbeans.windows.role.LazyDataLoaderStrategy"/>
</file>
</folder>
</folder>
...
<filesystem>
Splitting the application behaviour in these fine grained roles makes it possible to achieve a great flexibility. For instance, a customization module can change the policy from lazy to eager and a different data provider than the default one can be specified.

Thrive in the application economy with an APM model that is strategic. Be E.P.I.C. with CA APM.  Brought to you in partnership with CA Technologies.

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 }}