Declarative Asynchronous Actions in NetBeans Platform 6.8

When you create actions for a node (such as when you want to display the node in an explorer view), one way of doing so is like this, within your DataNode extension class:
@Override
public Action[] getActions(boolean context) {
    Action[] result = new Action[]{
        new MyAction(),
    };
    return result;
}

However, it is much smarter to register your actions in the System FileSystem (via the module's layer.xml file). (You do this in the "Loaders" folder, within the subfolder for the MIME type.) Why is this smart? Because the layer lets you declaratively assign attributes to your action. And some of those attributes are extremely powerful.

In particular, NetBeans Platform 6.8 will introduce a new attribute for actions registered in the layer, enabling an action to be executed asynchronously. (Read about it here.) So, without needing to write any code at all (for which I've been using the NetBeans API RequestProcessor class), you'll let the user continue working while an action is being processed. That's pretty cool. Let's see it in action now (already possible since NetBeans Platform 6.8 Milestone 1.)

Let's say we want to create support for PostScript files. Specifically, we'd like to generate PDF files from PS files, using the "ps2pdf" command line utility (which works wonderfully, at least, on Ubuntu). Obviously, after the user has selected our "Make PDF" menu item, on the PS node, we'd like the IDE to stay enabled so that work can continue to be done while processing of the PDF happens in the background.

The main NetBeans API classes that will be used:

Implement the above scenario with NetBeans Platform 6.8 as follows:

  1. Use the New File Type wizard to recognize PostScript files by their extension, i.e., ".ps".

  2. Next, we implement ActionListener, using the NetBeans NbProcessDescriptor class to call the "ps2pdf" utility, passing in its required arguments (an input PS file and an output PDF file):
    public class Ps2PdfAction implements ActionListener {
    
        FileObject fo;
    
        public Ps2PdfAction() {
            fo = Utilities.actionsGlobalContext().lookup(FileObject.class);
        }
    
        @Override
        public void actionPerformed(ActionEvent e) {
    
            File theFile = FileUtil.toFile(fo);
            File theFolder = FileUtil.toFile(fo.getParent());
            Long timeStamp = System.currentTimeMillis();
    
            String input = theFile.getAbsolutePath();
            String output = theFolder.getAbsolutePath() + "/" +
                    theFile.getName() + "-" + timeStamp + ".pdf";
            
            try {
                NbProcessDescriptor desc = new NbProcessDescriptor("ps2pdf", input + " " + output);
                Process p = desc.exec();
                p.waitFor();
            } catch (InterruptedException ex) {
                Exceptions.printStackTrace(ex);
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            }
    
        }
        
    }

  3. We can get the FileObject from the DataNode because we know the selected node is proxied by Utilities.actionsGlobalContext. So, when we right-click the DataNode, its FileObject finds itself in the global context, where we find it in our action, and which we can then use to generate our PDF.

    Now, this ActionListener is in a separate class and there is no code in our DataNode to connect the ActionListener to the DataNode. Enter the layer file! I register the ActionListener as follows, using the MIME type of my PostScript file support, which is "application/ps". Take note of the line in bold:

    <folder name="Actions">
        <folder name="File">
            <file name="org-demo-makemepdf-Ps2PdfAction.instance">
                <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.alwaysEnabled"/>
                <attr name="delegate" newvalue="org.demo.makemepdf.Ps2PdfAction"/>
                <attr name="displayName" stringvalue="Make me PDF..!"/>
                <attr name="noIconInMenu" stringvalue="false"/>
                <attr name="asynchronous" boolvalue="true"/>
            </file>
        </folder>
    </folder>
    <folder name="Loaders">
        <folder name="application">
            <folder name="ps">
                <folder name="Actions">
                    <file name="org-demo-makemepdf-Ps2PdfAction.shadow">
                        <attr name="originalFile" stringvalue="Actions/File/org-demo-makemepdf-Ps2PdfAction.instance"/>
                        <attr name="position" intvalue="100"/>
                    </file>
    

    Note: In production code, you'd use "bundlevalue" instead of "stringvalue" for the "displayName" attribute and then point to a key in the bundle.

  4. Now the action will be connected to the DataNode and, when invoked from its menu item, will process asynchronously. For good measure, a busy cursor will be shown, though work can continue.

    Let's be even friendlier to our users and also display a progress bar, using the Progress API. Add the lines in bold to the ActionPerformed:

    @Override
    public void actionPerformed(ActionEvent e) {
    
        File theFile = FileUtil.toFile(fo);
        File theFolder = FileUtil.toFile(fo.getParent());
        Long timeStamp = System.currentTimeMillis();
    
        String input = theFile.getAbsolutePath();
        String output = theFolder.getAbsolutePath() + "/" +
                theFile.getName() + "-" + timeStamp + ".pdf";
    
        ProgressHandle ph = ProgressHandleFactory.createHandle("PDF Generation");
        ph.start();
        ph.switchToIndeterminate();
        ph.setDisplayName("Generating PDF...");
    
        try {
            NbProcessDescriptor desc = new NbProcessDescriptor("ps2pdf", input + " " + output);
            Process p = desc.exec();
            p.waitFor();
        } catch (InterruptedException ex) {
            Exceptions.printStackTrace(ex);
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }
    
        ph.finish();
    
    }

  5. Finally, let's display the output from the "ps2pdf" utility in the Output window of NetBeans IDE, so that we can see exactly what's going on during the processing of the PostScript file. In this case, which you can only figure out via trial and error, the relevant information comes from the error stream of the "ps2pdf" utility:
    @Override
    public void actionPerformed(ActionEvent e) {
    
        File theFile = FileUtil.toFile(fo);
        File theFolder = FileUtil.toFile(fo.getParent());
        Long timeStamp = System.currentTimeMillis();
    
        String input = theFile.getAbsolutePath();
        String output = theFolder.getAbsolutePath() + "/" +
                theFile.getName() + "-" + timeStamp + ".pdf";
    
        ProgressHandle ph = ProgressHandleFactory.createHandle("PDF Generation");
        ph.start();
        ph.switchToIndeterminate();
        ph.setDisplayName("Generating PDF...");
    
        InputOutput io = IOProvider.getDefault().getIO("Producing PDF", false);
        io.select();
        final OutputWriter writer = io.getOut();
    
        try {
            String line = null;
            NbProcessDescriptor desc = new NbProcessDescriptor("ps2pdf", input + " " + output);
            Process p = desc.exec();
            BufferedReader reader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            while ((line = reader.readLine()) != null) {
                writer.println(line);
            }
            p.waitFor();
        } catch (InterruptedException ex) {
            Exceptions.printStackTrace(ex);
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }
    
        ph.finish();
    
    }

You should now have this:

Comments:

Hello,

nice article. I found it searching for Output Window and asynchronous. The reason is that I have problems to call my writer.println("") commands from within a parall Thread which is running beside the EDT. The resulting data is only displayed after the parallel Thread has finished. If someone knows how to deal with this problem, please let me know. Thanks a lot.

Regards,
Klaus

Posted by Klaus Martinschitz on September 04, 2011 at 08:02 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

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.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today