NetBeans Platform Porting Tutorial

This tutorial demonstrates how to port a simple Swing application to the NetBeans Platform. Though the sample is simple, the basic concepts of "porting" an application to the NetBeans Platform will become clear. At the end, some general principles will be identified, based on the steps taken in the tutorial. Hopefully, they will be useful to you when porting your own Swing applications to the NetBeans Platform.

Note: This tutorial is out of date! The latest version of this tutorial is found here.

Contents

Content on this page applies to NetBeans IDE 6.1

To follow this tutorial, you need the software and resources listed in the following table.

Software or Resource Version Required
NetBeans IDE version version 6.1 or
version 6.0
Java Developer Kit (JDK) version 6 or
version 5
Download the Sample

Introduction to Porting

Before beginning this procedure, it makes sense to ask why one would want to do so in the first place. A typical Swing application consists of a user interface layer on top of a general framework. The general framework normally provides features dealing with an application's infrastructure, such as an application's menu bar, windowing system (also known as "docking system"), and lifecycle management. Typically this framework is reused by many applications within the same company. The NetBeans Platform exists specifically to cater to these infrastructural concerns. You do not need to create these on your own for your own Swing applications. You can simply move the useful parts of your own application to the NetBeans Platform and then, from that point onwards, the NetBeans Platform will be the new underlying 'plumbing' layer of your application. You can then focus on the more interesting parts of your application, specifically, the user interface. This will speed up your development process and give you a consistent basis for all your applications.

In this tutorial, we will begin with the Anagram Game, which is a standard Swing application sample that is distributed with NetBeans IDE. We will, step by step, move it to the NetBeans Platform and then see the advantages and disadvantages of doing so.

Getting the Anagram Game

We begin by getting the Anagram Game, which is one of the IDE's standard Java samples, from the New Project wizard. Then we run it and analyze its parts.

  1. Choose File > New Project (Ctrl-Shift-N). Under Categories, select Samples > Java. Under Projects, select Anagram Game. Click Next and Finish.

    You should now see the Anagram Game application outlined in the Projects window, as shown here:

    The application contains the following classes:

    • WordLibrary.java. Provides a list of scrambled words, as well as their unscrambled equivalents, together with getters and setters for accessing them.
    • About.java. Provides the About box, accessed from the File menu.
    • Anagrams.java. Provides the main user interface of the application, principally consisting of a JPanel containing labels and text fields. Also included is a menu bar containing a File menu, with the menu items 'About' and 'Exit'.

  2. Run the application and you should see the following:

  3. When you specify the correctly unscrambled word, you will see this:

Before porting this application to the NetBeans Platform, we need to think about the stages in which we want to port our application. In other words, you do not need to port everything at once. And there are different levels to which you can integrate your application, from a mostly superfical level to a level that aligns your application completely with the paradigms and purposes of the NetBeans Platform. The next section will show the levels of compliance your application can have with the NetBeans Platform.

Levels of Compliance

Converting an application to be fit for a framework such as the NetBeans Platform can be done on various levels. The integration can be shallow and use just a few integration points or it can be deeper, tightly following the paradigms of the NetBeans Platform.

The stages can be described as follows:

Level 0: Launchable

One or more of the following can be done to make your application launchable with as few changes as possible:

  • Enhance your manifest with NetBeans key/value pairs so that your JAR is recognized as a NetBeans module.
  • Use dependencies between modules. With instances of plain Class-Path you can set dependencies between modules.
  • Register a menu item. Simply register a menu item in the declarative layer file (layer.xml) of your module, to invoke your original application. This file is created automatically when you create a new module project in NetBeans IDE, as you will see later.

In this tutorial, we will do all of the above. We will enhance the manifest, which the module project wizard will do for us. We will create a menu item that will invoke our application. To do so, we will move our application's classes into a module source structure. Then we will use the New Action wizard to create a new menu item, which will automatically be registered for us, and a dependency will be set on the Utilities API, which we will need to implement the Action. From that action, we will invoke our application.

