Aug 23, 2010

Migration to (Better) Swing Application Framework

This article was inspired by NetBeans Platform Porting Tutorial. I really respect the NetBeans Application Platform, I use the NetBeans IDE on a daily basis, but this tutorial confused me very much. To port a simple application to this platform I have to do a lot of work. What annoys me the most is a significant amount of textual and XML files with configurations. All configurations are just a plain text without any support of refactoring and any kind of the compile time check. It is only a personal preferences, but I always try to stay away from such error-prone solution.

It must be mentioned, the NetBeans platform accompanied by NetBeans IDE gives a lot of additional services and benefits. An executable binary file, as well as an installer, for a target platform can be easily created for your application by the IDE. As an additional bonus you obtain a well established modular infrastructure. Thus if you are planing to develop a big, scalable, modular, enterprise level application, you definitely should give it a try. But in this article we will talk about small or middle size applications. Anagram Game is a good example for such kind of applications. Lets try to follow the tutorial, but use BSAF instead of the NetBeans Platform. First of all you need to create an application from the samples folder. I will use NetBeans IDE for this example:

1. Ctrl+Shift+N - to open a New Project Dialog
2. Select Samples/Java/Anagram Game project and press "next" button


3. Specify name and location for a new project and press button "finish"



Now you are ready to migrate this sample application to BSAF.

Porting level 0: Launchable
At this stage, we simply want to be able to launch our application...
BSAF is extremely simple and straightforward library that is why we don't need to do any strange things like a redundant menu entry to show a window of our application. All we need is 4 easy steps:
1. Add BSAF library to the project (the process is discussed in more details in the article Getting Started using BSAF with NetBeans)
2. Create an application class AnagramsApplication derived from the SingleFrameApplication.
3. Implement startup() and main() methods as following:

package com.toy.anagrams.ui;
import org.jdesktop.application.SingleFrameApplication;
public class AnagramsApplication extends SingleFrameApplication {
  public static void main (String[] args) {
    Application.launch(AnagramsApplication.class, args);
  }
  protected void startup() {
    show(new Anagrams());
  }
}

4. Run the application.

Porting level 1: Integration
Is spite of the simplicity, BSAF provides many convenient features for developers. Lets use them in our application. First of all we don't need to create any windows (JFrame) by ourself, the framework can do it for us. Thus we need to slightly refactor our application:
1. Override initialize() method of the AnagramsApplication. Move there all initialization code from the Anagrams class.

  protected void initialize(String[] args) {
    super.initialize(args);
    wordLibrary = WordLibrary.getDefault();
  }

2. Implement method createMenuBar() for the AnagramsApplication. Use menu bar creation code from the Anagrams class.
3. Implement method createComponent() for the AnagramsApplication. Use panel creation code from the Anagrams class.
4. Move event handlers from the Anagrams class to the AnagramsApplication.
5. Rewrite method startup() as following:

  protected void startup() {
    FrameView mainView = getMainView();
    mainView.setMenuBar(createMenuBar());
    mainView.setComponent(createComponent());    
    mainView.getRootPane().setDefaultButton(guessButton);
    
    scrambledWord.setText(wordLibrary.getScrambledWord(wordIdx));

    show(mainView);
  }
6. Remove the Anagrams class because we don't need it any more.
7. Run the application

Now it looks as before, but the code is reduced. Now all window's initialization/positioning stuff is covered by the framework. The window state will be stored and restored automatically.

Porting Level 2: Use Case Support

Our current implementation of the event handlers looks ugly. With BSAF we don't have to write a lot of code for such a common task. With simple annotations we can create actions for our application's use cases. Please pay attention that the framework provides a set of useful actions. "Quit" is one of such actions. Another useful facility of the framework is resource management. We can provide all actions related information in declarative way through properties file. Also properties of all visual component could be injected, all we need is supply components with names.

