How do I lazy-load an item in the Lookup?

Apache NetBeans Wiki Index

Note: These pages are being reviewed.

A node is typically used to represent some business object and it’s a common idiom to place that business object in the node’s lookup so that, for example, a context-sensitive action can operate on it. Sometimes fully initializing that business object can involve an expensive operation that would be wasted effort if the user never invoked the action that used it anyway.

So how can you defer loading or initializing the business object until it is truly needed?

There are probably several ways, but two common ones are:

Override the beforeLookup(Lookup.Template<?> template) method

If you are using the AbstractLookup class to create the lookup, you can override the beforeLookup(Lookup.Template<?> template). By doing this, you will be notified just before a lookup query is processed and you could check to see if the template would match the objects for which you’ve deferred loading, giving you an opportunity to load them now and add them to the InstanceContent used by the AbstractLookup.

Use InstanceContent.Convertor to create a placeholder object

The InstanceContent.Convertor class can be registered in an AbstractLookup such that it provides a typesafe placeholder until the actual object type is requested, and at that point, the convertor can create and return the actual object.

Consider the following example in which you have a Token class which represents a database record ID and a business object class AnExpensiveClass which will be populated from the database based on the supplied token’s ID.

public final class Token {

    private final long id;

    public Token(long id) {
        this.id = id;
    }

    public long getId() {
       return id;
    }
}

Now we will write a converter. Until the first time something calls theLookup.lookup(AnExpensiveClass.class), only our quick-to-create Token object is in memory. On the first such lookup call, the following code is run:

public class LazyLoadingDelegate implements InstanceContent.Convertor<Token, AnExpensiveClass> {

    @Override
    public AnExpensiveClass convert(Token token) {
        // Return an instance based on the supplied token (i.e. assume that
        // the AnExpensiveClass constructor will load data from the database
        // and populate the instance we're returning).
        return new AnExpensiveClass(token);
    }

    @Override
    public Class<? extends AnExpensiveClass> type(Token token) {
        return AnExpensiveClass.class;
    }

    @Override
    public String id(Token token) {
        return String.valueOf(token.getId());
    }

    @Override
    public String displayName(Token token) {
        return "my lazy loading delegate";
    }
}

Code that creates a Lookup and registers the InstanceContent:

    ic = new InstanceContent();
    al = new AbstractLookup(ic);

    Token token = new Token(12345);
    ic.add(token, new LazyLoadingDelegate());

Your context-sensitive action will behave normally — it does not need to know about the lazy loading (code not relevant to lazy loading has been removed for the sake of brevity):

public final class ExpensiveClassAction implements ActionListener {
    private final AnExpensiveClass expensiveClass;
    public ExpensiveClassAction(AnExpensiveClass a) {
      this.expensiveClass = a;
    }

    public void actionPerformed(ActionEvent ev) {
        // now you have the actual do AnExpensiveClass instance,
        // in variable expensiveClass
        // so do something with it...
    }
}

Lifecycle With InstanceContent.Converter

Objects created using an InstanceContent.Converter are only weakly cached by default. That means that, after AnExpensiveClass is instantiated, it can be garbage collected if no object holds a reference to it in a field. If the object is going to be queried for repeatedly, you may want your InstanceContent.Converter to cache the last-created value, either for some period of time, or using a SoftReference or hard reference or other caching strategy.