Feb 282012
 

Using the Command Line Parsing API, it is possible to process parameters passed to your NetBeans Platform application via the command line. This article explores how it works.

Read this excellent blog post by Geertjan which provided me with some of the information for this article. Also useful was the javadoc.

NetBeans Platform 7.1 Code

When using the NetBeans Platform 7.1, the way to access command line parameters is by extending the class OptionProcessor.

@ServiceProvider(service = OptionProcessor.class)
public class MyProcessor extends OptionProcessor
{
    private Option option1 = Option.requiredArgument(
            'a', "argumentName");

    @Override
    protected Set getOptions()
    {
        Set set = new HashSet();
        set.add(option1);
        return set;
    }

    @Override
    protected void process(Env env, Map optionValues)
            throws CommandException
    {
        if (optionValues.containsKey(option1))
        {
            System.out.println(optionValues.get(option1)[0]);
        }
    }
}

Note: Module dependencies are required on Command Line Parsing API and Lookup API.

The interesting part is the static method Option.requiredArgument() that creates the actual Option instance. Have a look at the full list of methods in the javadoc.

Testing the Code

This can be tested when running the suite (or standalone module) from the IDE. Just add the new parameter to the project.properties file:

[text]run.args.extra=–argumentName=theName[/text]

If the lock file exists in the test user directory, you will see a message indicating that there was an unrecognised option. Just delete the lock file or perform a clean and build on the project and run it again.

[text]Unrecognized option –test-userdir-lock-with-invalid-arg[/text]

NetBeans Platform 7.2 Code – Enters Annotations

As I was searching for the javadocs, I came across some new classes and annotations that are introduced in the development builds of NetBeans 7.2. There is now a handy @Arg annotation for specifying a command line parameter. Here is an example in the IDE source code: GroupOptionProcessor from the Project UI module.

So now it is possible to declare the same thing as follows:

public class NewProcessor implements ArgsProcessor
{
    @Arg(longName = "argumentName")
    @Description(displayName = "--argumentName NAME",
    shortDescription = "just any old argument")
    public String theOption;

    @Override
    public void process(Env env) throws CommandException
    {
        if (theOption != null)
        {
            System.out.println(theOption);
        }
    }
}

As with many of the other annotations, a generated layer file is created at compile time. If you are experiencing trouble with this, have a look at this bug report and consider voting for it. ๐Ÿ™‚

Update: Jaroslav Tulach replied to the the bug report with a very easy work around: add a compile time dependency on the FileSystem API. Thanks Jaroslav!

Feb 222012
 

The NetBeans Platform manages the creation of actions at run time. When you create a new action with the wizard (File > New File… > Module Development > Action) everything gets generated that is needed for the Platform to load the action and call your actionPerformed() method when required.

However, the question does sometimes arise whether it is possible to get hold of the action that is created. And there is indeed a way. Whether this is desirable in general, or the the best way to solve a particular problem, is a debate for another day. It does, however, illustrate an interesting way in which the Platform can be extended. And so I thought it worth documenting here. ๐Ÿ™‚

Note that the generated actions discussed below are Always Enabled actions.

With NetBeans 7.1, a class implementing ActionListener is generated with some annotations on it. At compile time, the annotations are used to generate a layer file where the action is registered. (With earlier versions of NetBeans, such as 6.9.1, actions used to be specified directly in the module’s layer file. And the wizard generated the layer file entries instead of the annotations.)

The interesting attribute that is not visible in the annotations (but still in the generated layer file) is the attribute called instanceCreate. It has as default value the method org.openide.awt.Actions.alwaysEnabled. This means that the method that is called to create the action is alwaysEnabled in the class org.openide.awt.Actions. It is possible to replace this method with our own.

Assume that a singleton class called Controller exists, that is interested in the action instance. Now create a new class that will be containing the custom version of the method:

public class ActionCreator
{
    public static Action alwaysEnabled(java.util.Map TheMap)
    {
        javax.swing.Action newAction = 
                org.openide.awt.Actions.alwaysEnabled(
                (java.awt.event.ActionListener) TheMap.get("delegate"),
                (String) TheMap.get("displayName"),
                (String) TheMap.get("iconBase"),
                (Boolean) TheMap.get("noIconInMenu"));
        Controller.instance().setAction(newAction);
        return newAction;
    }
}

And add to the layer file (create the file if it does not yet exist, and remember to change the paths as applicable):

<folder name="Actions">
    <folder name="Edit">
        <file name="za-co-pellissier-example-TestAction.instance">
            <attr name="instanceCreate" methodvalue=
                "za.co.pellissier.example.ActionCreator.alwaysEnabled"/>
            <attr name="noIconInMenu" boolvalue="false"/>
        </file>
    </folder>
</folder>

That is it. Now the controller will have an instance of that particular action, and it can do with it whatever it wants.

Note that the same instance will be registered on the menu as well as the toolbars (if you choose to put it in both places in the wizard). So for example disabling it will disable the action in both locations.

Edit: If you have trouble disabling the action that you now have a reference to in the above code, create a new class extending AbstractAction and instantiate that instead of calling the alwaysEnabled method to create an action for you.

Feb 152012
 

When loading classes as extensions in a NetBeans Platform application, serialising the dynamically loaded objects is straight forward. However, deserialising them is not. ๐Ÿ™‚

This article assumes that the reader is familiar with extension points. For more information on this topic, read the NetBeans Platform 7.1 Quick Start tutorial, the Lookup API javadocs and Toni Epple’s article How Do NetBeans Extension Points Work? (Note that the article was written before the @ServiceProvider annotation was introduced.) And for a general code example for serialisation, see Serialize an object to a file.

As an example of the problem, consider the following scenario. In a module called VehicleProvider, I have an interface called IVehicle. This is the interface that extensions need to implement. And I have a class called VehicleProvider that loads all the instances of IVehicle and keeps them in a serialisable collection (called mList). In a separate module called Car, I have a class called Car that implements IVehicle and Serializable, and is registered as providing the IVehicle service.

VehicleProvider does not have a dependency on module Car, but loads dynamically the object that is found in it using Lookup.

Serialising the collection in the VehicleProvider class works perfectly as expected:

try
{
    ObjectOutputStream out = new ObjectOutputStream(
            new FileOutputStream("f.dat"));
    out.writeObject(mList);
    out.close();
}
catch (FileNotFoundException ex)
{
    Exceptions.printStackTrace(ex);
}
catch (IOException ex)
{
    Exceptions.printStackTrace(ex);
}

However, when deserialising the same file, things go wrong:

try
{
    ObjectInputStream in = new ObjectInputStream(
            new FileInputStream("f.dat"));
    mList = (ArrayList) in.readObject();
    in.close();
}
catch (ClassNotFoundException ex)
{
    Exceptions.printStackTrace(ex);
}
catch (FileNotFoundException ex)
{
    Exceptions.printStackTrace(ex);
}
catch (IOException ex)
{
    Exceptions.printStackTrace(ex);
}

When readObject() is called, a java.lang.ClassNotFoundException is thrown indicating that class Car is nowhere to be found. And if you think about it, that is entirely true. Each module in a NetBeans Platform application has its own class loader and hence its own class path. The class path is determined by the module dependencies. And since no dependency exists on module Car, the VehicleProvider module’s class loader would not know about the class.

After much Googling and debugging through source code, I realised that I needed a way to resolve the classes. And so I extended ObjectInputStream for this purpose:

public static class SystemObjectInputStream 
        extends java.io.ObjectInputStream
{
    public SystemObjectInputStream(InputStream TheStream) 
            throws IOException, StreamCorruptedException
    {
        super(TheStream);
    }

    @Override
    protected Class resolveClass(ObjectStreamClass Osc) 
            throws IOException, ClassNotFoundException
    {
        Class theClass = null;

        ClassLoader syscl = Lookup.getDefault().lookup(ClassLoader.class);
        theClass = Class.forName(Osc.getName(), true, syscl);

        return theClass;
    }
}

