…and start by simplifying NetBeans.
I’ve just returned from a training at skyguide in Geneva. We where invited there by a group of enthusiastic Java pros who are thinking about porting some of their applications to NetBeans. When teaching the NetBeans Platform some APIs are harder to understand for new users than others - especially the Nodes API, System FileSystem and Lookups. If you’ve been using these APIs for a while they actually seem very simple. So when it’s hard to explain an API which is actually quite elegant and simple, there must be something wrong. The main issues seem to be strange naming on the one side and mixing of concerns on the other side. Often it’s only small things that get into your way when learning the platform. Here are some suggestions, what could be improved. I’m starting to collect these ideas, so feel free to add your own ideas in the comments section:
Lookup
Let’s start with Lookup API. There are several problems with this API for beginners. The first problem is, that the term “Lookup” is used for a lot of different things. First it is used for a generic API, the abstract Lookup class and it’s companions, like Lookup.Template, Lookup.Result and LookupListener. But the same term is also used synonymously for some very specific uses of this API, namely as a Service loader (Lookup.getDefault()) and for global selection management (Utilities.actionsGlobalContext()). Somehow it’s also an unfortunate mixture that Lookup.getDefault() is a method in the Lookup class, because I think it binds the data structure to close to one of it’s uses.
Just imagine a less elaborate and powerful mechanism for service loading using a HashMap with Class objects as keys and Collections of instances as values. You probably wouldn’t expect something like HashMap.getDefault() to return a ServiceLoader. For a new user the situation is similar when they first see Lookup. If ServiceLoading were the only purpose of Lookup it would be OK, but since Lookup is used pervasively and for totally different purposes it’s more like a data structure. In the case of Utilities.actionsGlobalContext() I think it’s bad naming that get’s into the way of an easy understanding.
ServiceLoader
So the first thing that could be done to simplify the API would be to create a ServiceLoader class with a user friendly name as a wrapper for Lookup.getDefault():
public class ServiceLoader{
public static <T> Collection<? extends T> loadServices(Class<T> clazz) { return Lookup.getDefault().lookupAll(clazz);}
public static void addServiceListener(Class clazz, LookupListener listener) {Lookup.Result result = Lookup.getDefault().lookupResult(clazz); ...}
public static void removeServiceListener(Class clazz, LookupListener listener){...}}
This would serve several purposes.
- First it would use a name that reflects what the default Lookup is actually doing: loading services.
- Second it would remove the need to initialize results correctly, one of the most common errors of beginners.
- Third it would lead to clearer separation of concerns between specific service loader functionality and generic Lookup functionality.
So instead of this:
Lookup.getDefault().lookupAll(ServiceProvider.class);
We would write:
ServiceLoader.loadServices(ServiceProvider.class);
and instead of:
Lookup lookup = Lookup.getDefault();
Lookup.Result result = lookup.lookupResult(ServiceProvider.class);
result.addLookupListener(listener);
result.allInstances();
It would be:
ServiceLoader.addServiceListener(ServiceProvider.class, listener);
It would be even nicer if the LookupListener could be wrapped in a ServiceListener with convenience methods that totally hide the Lookup.Result. Another extension might be to have a method getRegisteredServices() that returns the fully qualified name of all class objects that are registered as keys.
Selection Management
The same could be done for Selection Management with a SelectionManager that gives a nicer interface to Utilities.actionsGlobalContext().
public class SelectionManager{
public static void addSelectionListener(Class clazz, LookupListener listener) {...}
public static void removeSelectionListener(Class clazz, LookupListener listener){...}
}
Again a user friendly name and hiding the initialization of the Lookup.Result would simplify the usage a lot. So when you want to listen for selection instead of:
Lookup lookup = Utilities.actionsGlobalContext();
Lookup.Result result = lookup.lookupResult(MyInteresting.class);
result.addLookupListener(listener);
result.allInstances();
It would be:
SelectionManager.addSelectionListener(MyInteresting.class, listener);
This way the naming reflects what the user is using it for, so it’s much easier to remember. The main benefit when explaining Lookup is the separation of concerns, and since all the changes could be done by simple wrappers it would be fairly easy to implement it in a fully backward compatible way.
FileSystem
FileSystems again have a similar problem. First it’s an API for accessing structured data, very simple and powerful. But again the name and concept is overloaded with the DefaultFileSystem or System FileSystem. Although it’s basically only a usecase for FileSystem API to create a central registry for intermodule communication it’s very often confused with the API itself. And have a look at the code:
FileSystem filesystem = Repository.getDefault().getDefaultFileSystem();
Who would guess that we’re accessing the Registry here? It would be much nicer like this:
Registry registry = Registry.getInstance();
Even a simpe name change like this would help:
FileSystem registry = Registry.getInstance();
Another thing that would be useful would be to mark Extension points in the layer. I’ve recently filed an issue for this, and again in Geneva I was asked by a attendee of the course, how he can easily find available extension points from inside the IDE. Other Platforms have this feature, and I think it’s very useful for learning more about the platform.
Nodes
The main problem in understanding the Nodes API is the use of Children. I’m not saying something is wrong about using Children ( now that sounded really weird! ), but when first learning the API they get into the way of understanding. Users who are starting with the platform are usually Swing developers looking for more. As Swing developers they know JTree and DefaultMutableTreeNode. So when looking at the Nodes hierarchy they are looking for something similar. Instead the first thing they learn is they need to create a “Children Object” or a ChildFactory with a setKeys method and a createNodeForKey method that seem to be magically called from somewhere.
Here we would benefit from a SimpleNode, maybe like this:
public class SimpleNode extends AbstractNode{
private Children.Array childrenArray ;
private InstanceContent content;
public SimpleNode() {
this(new Children.Array(), new InstanceContent());
}
public SimpleNode(String name){
this(name, name);
}
public SimpleNode(String name, Object userObject){
this();
content.add(userObject);
super.setName(name);
}
private SimpleNode( Children.Array childrenArray, InstanceContent ic){
super(childrenArray, new AbstractLookup(ic));
this.childrenArray = childrenArray;
this.content = ic;
}
public void addChildNode(Node node){
addChildNodes(new Node[]{node});
}
public void addChildNodes(Node [] nodes){
childrenArray.add(nodes);
}
public void removeChildNode(Node nodes){
removeChildNodes(new Node [] {nodes});
}
public void removeChildNodes(Node [] nodes){
childrenArray.remove(nodes);
}
}
So instead of having to implement a ChildFactory a user could simply generate SimleNodes and put his hierarchy together in a very simple way. For many usecases this will be fine and in case advanced node creation is required the Children concept can be used.
The “Hello World!” example would now look like this:
SimpleNode root = new SimpleNode("I'm a maid I should mary any:");
root.addChildNode(new SimpleNode("Tom"));
root.addChildNode(new SimpleNode("Dick"));
root.addChildNode(new SimpleNode("or Harry."));
explorermanager.setRootContext(root);
Much simpler and easier to grasp, than having to create a ChildFactory and still fully compatible.
I guess with those little changes it would be easier to understand these APIs. What do you think?