blob: 6c3f5c068c3b207d7fa3822ce6eda0eff570c4b2 [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
= DevFaqNodeInjectingLookupContents
:jbake-type: wiki
:jbake-tags: wiki, devfaq, needsreview
:jbake-status: published
:keywords: Apache NetBeans wiki DevFaqNodeInjectingLookupContents
:description: Apache NetBeans wiki DevFaqNodeInjectingLookupContents
:toc: left
:toc-title:
:syntax: true
== I want to allow other modules to inject objects into my Node's Lookup or elsewhere (Actions, Properties...)
There is a simple general method for allowing you to define your own registry of objects as a folder in the System Filesystem, and look them up on demand.
[source,java]
----
class BaseNode extends AbstractNode {
//...
private static final String PATH_IN_SFS = "path/to/some/folder/actions";
public Action[] getActions(boolean ignored) {
return Lookups.forPath(PATH_IN_SFS).lookupAll(Action.class).toArray(new Action[0]);
}
//...
}
----
You can use this pattern for properties, or whatever. If you want your Node to respond to new modules being loaded on the fly, you may want to get a Lookup.Result and listen for changes on it (not necessary in the example above, but necessary for things like Lookup contents or Properties, which are cached).
=== Injecting Lookup Contents
First, be sure this is something you really need. Typically, you expose some model object from your Node, and link:http://wiki.netbeans.org/DevFaqActionContextSensitive[write Actions that are sensitive to it].
However, if you want to use built-in actions (such as link:http://bits.netbeans.org/dev/javadoc/org-openide-actions/org/openide/actions/OpenAction.html[OpenAction]) over your custom Nodes, and the module which created the Node does not provide the `Openable` or `OpenCookie` object which, for example, `OpenAction` needs, then you do need some way for other modules to inject contents into your lookup. If you are both injecting an object into the lookup, and writing an action against that object in the same module (and not expecting other modules to also add actions sensitive to your Converter's type), you can probably skip the injecting of lookup contents, and just go straight to the Node's model object.
Lookup contents should not be added programmatically - that would mean every module that cares about a Node type would have to be called to add contents (which may never be used) to it - meaning a performance penalty. Also this breaks things like FilterNode, which cannot transparently proxy methods that exist on the Node it is acting as a clone of.
It is simple to create a declarative registry for lookup contents. It makes use of the fact that the contents of an link:http://bits.netbeans.org/dev/javadoc/org-openide-util-lookup/org/openide/util/lookup/AbstractLookup.html[AbstractLookup] are provided by a mutable link:http://bits.netbeans.org/dev/javadoc/org-openide-util-lookup/org/openide/util/lookup/InstanceContent.html[InstanceContent] object, and that a factory class can be added to an InstanceContent, link:http://bits.netbeans.org/dev/javadoc/org-openide-util-lookup/org/openide/util/lookup/InstanceContent.Convertor.html[InstanceContent.Converter]. So you can create a folder where other modules will register instances of InstanceContent.Converter for Nodes which hold an object of your type. When you create the Node's lookup, you can collect all such Converters, and add them to your Lookup's contents. Unless the lookup is queried for the type one Converter creates, it will never be called.
Here is an example base Node class that will do this:
[source,java]
----
public class BaseNode<T> extends AbstractNode {
final InstanceContent content;
final Class<T> type;
public BaseNode(Class<T> type, T modelObject) {
this(type, modelObject, new InstanceContent());
}
public BaseNode(Class<T> type, T modelObject, InstanceContent content){
super(Children.LEAF, new ProxyLookup(Lookups.fixed(modelObject), new AbstractLookup(content)));
this.content = content;
this.type = type;
//Populate lookup based on declaratively registered factories
String pathInSystemFS = getRegistrationPath("lookupContents");
Collection<? extends InstanceContent.Convertor> all =
Lookups.forPath(pathInSystemFS).
lookupAll(InstanceContent.Convertor.class);
for (InstanceContent.Convertor<T, ?> factory : all) {
content.add(modelObject, factory);
}
//if you want to handle modules being loaded/unloaded in a running app,
//use lookupResult() instead of lookupAll(), retain a reference to the
//Lookup.Result, listen on it for changes, and remove all acquired
//InstanceContent objects if it changes, then rerun the above code
}
@Override
public Action[] getActions(boolean context) {
return Lookups.forPath(getRegistrationPath("actions")).
lookupAll(Action.class).toArray(new Action[0]);
}
String getRegistrationPath(String subfolder) {
//e.g. pass "lookupContents" and get
//MyModule/com/foo/mymodule/MyType/lookupContents
return "MyModule/" + type.getName().replace('.', '/') + "/" + subfolder;
}
}
----
Suppose that we have some BaseNodes whose model objects are instances of Strings. We want to add a Foo object to their Lookups, and register an action which operates against Foo objects. So, we have an InstanceContent.Converter implementation:
[source,java]
----
public class FooFactory implements InstanceContent.Convertor<String, Foo> {
@Override
public Foo convert(String string) {
return new Foo(string);
}
@Override
public Class<? extends Foo> type(String obj) {
return Foo.class;
}
@Override
public String id(String obj) {
return getClass().getName() + obj;
}
@Override
public String displayName(String obj) {
return obj;
}
}
----
The action implementation can be any Action subclass, so we can omit the code for that - but its classname for this example will be `org.netbeans.demo.elookup.FooAction`.
All we need to do now is register both of these objects in the link:http://wiki.netbeans.org/DevFaqSystemFilesystem[System Filesystem] and we will have working code.
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN"
"http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<filesystem>
<folder name="MyModule">
<folder name="java">
<folder name="lang">
<folder name="String">
<folder name="lookupContents">
<file name="org-netbeans-demo-elookup-FooFactory.instance"/>
</folder>
<folder name="actions">
<file name="org-netbeans-demo-elookup-FooAction.instance"/>
</folder>
</folder>
</folder>
</folder>
</folder>
</filesystem>
----
Note that objects created by such factories will be _weakly cached_ by the lookup - if no object is holding a reference to the object, it can be garbage collected. If such objects are expensive to create, or if you expect callers to attach listeners to the factory-created objects, you may want to cache them in your implementation of `InstanceContent.Converter`.
=== Apache Migration Information
The content in this page was kindly donated by Oracle Corp. to the
Apache Software Foundation.
This page was exported from link:http://wiki.netbeans.org/DevFaqNodeInjectingLookupContents[http://wiki.netbeans.org/DevFaqNodeInjectingLookupContents] ,
that was last modified by NetBeans user Jtulach
on 2010-07-24T19:35:53Z.
*NOTE:* This document was automatically converted to the AsciiDoc format on 2018-02-07, and needs to be reviewed.