NetBeans Platform Lookups for Beginners

Hi,

this seems to be post #1001 about NetBeans Lookups. Most of the other Posts try to explain lookups. They mostly focus on what Lookups are, not what they are good for.

All posts will tell you that lookups are very simple. They’re just a „bag of objects“ you can use for whatever you want.
And that’s the point. I didn’t know what I want to do with lookup, mostly because I didn’t know what I can do with it and at which point it will help me to do something.

Since I did not have any experience with Dependency Injection, I had to learn a completely new way of thinking.

While having the NetBeans Platform Training with Geertjan Wielenga I finally understood all of it and can only recommend to attend a NB Platform Training!

OK, let’s start.

Yes, the NetBeans Lookup System is nothing more than a Map like this one below with some special methods to retrieve it’s contents (INFO: direct write not possible in global lookup).
In a different project which was not based on NetBeans, I implemented a small version of the NetBeans Lookup (lacks some features of the NetBeans Lookup but illustrates the principle, for details look at the NetBeans Lookup JavaDoc):

 


public class Lookup {
	private static HashMap, ArrayList> lookupMap = new HashMap<>();

	/**
	 * find an object by it's classname, superclass name or interface name
	 * 
	 * @param needle
	 * @return
	 */
	public static  T lookup(Class needle) {
		ArrayList objectList = lookupMap.get(needle);
		if (objectList == null) {
			objectList = lookupDeeply(needle);
			if (objectList == null || objectList.size() == 0) {
				System.out.println("cannot find instances of "
						+ needle.getName());
				return null;
			}
		}
		Object lookedUpObject = objectList.get(objectList.size() - 1);
		return (T) lookedUpObject;
	}

	private static ArrayList lookupDeeply(Class needle) {
		ArrayList fountObjects = new ArrayList();
		for (ArrayList objectList : lookupMap.values()) {
			for (Object candidate : objectList) {
				if (clazzToLookupIsOfSuperClass(candidate, needle)) {
					fountObjects.add(candidate);
				} else if (clazzToLookupIsOfInterface(candidate, needle)) {
					fountObjects.add(candidate);
				}
			}
		}
		return fountObjects;
	}

	private static boolean clazzToLookupIsOfSuperClass(Object candidate,
			Class needle) {
		Class superClass = candidate.getClass().getSuperclass();
		while (superClass != null && !superClass.getName().equals("")) {
			if (superClass.getName().equals(needle.getName())) {
				return true;
			}
			superClass = superClass.getSuperclass();
		}
		return false;
	}

	private static boolean clazzToLookupIsOfInterface(Object candidate,
			Class needle) {
		Class[] clazzInterfaces = candidate.getClass().getInterfaces();
		for (int i = 0; i < clazzInterfaces.length; i++) {
			if (clazzInterfaces[i].getName().equals(needle.getName())) {
				return true;
			}
		}
		return false;
	}

	public static  ArrayList lookupAll(Class needle) {
		ArrayList objectList = (ArrayList) lookupMap.get(needle);
		if (objectList == null) {
			objectList = lookupDeeply(needle);
			if (objectList == null || objectList.size() == 0) {
				System.out.println("cannot find instances of "
						+ needle.getName());
				return null;
			}
		}
		return (ArrayList) objectList;
	}

	/**
	 * registers the provider object under it's classname
	 * 
	 * @param provider
	 */
	public static void registerProvider(Object provider) {
		ArrayList providers;
		Class clazz = provider.getClass();
		if (lookupMap.containsKey(clazz)) {
			providers = lookupMap.get(clazz);
		} else {
			providers = new ArrayList<>();
			lookupMap.put(clazz, providers);
		}
		providers.add(provider);
	}
}

Types of Lookups

There are several scopes or types of Lookups. Let me begin with the global Lookup.

The global Lookup is initialized when the application is starting. It will parse all the @ServiceProvider annotations in your class files. The ServiceProvider Annotation is used to register an object in the global Lookup.
When you try to retrieve an instance of one of those objects, it will be instantiated by it’s default constructor at first retrieval – so make sure the class has one!! Once instantiated you will retrieve a singleton on each following fetch.

@ServiceProvider(service = SomeAction.class) //register this object under "SomeAction" AND Runnable because of inheritance!
public class SomeAction extends Runnable{
    public void run(){
        ....
    }
}

When the application is running, you can fetch the object from the Lookup:

    ...
    SomeAction obj = Lookup.getDefault().lookup(SomeAction.class);
    ...

 

Fine, but why and when do you need this?
Short answer: To avoid class constructor parameters and by that avoid having to pass arguments to your constructors!
Hm, ok, and why is it bad to have parameters in my constructors?

