Creating Context-Sensitive Capabilities for File-Based Nodes (Part 1)

Here's a capability for opening something:
package no.imr.viewer;

public interface OpenCapability {
    public void open();

In my DataObject, I assign the OpenCapability to the dynamic Lookup of the DataObject, take note of the code in bold below:

public class IMRCategoryDataObject extends MultiDataObject {

    InstanceContent content = new InstanceContent();

    public IMRCategoryDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException {
        super(pf, loader);
        CookieSet cookies = getCookieSet();
        cookies.add((Node.Cookie) DataEditorSupport.create(this, getPrimaryEntry(), cookies));

    protected Node createNodeDelegate() {

        final DataNode node = new DataNode(this, Children.LEAF, 
           new ProxyLookup(getLookup(), new AbstractLookup(content)));

        content.add(new OpenCapability() {
            public void open() {
                //Define what should happen for each node:
                StatusDisplayer.getDefault().setStatusText("Opened: " + node.getName());

        return node;


    public Lookup getLookup() {
        return getCookieSet().getLookup();

(Alternatively, use "getCookieSet.assign(OpenCapability.class", oc), if you want to use cookies, which is simply an outdated pattern replaced by Lookup.)

I then use the New Action wizard to create a new "OpenCategoryAction" action that is sensitive to "OpenCapability" objects (which is added to the dynamic content of the DataObject, as shown above). Here's the layer entry generated:

<folder name="Actions">
    <folder name="Edit">
        <file name="no-imr-viewer-OpenCategoryAction.instance">
            <attr name="delegate" methodvalue="org.openide.awt.Actions.inject"/>
            <attr name="displayName" bundlevalue="no.imr.viewer.Bundle#CTL_OpenCategoryAction"/>
            <attr name="injectable" stringvalue="no.imr.viewer.OpenCategoryAction"/>
            <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/>
            <attr name="noIconInMenu" boolvalue="false"/>
            <attr name="selectionType" stringvalue="EXACTLY_ONE"/>
            <attr name="type" stringvalue="no.imr.viewer.OpenCapability"/>

It is registered for the MIME type of the node, which I created via the New File Type dialog, so will appear in the right-click contextual menu item of my node, having the same appearance for each node, but with behavior that depends on the particular node in question (thanks to the dynamicity of the Lookup used in the definition of the DataObject, as shown above). Here's how the ActionListener is registered (by the wizard automatically) in the layer:

<folder name="Loaders">
    <folder name="text">
        <folder name="x-imrcategory">
            <folder name="Actions">
                <file name="no-imr-viewer-OpenCategoryAction.shadow">
                    <attr name="originalFile" stringvalue="Actions/Edit/no-imr-viewer-OpenCategoryAction.instance"/>
                    <attr name="position" intvalue="0"/>

And here's how the ActionListener is defined, simply getting the capability injected into it (thanks to the layer registration for the Action), from where the method is called, which behaves context sensitively, i.e., as defined in the DataObject above:

public final class OpenCategoryAction implements ActionListener {

    private final OpenCapability context;

    public OpenCategoryAction(OpenCapability context) {
        this.context = context;

    public void actionPerformed(ActionEvent ev) {;

That's how a capability can be added to a Node, letting me dynamically compose the capabilities of my Node as and when needed. Currently, the "OpenCapability" is set in the definition of the DataObject, but since the InstanceContent is in the Lookup of the DataObject, we can dynamically add/remove the OpenCapability to/from the Node's Lookup as needed.


Geertjan, node's lookup is different than DataObject's lookup: in the way you described OpenCapability instance is contained only in the node's lookup. Instead using getCookieSet.assign(OpenCapability.class, oc) will place oc in the DataObject's lookup.


Posted by Matteo Di Giovinazzo on April 14, 2010 at 10:44 PM PDT #

So, got trapped in norway or did you get out before the air was filled with ash? :-)

Posted by Christian on April 15, 2010 at 07:10 PM PDT #


I would suggest to only use Lookup completely get rid off CookieSet in DataObjects. The benefit of this is that you no longer have both a Lookup and CookieSet in parallel, but you have only a single Lookup:

public class MyDataObject extends MultiDataObject {

private final InstanceContent content = new InstanceContent();
private Lookup lookup;

public AbnfDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException {
super(pf, loader);
lookup = new ProxyLookup(getCookieSet().getLookup(), new AbstractLookup(content));
content.add((Node.Cookie) DataEditorSupport.create(this, getPrimaryEntry(), super.getCookieSet()));

protected Node createNodeDelegate() {
return new DataNode(this, Children.LEAF, getLookup());

public Lookup getLookup() {
return lookup;

public <T extends Cookie> T getCookie(Class<T> type) {
return lookup.lookup(type);

protected <T extends Cookie> T getCookie(DataShadow shadow, Class<T> clazz) {
return getCookie(clazz);


Posted by Jean-Marc Borer on April 15, 2010 at 07:18 PM PDT #

[Trackback] Geertjan hat in seinem Blog einem kurzen Artikel darüber geschrieben, wie man eine sogenannte &#8220;Capability&#8221; dynamisch in ein eigenes Node einbaut. Er nutzt dazu die Klasse InstanceContent über die dynamisch ein Lookup verändert werden kann. ...

Posted by Java Getriebe on April 15, 2010 at 07:49 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.


« June 2016