How do I lazy-load an item in the Lookup?
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.