And in the deserialisation code, the derived class is used instead of ObjectInputStream:

in = new SystemObjectInputStream(new FileInputStream("f.dat"));

The important part of the SystemObjectInputStream code above is the line where Lookup is used to find an instance of ClassLoader.class. This is the system class loader, and it has access to all the modules in the application.

Feb 082012
 

Selection management and nodes are two frequently used aspects of the NetBeans Platform. And these aspects have been well documented in tutorials:

These tutorials (specifically the second one) deal with explorer views that are located on top of TopComponents. And how the selected node can influence global selection. And that is very useful indeed.

However, one day I ran into a requirement that was a little different. I needed a dialog box with an explorer view (a BeanTreeView in that specific case). And selection of one of the nodes in the view needed to only influence other controls on that same dialog box.

Having found a way to accomplish this, I decided to create a sample movie information viewer dialog using the same principles (see Figure 1 below). And write a tutorial for NetBeans 7.1 about this application.

In the final product, when selecting one of the nodes on the left, the release date textbox will be updated according to the selection.

Movie Viewer

Figure 1: Completed movie information viewer screenshot

Note that this tutorial does assume some previous experience with NetBeans Platform application development. See the the NetBeans Platform Learning Trail for more information.

Creating the Project

  1. From the File menu, choose New Project…
  2. Choose the NetBeans Modules category, and the Module project type. Click Next.
  3. On the name and location page, specify a name for the project.
  4. Select the Standalone Module option, and click Next.
  5. Specify a code name base for the project, such as za.co.pellissier.selectiondemo.
  6. Click Finish.

Displaying the Dialog

Displaying a dialog in a NetBeans Platform application is done by using the Dialogs API. This took me a while to find the first time I used it, so I think it is worthwhile mentioning here. ๐Ÿ™‚

  1. Create a new JPanel Form and call the class MoviePanel.
  2. Add a module dependency on the Dialogs API.
  3. Create a new always enabled action on the global toolbar. This action will display the dialog.
  4. Add the following code to the generated actionPerformed() method, and fix imports.

MoviePanel f = new MoviePanel();
NotifyDescriptor msg = new DialogDescriptor(f, "Movies!");
Object result = DialogDisplayer.getDefault().notify(msg);

The Data

  1. Create a class to contain the data to be displayed, and call it Movie.

public class Movie
{
    private String title;
    private int releaseYear;

    public Movie(String title, int releaseYear)
    {
        this.title = title;
        this.releaseYear = releaseYear;
    }

    public int getReleaseYear()
    {
        return releaseYear;
    }

    public void setReleaseYear(int releaseYear)
    {
        this.releaseYear = releaseYear;
    }

    public String getTitle()
    {
        return title;
    }

    public void setTitle(String title)
    {
        this.title = title;
    }
}

The Children

  1. Add module dependencies on the Nodes API and Lookup API.
  2. Create a class to create the child nodes and call it MoviesChildren.

public class MoviesChildren extends Children.Keys
{
    ArrayList children = new ArrayList();

    public MoviesChildren()
    {
        children.add(new Movie("The Green Hornet", 2011));
        children.add(new Movie("How to Train Your Dragon", 2010));
        setKeys(children);
    }

    @Override
    protected void addNotify()
    {
        setKeys(children);
    }

    @Override
    protected Node[] createNodes(Object t)
    {
        Node node = new AbstractNode(LEAF, Lookups.singleton(t));
        node.setName(((Movie) t).getTitle());
        return new Node[]
                {
                    node
                };
    }
}

Note that the children class would normally get the actual data from elsewhere. For the purposes of this tutorial, however, it is the simplest solution to just have the data hardcoded there since the data is not the focus of the tutorial.