1. Transform event handlers into actions:

  @Action
  public void aboutMenuItemActionPerformed() {
    new About(getMainFrame()).setVisible(true);
  }

  @Action
  public void nextTrialActionPerformed() {
    wordIdx = (wordIdx + 1) % wordLibrary.getSize();

    feedbackLabel.setText(" ");
    scrambledWord.setText(wordLibrary.getScrambledWord(wordIdx));
    guessedWord.setText("");
    getMainView().getRootPane().setDefaultButton(guessButton);

    guessedWord.requestFocusInWindow();
  }

  @Action
  public void guessedWordActionPerformed() {
    if (wordLibrary.isCorrect(wordIdx, guessedWord.getText())) {
      feedbackLabel.setText("Correct! Try a new word!");
      getMainView().getRootPane().setDefaultButton(nextTrial);
    } else {
      feedbackLabel.setText("Incorrect! Try again!");
      guessedWord.setText("");
    }

    guessedWord.requestFocusInWindow();
  }

2. Use action for menu items and buttons creation:

  private JMenuBar createMenuBar() {
    ApplicationActionMap actionMap = getContext().getActionMap();

    JMenuBar menuBar = new JMenuBar();
    JMenu fileMenu = new JMenu();
    fileMenu.setName("fileMenu");
    fileMenu.add(new JMenuItem(actionMap.get("aboutMenuItemActionPerformed")));
    fileMenu.add(new JMenuItem(actionMap.get("quit")));

    menuBar.add(fileMenu);

    return menuBar;
  }

private JComponent createComponent() {
  ApplicationActionMap actionMap = getContext().getActionMap();
  ...
  guessButton = new JButton(actionMap.get("guessedWordActionPerformed"));
  ...
  nextTrial = new JButton(actionMap.get("nextTrialActionPerformed"));
}

3. Create resource folder and properties file (/com/toy/anagrams/ui/resources/AnagramsApplication.properties):

# Application's properties
Application.name = Anagram Game
Application.title = Anagrams
Application.version = 1.0
Application.vendor = Illya Yalovyy
Application.homepage = http://kenai.com/projects/bsaf/
Application.vendorId = etf
Application.id = Anagrams
Application.lookAndFeel = com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel
Application.icon = icon.png

# Actions
aboutMenuItemActionPerformed.Action.text = &About
aboutMenuItemActionPerformed.Action.shortDescription = Show the application's information dialog

nextTrialActionPerformed.Action.text = &New Word
nextTrialActionPerformed.Action.shortDescription = Fetch a new word.

guessedWordActionPerformed.Action.text = &Guess
guessedWordActionPerformed.Action.shortDescription = Guess the scrambled word.

# Components
scrambledLabel.text = Scrambled Word:

scrambledWord.columns = 20
scrambledWord.editable = false

guessLabel.text = &Your Guess:

# Menu
fileMenu.text = &File

4. Done.



Conclusion

It is obvious that porting to BSAF is easy like a piece of cake. What is more important, the framework provides a handful of useful features while maintaining a great deal of simplicity. Let's consider some figures.

1. Resulting binary size:


Size, bytes
Original Application 23095
BSAF Application 206168
NetBeans Application 18596885



2. Source size:



3. Application start time:

References

5 comments:

P e t e said...

Thanks for this tutorial, well done.

brad said...

Illya

I started working in NetBeans making Java desktop applications with the SAF, since they are dropping support after 7.0 what would you recommend?

Illya Yalovyy said...

Good question. Personally I use maven project. It could be used with any popular IDE. The only thing that was dropped in NB is the visual editor. I didn't use it anyway. For really complex GUI forms it is more efficient to use "Builder" pattern.

Mary Aubaun said...

I have an application I started 2 years ago using SAF, it has about 50 panels/screens; I create menus manually using Swing.

Netbeans 7.1 (no SAF support) seems to have a screen/panel designer still, and it seems to generate nearly the same code as Netbeans 7.0 which was doing SAF.

I've read a bunch of articles and am at a loss (hopefully it's one or two small things I'm not getting!). I don't quite understand what the differences are, but if I am going to convert to BSAF, what would I have to do?

gilbertoca said...

It is back as module! See[1]!

Regards,


[1]http://netbeans.org/bugzilla/show_bug.cgi?id=204661#c59