A quick example:

//the usual way to create another object that uses our sample action
public class MyOtherSampleObject{
    private final SomeAction action;//action would be stored as member variable or initialized in constructor

    //constructor takes the action or it would be initialized here
    public MyOtherSampleObject(SomeAction action){
        this.action=action;
    }

    public void doSomething(){
        this.action.run(); //run the action stored in this instance
    }
}

//The NetBeans way:
public class MyOtherSampleObject{
    public void doSomething(){
        //retrieve a singleton instance of SomeAction
        SomeAction action = Lookup.getDefault().lookup(SomeAction.class);
        this.action.run(); //run the action
    }
}

As you can see, the latter sample is
a) much smaller in lines of code and
b) the action object will only be initialized if needed which keeps the memory clean and
c) you don’t need the constructor to have arguments.

When I ported my existing Swing app to the NetBeans Platform two years ago, the first thing I did after reading about Lookup was removing most of my constructors (can’t tell how much I love mind numbing refactorings of some hundered classes for hours! – but this was necessary)

Yes, there are other Lookups! The global Lookup is a bit inflexible for certain tasks – its contents can’t be changed at runtime. I learned that every object can have a lookup (imagine a member variable of type Lookup). This is used to staff objects with other objects (dependencies btw).

For example, if you are developing a vector drawing app which supports arranging geometrical objects like circles, lines etc. (like SVG), usually you would give each shape a reference to the parent image to be able to communicate between those e.g. when you want to merge two shapes into one you would need to remove the two shapes from the image and add a new one containing the merged shape – and all that from an action coming from the shape! Usually initialization would be a big overhead for a simple task.

Forget all that when using NetBeans Lookups. Just fetch the object when you need it.

NetBeans has another important Class called Node. A Node is a tree element. Wherever NetBeans uses trees to display data (project explorer, navigator, etc), Node objects will be used. These have the advantage that by clicking on them, NetBeans recognizes that and makes all objects stored in the node available in the local Lookup. The local Lookup is changing each time you click on another node or TopComponent (basically wrapper for a JPanel).

This way you have a context sensitive lookup that always represents the contents of the selected element.e.g. when a click is made on a file node in the project tree, NetBeans will look into the selected Node’s local lookup and check for special NetBeans objects like OpenCookie. When OpenCookie is found, NetBeans knows that it can load the file and show a TopComponent displaying the loaded data.
And so there are more special Classes typically residing in Nodes: DataObject(wrapper for the file „under“ the node), Savable (indicates that the file needs to be saved), …

You can see what is in the lookup of the currently selected Node/TopComponent by using Utilities.actionsGlobalContext().lookupAll(Object.class) which gives you a List of Objects contained in the lookup.
 
So, how are those Lookups filled?

Local Lookups are made of two Objects: InstanceContent, AbstractLookup:

InstanceContent is like an ArrayList which can be modified dynamically.
AbstractLookup will be initialized with the InstanceContent. This way you can give a Lookup to any Object. To let NetBeans know about this, let the Object implement LookupProvider, then implement the getLookup() method and you’re fine:

public class SomeAction implements LookupProvider{
    InstanceContent ic = new InstanceContent();
    AbstractLookup lookup;

    public Lookup getLookup(){
        if(lookup == null){
            this.lookup = new AbstractLookup(ic);
        }
        return this.lookup;
    }
}

 
Lookups do have their downsides, especially when Operations are running that take some time. In this time, the user can click on any element of the UI and by that, NetBeans will change the dynamic Lookup and you cannot rely on getting the right Lookup contents when using Utilities.actionsGLobalContext()! In some situations I store the lookup contents when I get them first and work on those. Problem solved! The user can click around but doesn’t interfere with your code.

OK, I’m done for today, hope it was helpful! Let me know how I can improve the post.
Nico

3 Gedanken zu “NetBeans Platform Lookups for Beginners

  1. Hi Peter!

    Geertjan was at our office in Leipzig, Germany. You can have an in-house training, too! Just ask Geertjan or have a look at: https://edu.netbeans.org/courses/nbplatform-certified-training/

    citation at the bottom:
    „Ask us to deliver an in house training. Both companies and universities are welcome to invite us to deliver the course in person. Depending on the circumstances, we could provide the training in different ways, such as in house at your location. Let us know your preferences by writing to geertjan.wielenga@oracle.com. Tell us how many students will attend, what your plans are with the NetBeans Platform, what your current skill set is, and so on.“

    We did that and got a perfect NetBeans Platform training short time after. We also got great practical help regarding our specific questions. By that, we solved some major issues of our code during the training – definitely worth the money 😀 !

Schreibe einen Kommentar