Form Design

  1. Add a module dependency on the Explorer & Property Sheet API.
  2. Open the MoviePanel in Design view, and drag a Swing Scroll Pane onto the panel. Call it mMoviesPane.
  3. Change the scroll pane’s custom creation code to be:
    new BeanTreeView()
  4. Change the scroll pane’s post creation code to be: ((BeanTreeView)mMoviesPane).setRootVisible(false);
  5. Switch to source view and fix imports.
  6. Switch back to Design view, and create the label and text field for the release date. Call the text field mDate.

The Magic

So far, we haven’t done anything out of the ordinary. However, now we get to the interesting part.

Firstly, it is worthwhile mentioning how an explorer view such as the BeanTreeView finds the explorer manager where it gets its data from. It finds is parent component in the GUI component hierarchy, and checks whether it implements ExplorerManager.Provider. If not, it continues up the hierarchy until it finds one. So we want our MoviePanel to implement ExplorerManager.Provider, just like the TopComponent in the Selection Management tutorial.

  1. Change the MoviePanel class to implement ExplorerManager.Provider
  2. Add the following code to the class to implement the required method:

private ExplorerManager mManager = new ExplorerManager();

@Override
public ExplorerManager getExplorerManager()
{
    return mManager;
}

Now we need to create the root node and its children.

  1. Add the following code to the constructor, after the initComponents() call:

mManager.setRootContext(new AbstractNode(new MoviesChildren()));

Now the nodes will be displayed when you run the module. The only part that remains is the selection management. The MoviePanel itself will be listening for the selection events.

  1. Change the MoviePanel class to also implement LookupListener
  2. Add the following code to the class to implement the required method:

@Override
public void resultChanged(LookupEvent Event)
{
    Collection results = ((Lookup.Result) Event.getSource()).allInstances();
    for (Object object : results)
    {
        if (object instanceof Movie)
        {
            mDate.setText(((Movie)object).getReleaseYear() + "");
        }
    }
}

The very last piece of the puzzle is how to register for the result changed events.

Declare the lookup result variable in the class:

private Lookup.Result mResult = null;

And add the following to the constructor of the MoviePanel:

Lookup actionLookup = ExplorerUtils.createLookup(mManager, getActionMap());
mResult = actionLookup.lookupResult(Movie.class);
mResult.addLookupListener(this);
mResult.allInstances();

Compare this to the code from NetBeans Selection Management Tutorial Iโ€”Using a TopComponent’s Lookup:

result = Utilities.actionsGlobalContext().lookupResult(Event.class);
result.addLookupListener (this);

The important change is which lookup gets used to register for the events.

Concluding Remarks

No associateLookup() call is required since the objects are not put onto the global lookup. And in fact, that method is only available to TopComponents.

Feb 012012
 

Observing developers at work in the NetBeans IDE, I have noticed that the project groups feature is perhaps not as well known as one might expect. Despite being listed as one of the Base IDE features. ๐Ÿ™‚ Quickly opening up a set of related projects is a very useful feature indeed, if you work on multiple projects. Or even just if you want to quickly switch between the project you are working on and a sand box environment where you can try new things out.

So I decided to search online for documentation related to this feature, to avoid repeating what others have already written. And I found this gem: How to use Project Groups in NetBeans. If you are not familiar with the concept or practical application of a project group, do read that blog post.

The interesting bit that I would like to add to that, is that there is a New and Noteworthy feature in NetBeans 7.1 related to this: command line options for opening or closing project groups.

Opening the project group called “Test” when IDE starts:
[text]netbeans –open-group Test[/text]

Closing the group that was open when the IDE was last used, if any:
[text]netbeans –close-group[/text]

These command line parameters would be quite useful when demonstrating multiple things with the IDE during training. You could set up a project group for each demonstration, and create a shortcut that includes the relevant parameters to open it up.

The big advantage of having the command line options, however, is being able to get to work on large project groups much faster in some cases. Read the RFE on Bugzilla for more details. ๐Ÿ™‚