Level 1: Integrated

Here are some pointers for integrating the application more tightly with the NetBeans Platform:

  • Integrate visually to get the benefits of the NetBeans Window System, which is also known as a docking framework.
  • Use NetBeans Dialog APIs, primarily the TopComponent class and the DialogDisplayer class.
  • Change initialization code of your application, use the ModuleInstall class or declarative registrations, through the layer file or the META-INF/services folder.

In this tutorial, we will first create a JPanel and move the relevant JPanel from the JFrame to the new JPanel. We will use a DialogDisplayer, from within our action, to call the JPanel. The DialogDisplayer integrates nicely with the NetBeans Platform, unlike a JFrame or a JPanel. For example, the DialogDisplayer is modal and it works well with the IDE's integrated JavaHelp. Next, we will move the JPanel to a TopComponent. The TopComponent class creates a window on the NetBeans Platform, which in our case will show our JPanel.

Level 2: Use Case Support

This level of compliance with the NetBeans Platform is concerned with one or more of the following activities:

  • Bind your application to other modules by inspecting existing functionality and trying to use it.
  • Simplify the workflow to fit into the NetBeans Platform paradigms.
  • Listen to the global selection to discover what other modules are doing and update your state accordingly.

In this tutorial, we will listen for the existence of EditorCookies, using the following statement:

EditorCookie ec = activatedNodes[0].getLookup().lookup(EditorCookie.class);

A cookie is a capability and cookies are a powerful feature of NetBeans. With a Java interface, your object's capabilities are fixed at compile time, while NetBeans cookies allow your object to behave dynamically because your object can expose capabilities, or not, based on its state. An EditorCookie defines an editor, with interfaces for common activities such as opening a document, closing the editor, background loading of files, document saving, and modification notifications. We will listen for the existence of such a cookie and then we will pass the content of the editor to the TopComponent, in the form of words. By doing this, we are doing what the first item above outlines, i.e., inspecting existing functionality and reusing it within the context of our ported application. This is a modest level of integration. However, it pays off because you are reusing functionality provided by the NetBeans Platform.

Level 3: Aligned

In this final stage of your porting activity, you are concerned with the following thoughts, first and foremost:

  • Become a good citizen of the NetBeans Platform, by exposing your own state to other modules so that they know what you are doing.
  • Eliminate duplicated functionality, by reusing the Navigator, Favorites window, Task List, Progress API, etc., instead of creating or maintaining your own.
  • Cooperate with other modules and adapt your application to the NetBeans Platform way of doing things.

Towards the end of this tutorial, we will adopt this level of compliance by letting our TopComponent expose a SaveCookie when changes are made to the "Guessed Word" text field. By doing this, we will enable the Save menu item under the Tools menu. This kind of integration brings the full benefits of the NetBeans Platform, however it also requires some effort to attain.

Creating the Module Project Source Structure

First, let's create the source structure of our module. We use a wizard to do so. This is the typical first step of creating a new module for a NetBeans Platform application.

  1. Choose File > New Project (Ctrl-Shift-N). Under Categories, select NetBeans Modules. Under Projects, select Module, as shown below:

Click Next.

  • Type AnagramGamePlugin in Project Name and choose somewhere to store the module, as shown below:

    Click Next.

  • Type a unique name in the Code Name Base field, which provides the unique identifier for your module. It could be anything, but here it is org.netbeans.modules.anagramgameplugin.
  • Also make sure to fill in the XML Layer field, which specifies the location and name of the module's configuration file. By default, the field is empty because not all NetBeans modules need a configuration file. In this case, you will need one, so fill out the field as shown below:

  • Click Finish.

    Below the Anagram Game sample, you should now see the source structure of your new module, as shown here:

    Above, we can see that we now have the original application, together with the module to which it will be ported. In the next sections, we will begin porting the application to the module, using the porting levels described earlier.

    Porting Level 0: Launchable

    At this stage, we simply want to be able to launch our application from a module. Here we will create a menu item that invokes the application. We begin by copying the application's sources into the module source structure.

    1. Copy the two packages from the Anagram Game into the module. Below, the new packages and classes in the module are highlighted:

  • Use the New Action Wizard to create a new menu item. To do so, right-click the module project, choose New > Other and then Module Development > Action. Click Next and then specify that you want a menu item that will always be enabled.
  • Click Next and specify the location of the menu item that will invoke the application. Here, the Window menu is chosen, but it could be any menu at all and it could be between any menu item within the menu:

    Note: The screenshot of the wizard step above is truncated to save space in this tutorial.

  • In the final step of the wizard, specify a name, display name, and package for the action. These could, again, be anything, but the screenshot below shows the values used in this tutorial:

  • Click Finish and the new Java class is added, together with new registration entries in the configuration file, layer.xml.
  • Next, modify the action, so that we can invoke the JFrame from the menu item. To do so, copy the content of the main method from the Anagrams class to the performAction in the action:

    public void performAction() {
        new Anagrams().setVisible(true);
    }
  • Right-click the module in the Projects window and choose Install/Reload in Development IDE or Install/Reload in Target Platform. Depending on which of these you choose, the module will either be installed in the current IDE or in a new instance of the IDE that automatically starts up.
  • Under the Window menu, or whichever menu you specified earlier, you should find the menu item that you created. Choose it and your application appears.

    The application is displayed, but note that it is not well integrated with the NetBeans Platform. For example, it is not modal. In the next section, we will integrate it more tightly with the NetBeans Platform.

    Porting Level 1: Integration

    In this section, we integrate the application more tightly by creating a new window, so that we have a user interface, i.e., a window, to which we can move those contents of the JFrame that are useful to our new application.

    1. Right-click the AnagramGamePlugin in the Projects window and then choose New > Other. Under Categories, select Module Development. Under File Types, select Window Component. Click Next.

    2. Choose the position where you would like the window to appear. For purposes of this tutorial choose "editor", which will place the Anagram game in the main part of the application. Optionally, specify whether the window should open automatically when the application starts up. Click Next.

    3. Type Anagram in Class Name Prefix and select org.netbeans.modules.anagramgameplugin in Package, as shown here:

      Above, notice that the IDE shows the files it will create and modify.

    4. Click Finish.

      Now you have a set of new Java and XML source files, as shown here:

    5. Open the Anagrams class in the Anagram sample, as well as the AnagramTopComponent, which was created in the previous step.

      When you click the mouse in the Anagram class, notice that the labels and text fields are in a Swing container, in this case a JPanel, as shown here:

      Tip: If the Swing components were not within a container, you could select them all with the mouse, then right-click and choose Enclose In, to let the IDE create a container within which all the selected components would be enclosed.

    6. Right-click the JPanel and copy it. Paste it in the TopComponent and you should see the old user interface in your new plugin:

    7. Only one thing needs to be transferred, at this point: the variable declared in the original JFrame. Open the JFrame and copy the variable:

      Paste it into the source view of the TopComponent:

    8. Right-click the module in the Projects window and choose Install/Reload in Development IDE or Install/Reload in Target Platform. Depending on which of these you choose the module will either be installed in the current IDE or in a new instance of the IDE that automatically starts up.

      You should now see the Anagram Game window, which you defined in this section. You will also find a new menu item that opens the window, under the Window menu.

      Also notice that the game works as before. You need to click New Word once, to have the module call up a new word, and then you can use it as before:


    Porting Level 2: Use Case Support

    In this section, we are concerned with listening to the global selection and making use of data we find there.The global selection, also called the default lookup, is the registry for global singletons and instances of objects which have been registered in the system by modules. Here we query the lookup for EditorCookies and make use of the EditorCookie's document to fill the string array that defines the scrambled words displayed in the TopComponent.

    1. We begin by tweaking the WordLibrary class. We do this so that we can set its list of words externally. The sample provides a hardcoded list, but we want to be able to set that list ourselves, via an external action. Therefore, add this method to WordLibrary:

      public static void setScrambledWordList(String[] inScrambledWordList) {
              SCRAMBLED_WORD_LIST = inScrambledWordList;
      }

      Next, we will create an action that will obtain the content of a Java class, break the content down into words, and fill the SCRAMBLED_WORD_LIST string array with these words.

    2. Right-click the project node in the Projects window, choose New > Action, and then choose "Conditionally Enabled" in the first panel of the New Action wizard. Select "EditorCookie" in the Cookie Classes drop-down list, because we want the action to be sensitive to EditorCookies. This means that the action will act on this type of Cookie and only be enabled when one is available from the current selection. Click Next.
    3. Unselect "Global Menu Item". Select "Editor Context Menu Item". In the "Content Type" drop-down list, choose "text/x-java". Click Next, give the action a prefix and display name, then click Finish.
    4. In the Source Editor, redefine the performAction as follows:

      protected void performAction(Node[] activatedNodes) {
          try {
          
              //Look up the EditorCookie for the current node:
              EditorCookie ec = activatedNodes[0].getLookup().lookup(EditorCookie.class);
              
              //Get the EditorCookie's document:
              StyledDocument doc = ec.getDocument();
              
              //Get the complete textual content:
              String all = doc.getText(0, doc.getLength());
              
              //Make words from the content:
              String[] tokens = all.split(" ");
              
              //Pass the words to the WordLibrary class:
              WordLibrary.setScrambledWordList(tokens);
              
              //Open the TopComponent:
              TopComponent win = AnagramTopComponent.findInstance();
              win.open();
              win.requestActive();
              
          } catch (BadLocationException ex) {
              Exceptions.printStackTrace(ex);
          }
      }
    5. Install the module, right-click in the Java editor, and choose the new menu item you see there. The TopComponent opens. When you click to the next word, the words from the Java class appear in the Scrambled Word text field.

    The result of this exercise is that you now see the content of the Java class in the Scrambled Word text field. Of course, these words are not really scrambled and you cannot really unscramble them. However, your module is making use of the content of files supported by a different module altogether, and it is updating its state accordingly.

    Porting Level 3: Aligned

    In this section, we are concerned with becoming a "good citizen" of the NetBeans Platform. We are going to expose the state of the TopComponent to the other modules, so that we can cooperate with them. As an example of this, we will modify the TopComponent to offer a SaveCookie, which gives the user a way to store the text typed in the text field. By offering the SaveCookie when changes are made in the text field, the Save button and the Save menu item under the File menu will become enabled. When the user selects the button or menu item, a dialog will be displayed and the button and menu item will become disabled, until the next time that a change is made to the text field.

    1. Begin by setting a dependency on the Dialogs API, by right-clicking the project node in the Projects window, choosing Properties, and then adding the Dialogs API and the Nodes API in the Libraries panel.
    2. Next, we need to create a node within our TopComponent. We need to do so because we need the TopComponent to expose a SaveCookie, i.e., the capability to save. Such capabilities apply to nodes and therefore we need to create a node, add an implementation of SaveCookie to its set of cookies, and then set the node as the TopComponent's activated node. Here is the node, with comments inline to explain what it is for:

      private class DummyNode extends AbstractNode {
      
          SaveCookieImpl impl;
      
          public DummyNode() {
              super(Children.LEAF);
              impl = new SaveCookieImpl();
          }
      
          //We will call this method, i.e., dummyNode.fire(),
          //from a document listener set on the text field:
          public void fire(boolean modified) {
              if (modified) {
                  //If the text is modified,
                  //we add the SaveCookie implementation to the cookieset:
                  getCookieSet().assign(SaveCookie.class, impl);
              } else {
                  //Otherwise, we make no assignment
                  //and the SaveCookie is not made available:
                  getCookieSet().assign(SaveCookie.class);
              }
          }
      
          private class SaveCookieImpl implements SaveCookie {
      
              public void save() throws IOException {
      
                  Confirmation msg = new NotifyDescriptor.Confirmation("Do you want to save \"" + 
                      guessedWord.getText() + "\"?", NotifyDescriptor.OK_CANCEL_OPTION, 
                      NotifyDescriptor.QUESTION_MESSAGE);
      
                  Object result = DialogDisplayer.getDefault().notify(msg);
      
                  //When user clicks "Yes", indicating they really want to save,
                  //we need to disable the Save button and Save menu item,
                  //so that it will only be usable when the next change is made
                  //to the text field:
                  if (NotifyDescriptor.YES_OPTION.equals(result)) {
                      fire(false);
                      //Implement your save functionality here.
                  }
      
              }
          }
      }

    Declare the DummyNode at the top of the class:

    private DummyNode dummyNode;

  • Next, in the TopComponent's constructor, we set the activated node:

    setActivatedNodes(new Node[]{dummyNode = new DummyNode()});
  • Finally, we listen for change on the text field's document, and fire change events whenever the text field's document changes, by adding this document listener code to the end of the TopComponent's constructor:

    guessedWord.getDocument().addDocumentListener(new DocumentListener() {
    
        public void insertUpdate(DocumentEvent arg0) {
            dummyNode.fire(true);
        }
    
        public void removeUpdate(DocumentEvent arg0) {
            dummyNode.fire(true);
        }
    
        public void changedUpdate(DocumentEvent arg0) {
            dummyNode.fire(true);
        }
        
    });
  • By default, you have a Save menu item under the File menu, but no Save button in the toolbar. For testing purposes, you may want a Save button in the toolbar. For this purpose, add the following to the layer:

    <folder name="Toolbars">
        <folder name="File">
            <file name="org-openide-actions-SaveAction.instance"/>
        </folder>
    </folder>
  • Install the module, make a change in the "Guessed Word" text field, and notice that the Save button and the Save menu item become enabled. Select either the button or the menu item, click Yes in the dialog, and notice that the Save functionality is disabled.

    Congratulations! Now that your module is making use of existing functionality, you have taken one step in successfully aligning it with the NetBeans Platform. Other modules can be now be plugged into the NetBeans Platform to take advantage of, or even extend, features added by your module. Hence, not only can your module benefit from what the NetBeans Platform provides, but you've created something that other modules can use as well.

    You need to continue finding ways to further align your original application with the functionality offered by the NetBeans Platform, in order to make it even more of a good "good citizen" and useful member of the community of modules within the application.

    For example, you can write a new node, with child nodes for each word defined in the class:

    public class WordListNode extends AbstractNode {
    
        private int index;
        private final WordLibrary wordLibrary;
    
        public WordListNode() {
            this(WordLibrary.getDefault());
        }
        
        private WordListNode(WordLibrary w) {
            super(new WordListChildren(w));
            wordLibrary = w;
        }
        
        WordListNode(int index, WordLibrary w) {
            super(Children.LEAF);
            this.index = index;
            this.wordLibrary = w;
            
            setName("Index: " + index);
            setDisplayName(wordLibrary.getWord(index));
        }
    
        @Override
        public String getHtmlDisplayName() {
            return "<b>" + wordLibrary.getWord(index) + "</b> (<i>" + wordLibrary.getScrambledWord(index) + "</i>)";
        }
        
        private static class WordListChildren extends Children.Keys<Integer> {
            private final WordLibrary wordLibrary;
    
            public WordListChildren(WordLibrary wordLibrary) {
                this.wordLibrary = wordLibrary;
            }
            
            @Override
            protected void addNotify() {
                List<Integer> arr = new ArrayList<Integer>();
                for (int i = 0; i < wordLibrary.getSize(); i++) {
                    arr.add(i);
                }
                setKeys(arr);
            }
    
            @Override
            protected void removeNotify() {
                setKeys(Collections.<Integer>emptyList());
            }
            
            @Override
            protected Node[] createNodes(Integer index) {
                WordListNode node = new WordListNode(index, wordLibrary);
                return new Node[] { node };
            }
        }
        
    }

    In return, the lifecycle of the original application is now handled by the NetBeans Platform and you can leverage as much of the existing modules' functionality as is reasonable for your module. In fact, your original application is now no longer an application, but an integral part of a larger application.

    Next Steps

    There are several next steps one can take at this point, aside from further aligning the module with the NetBeans Platform, as outlined above:

    • Become aware of the differences between standard Swing applications and the NetBeans Platform. For the most part, the standard Swing approach to creating a user interface will continue to work for your NetBeans Platform application. However, the NetBeans Platform approach is better, easier, or both in some cases. One example is that of the NetBeans Dialogs API. The standard Swing approach, via, for example, the JOptionsPane, works OK, but using the NetBeans Dialogs API is easier, because it automatically centers your dialog in the application and allows you to dismiss it with the ESC key. Using the Dialogs API also lets you plug in a different DialogDisplayer, which can make it easier to customize or test your application.

      Below is a list of the principle differences between the typical Swing approach and that of the NetBeans Platform:

      • Loading of images
      • Loading of resource bundles and localized string
      • Assigning of mnemonics to labels and buttons
      • Showing dialogs

      For details on all of the above items, read this FAQ: Common calls that should be done slightly differently in NetBeans than standard Swing apps (loading images, localized strings, showing dialogs).

      In addition, note that, since the NetBeans Platform now handles the lifecycle of your module, since it is now part of the whole application, you can no longer use System.exit. Instead, you need to use LifecycleManager. To run code on start up, which should only be done when absolutely necessary, you need to use the NetBeans ModuleInstall class and, specifically, its restored method. A useful reference in this context is Porting a Java Swing Application to the NetBeans Platform, by Tom Wheeler, in Building A Complete NetBeans Platform Application.

    • Create a module project for each distinct part of your application. The NetBeans Platform provides a modular architecture out of the box. Break your application into one or more modules. Doing so requires some analysis of your original application and an assessment of which parts could best fit within a new module and how to communicate between them. Since the example in this tutorial was simple, we only needed one module. A next step might be to put the WordLibrary class in a separate module and expose it as a public API. Doing so would let other modules provide user interfaces on top of the API provided by the first module, as shown here:

      As shown above, you need to put the modules in a module suite. Then set a dependency in the plugin module on the API module, using the Libraries panel in the plugin module's Project Properties dialog box. The size of each module, i.e., when one should create a new module or continue developing within an existing one, is a question of debate. Smaller is better, in general.

    • Always keep reevaluating what you really need to port. Look at the NetBeans Platform and decide where there is overlap with your own application. Where there is overlap, such as the menu bar and About box, decide what you want to do. Typically, you want to leverage as much as possible from the NetBeans Platform. Therefore, you would port as little as possible from your own application, while keeping as much of it as is useful to you.
    • Move distinct parts of your user interface to one or more TopComponents. On the NetBeans Platform, the TopComponent class provides the top level Swing container. In effect, it is a window. Move the user interface from your original application to one or more of these windows and discard your original JFrames.
    • Copy the Java classes that do not provide user interface elements. We simply copied the original WordLibrary.java class. You can do the same with the model of your own Swing applications. You might need to tweak some code to smoothen the transition between the old Swing application and the new NetBeans Platform application, but (as in the case shown in this tutorial) this might not even be necessary.



    Next Steps

    For more information about creating and developing NetBeans modules, see the following resources:

  • Other Related Tutorials
  • NetBeans API Javadoc

  • Project Features

    Project Links

    About this Project

    Platform was started in November 2009, is owned by Antonin Nebuzelsky, and has 149 members.
    By use of this website, you agree to the NetBeans Policies and Terms of Use (revision 20160708.bf2ac18). © 2014, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo
     
     
    Close
    loading
    Please Confirm
    Close