Monday, November 23, 2009

News - Swing-Generics (OpenJDK): JList patch commited

Great news from the OpenJDK swing-generics project: my patch to "generify" JList, along with AbstractListModel, DefaultListCellRenderer, DefaultListModel, ListCellRenderer and ListModel, has been commited to the OpenJDK/ Swing repository!

This patch helps writing more stable code.

Eg. instead of writing something like:


with this patch you can write now:


Note that:
JList.getSelectedValues(): Object[] has been deprecated and replaced with:
JList.getSelectedValuesList(): List<E>

Have a look at the jtreg tests to see more examples.

If you're interested in the future of Swing, join the discussions in the OpenJDK Swing mailing list.

Saturday, October 31, 2009

NetBeans Platform: JNLP & static codebase

Recently the question was asked on the dev@openide.netbeans.org mailing list, how to deploy a JNLP NetBeans Platform application to a web server, which doesn't support WAR-files.
I had the same problem when I wanted to deploy the sample application of my last NetBeans Platform post (NetBeans Platform meets Swing Application Framework: SessionStorage), which is a JNLP NetBeans Platform application, too, that I host along with its source at sourceforge.net.

Here are the build.xml and platform.properties I used:
The Ant script will create the file "${nbdist.dir}/${app.name}-updated.war". Just copy it to your web server and unzip it there. Then link to the master.jnlp to provide a starting-point to your application, eg. by creating a web start launch button.


Here is the complete code:
http://puces-samples.svn.sourceforge.net/viewvc/puces-samples/tags/sessionstate-1.0/

You can run this sample here:
http://puces-samples.sourceforge.net/
or here:
http://puces-blog.blogspot.com/2009/04/netbeans-platform-meets-swing.html
(at the bottom of the page)

Sunday, September 27, 2009

Technorama: The Magic of Software

Last Friday I visited the special exhibition "The Magic of Software" at the Technorama Winterthur, Switzerland. It's an event to the 20th anniversary of Microsoft Switzerland. Since Noser Engineering AG (the company I work for) is Microsoft Gold Partner, we got the chance to provide an application to this exhibition, of which I helped to implement a small part.

We implemented a multi-touch truck disposition game. The user has to try to distribute a list of orders to a fixed number of trucks. The more order you can deliver in the given time the more points you get.

At this technological excursion I got the chance to work with some of the newest Microsoft technologies:
  • .NET 4.0 Beta (WPF, C#, XAML)
  • Windows 7 (RC)
  • Visual Studio Team System 2010 (Beta)
The software runs on a HP TouchSmart.

Here are some pictures of the application at the exhibition (I took them with my cell phone, the quality isn't optimal):






The exhibition is open until the 31th October, 2009. Try to beat my result! As you can see on the second picture, that shouldn't be hard. ;-) (rank: 111; points: 42'605)

Sunday, July 19, 2009

NetBeans Platform meets Swing Application Framework: SessionStorage

In this post I want to explain how to use the Swing Application Framework (JSR-296) as a utility library in a NetBeans Platform application to reuse its session storage feature. Since I haven't found any documentation about this topic so far, I devote the first post of my new blog to this.

In the following we create a sample application to demonstrate the problem space. We will use a JXTable (SwingX) and want the application to remember things like:
  • column order
  • visible/ hidden columns
  • column widths
  • sorting info
Since the NetBeans Platform seems to provide only some very generic hooks like state serialization, Preferences API support and Settings API to persist session data, one would have to implement a lot of the details in a custom way.

The Swing Application Framework can help out here however with its session storage feature.

To make the session storage feature work inside a NetBeans Platform application, some issues have to be solved:
First, the LocalStorage class of the Swing Application Framework has an algorithm to find a suitable storage directory for various situations. The NetBeans Platform however has already its own place to store configuration data. And more, since a NetBeans Platform is modular, it would be preferable if each module would store its session storage data in its own directory to keep that data structured modularly as well and to avoid naming conflicts. Fortunately, the LocalStorage class provides a setDirectory method to specify a different storage directory. This leads us however to the next issue: when running the application in JNLP mode the directory property of the LocalStorage class is ignored. Thus we also need to subclass the LocalStorage class if JNLP support is needed. I filed an enhancement request, which should make things easier once fixed:
https://appframework.dev.java.net/issues/show_bug.cgi?id=112

A LocalStorage can be retrieved from an ApplicationContext, so we create our own ApplicationContext subclass - ModuleApplicationContext - and reset the directory property of the LocalStorage in the contructor with a path to a module specific directory inside the NetBeans Platform configuration directory:

public class ModuleApplicationContext extends ApplicationContext {

private static final String SESSION_STORAGE_DIR = "SessionStorage"; // NOI18N
private static final Object LOCK = new Object();

public ModuleApplicationContext(ModuleInfo moduleInfo) {
getLocalStorage().setDirectory(getModuleSessionStorageDir(moduleInfo));
}

private File getModuleSessionStorageDir(ModuleInfo moduleInfo) {
FileObject root = FileUtil.getConfigRoot();
try {
synchronized (LOCK) {
FileObject sessionStorageDir = getSessionStorageDir(root);
FileObject moduleStorageDir = getModuleSessionStorageDir(
sessionStorageDir, moduleInfo);
return FileUtil.toFile(moduleStorageDir);
}
} catch (IOException ex) {
Logger.getLogger(ModuleApplicationContext.class.getName()).logp(
Level.SEVERE, ModuleApplicationContext.class.getName(),
"getModuleSessionStorageDir", ex.getMessage(), ex); // NOI18N
return FileUtil.toFile(root);
}
}

private FileObject getSessionStorageDir(FileObject root) throws IOException {
FileObject sessionStorageDir = root.getFileObject(SESSION_STORAGE_DIR);
if (sessionStorageDir == null) {
sessionStorageDir = root.createFolder(SESSION_STORAGE_DIR);
}
return sessionStorageDir;
}

private FileObject getModuleSessionStorageDir(FileObject sessionStorageDir,
ModuleInfo moduleInfo) {
String moduleStorageDirName = getModuleDirName(moduleInfo);
FileObject moduleStorageDir = sessionStorageDir.getFileObject(
moduleStorageDirName);
if (moduleStorageDir == null) {
try {
moduleStorageDir =
sessionStorageDir.createFolder(moduleStorageDirName);
} catch (IOException ex) {
Logger.getLogger(ModuleApplicationContext.class.getName()).logp(
Level.SEVERE, ModuleApplicationContext.class.getName(),
"getModuleSessionStorageDir", ex.getMessage(), ex); // NOI18N
moduleStorageDir = sessionStorageDir;
}
}
return moduleStorageDir;
}

private String getModuleDirName(ModuleInfo moduleInfo) {
try {
return URLEncoder.encode(moduleInfo.getCodeName(), "UTF-8"); // NOI18N
} catch (UnsupportedEncodingException ex) {
// should not happen with UTF-8
Logger.getLogger(ModuleApplicationContext.class.getName()).logp(
Level.SEVERE, ModuleApplicationContext.class.getName(),
"getModuleDirName", "UTF-8 not supported!", ex); // NOI18N
return moduleInfo.getCodeNameBase();
}
}
}

We add this class to a Utility Library module and add its package (here: blogspot.puce.sessionstate.util.modules) to the list of public packages. Later we can add a dependency to this module and reuse this class.
We also create a Library Wrapper Module for the Swing Application Framework and add a dependency from the Utility Library module to this module.

As mentioned before, to support JNLP mode, we need to create a subclass of LocalStorage, which we call ModuleLocalStorage and which respects the directory property:


public class ModuleLocalStorage extends LocalStorage {

public ModuleLocalStorage(ApplicationContext context) {
super(context);
}

@Override
public boolean deleteFile(String fileName) throws IOException {
File path = new File(getDirectory(), fileName);
return path.delete();
}

@Override
public InputStream openInputFile(String fileName) throws IOException {
File path = new File(getDirectory(), fileName);
return new BufferedInputStream(new FileInputStream(path));
}

@Override
public OutputStream openOutputFile(String fileName) throws IOException {
File path = new File(getDirectory(), fileName);
return new BufferedOutputStream(new FileOutputStream(path));
}
}
We add this class to the same package as ModuleApplicationContext and change the constructor of ModuleApplicationContext to register this LocalStorage:


public ModuleApplicationContext(ModuleInfo moduleInfo) {
// Needed due to issue https://appframework.dev.java.net/issues/show_bug.cgi?id=112
setLocalStorage(new ModuleLocalStorage(this));

getLocalStorage().setDirectory(getModuleSessionStorageDir(moduleInfo));
}

To support storage of the properties of JXTable, we need a SessionStorage.Property implementation for JXTable. Fortunatly, Jeanette (user name: kleopatra) provided such an implemenation in the jdnc-incubator project. At the time of writing, it's just available in source form. Here is the link to the version we use in this sample application:
https://jdnc-incubator.dev.java.net/source/browse/jdnc-incubator/src/kleopatra/java/org/jdesktop/appframework/swingx/XProperties.java?rev=1.3&view=markup

We can add this class to the Utility Libarary module as well.

In order to use this implementation we add a static initializer to the ModuleApplicationContext class and change its constructor:


static {
new XProperties().registerPersistenceDelegates();
}

public ModuleApplicationContext(ModuleInfo moduleInfo) {
// Needed due to issue https://appframework.dev.java.net/issues/show_bug.cgi?id=112
setLocalStorage(new ModuleLocalStorage(this));

getLocalStorage().setDirectory(getModuleSessionStorageDir(moduleInfo));
getSessionStorage().putProperty(JXTable.class,
new XProperties.XTableProperty());
}


We also create another Library Wrapper Module for the SwingX library and add a dependency from the Utility Library module to this module.

To create an instance of
the ModuleApplicationContext class, we need to pass the ModuleInfo of the module of the calling class to its constructor. Unfortunatly, there is no method to get the ModuleInfo for a class, so we have to iterate over all ModuleInfos. I filed an issue:
http://www.netbeans.org/issues/show_bug.cgi?id=157828

Here is the work-around we can use:


public static ModuleInfo getModuleInfo(Class clazz) {
Collection moduleInfos = Lookup.getDefault().
lookupAll(ModuleInfo.class);
for (ModuleInfo moduleInfo : moduleInfos) {
if (moduleInfo.owns(clazz)) {
return moduleInfo;
}
}
return null;
}

We put this method to a new utility class called Modules and add this class to the same package as ModuleApplicationContext.

Here are the complete classes:
Now we have all parts together and we can use them in other modules. So let's see them in action! We make a sample module and add a dependency to our Utility Library module and the two Library Wrapper modules for SwingX and the Swing Application Framework. To this sample module we add a Module Installer, which initializes and holds a reference to a ModuleApplicationContext:


public class Installer extends ModuleInstall {

private static ModuleApplicationContext applicationContext;

@Override
public void restored() {
applicationContext = new ModuleApplicationContext(Modules.getModuleInfo(
Installer.class));
}

public static ModuleApplicationContext getApplicationContext() {
return applicationContext;
}
}

Then we add a TopComponent to our sample module, which we call JXTableSampleTopComponent. We change its LayoutManager to BoderLayout and add a panel, which we call contentPanel, with BorderLayout.CENTER to the JXTableSampleTopComponent.

Inside this sample application we want to show some sample data in a JXTable. The following Java/ Swing classes display a list of participants:
Apart from the NbBundle to localize the column names, they don't use any NetBeans Platform APIs.

We add now an instance of ParticipantsPane, which contains the JXTable, to the contentPanel of JXTableSampleTopComponent. Below the ParticipantsPane we add a button. Since the session storage feature of the Swing Application Framework relies on names set to the components, we must make sure that the components do have a name set: In the Form Designer select the root in the Inspector tab. Then in the Properties tab set the "Set Component Names" property to true. It's easy to forget to set the component names sometimes. If you think your persistence code should work, but it doesn't, check if you've set the component names!

In the constructor of JXTableSampleTopComponent we initialize the participants list field and assign it to the participants property of the ParticipantsPane instance:


private final List<Participant> participants;

public JXTableSampleTopComponent() {
initComponents();
setName(NbBundle.getMessage(JXTableSampleTopComponent.class,
"CTL_JXTableSampleTopComponent")); // NOI18N
setToolTipText(NbBundle.getMessage(JXTableSampleTopComponent.class,
"HINT_JXTableSampleTopComponent")); // NOI18N
// setIcon(ImageUtilities.loadImage(ICON_PATH, true));

participants = new ArrayList<Participant>();
// some fictional participants
participants.add(new Participant("Carl", "Smith", 25)); // NOI18N
participants.add(new Participant("Lisa", "Collins", 27)); // NOI18N
participants.add(new Participant("Martin", "Brooks", 34)); // NOI18N

participantsPane.setParticipants(participants);

}

To make the column order, column visibility, column widths and sorting info of the JXTable in our TopComponent persistent, we change the writeProperties- and readPropertiesImpl-methods of the JXTableSampleTopComponent. There we call the save/ restore methods of the SessionStorage of the ApplicationContext, which is referenced by our Installer. Note that due to a bug in the Swing Application Framework we need to remove the contentPane from the TopComponent before we save/ restore it and re-add later. I filed an issue: https://appframework.dev.java.net/issues/show_bug.cgi?id=94


private static final String PREFERRED_ID = "JXTableSampleTopComponent"; // NOI18N
private static final String SESSION_STORAGE_XML = PREFERRED_ID + ".xml"; // NOI18N

void writeProperties(java.util.Properties p) {
// better to version settings since initial version as advocated at
// http://wiki.apidesign.org/wiki/PropertyFiles
p.setProperty("version", "1.0");
saveSession();
}

private void saveSession() {
// remove and add needed due to a bug in the Swing Application Framework:
// https://appframework.dev.java.net/issues/show_bug.cgi?id=94
remove(getContentPanel());
try {
Installer.getApplicationContext().getSessionStorage().save(
getContentPanel(), SESSION_STORAGE_XML);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
add(getContentPanel(), BorderLayout.CENTER);
}

private JPanel getContentPanel() {
return contentPanel;
}

private void readPropertiesImpl(java.util.Properties p) {
String version = p.getProperty("version");
// TODO read your settings according to their version

// remove and add needed due to a bug in the Swing Application Framework:
// https://appframework.dev.java.net/issues/show_bug.cgi?id=94
remove(getContentPanel());
try {
Installer.getApplicationContext().getSessionStorage().
restore(getContentPanel(), SESSION_STORAGE_XML);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
add(getContentPanel(), BorderLayout.CENTER);
}
This is it! The column order, column visibility, column widths and sorting info of the JXTable of our TopComponent are persistent! Now let's look how to make these persistent if the JXTable is inside a dialog instead. For this we add an action listener to the button we added below the JXTable. Inside the action listener method we create a new instance of the ParticipantsPane and assign the participants list field to its participants property. Then we create a DialogDescriptor with a close button and the ParticipantsPane as its content. Since the session storage feature of the Swing Application Framework already supports dialogs - by default a SessionStorage.Property for Window is registered, which makes the dimension and position of a window persistent - we want to benefit from that and create a Dialog object from the DialogDescriptor rather than using one of the notify methods of the DialogDisplayer. After creating the dialog object and setting its name (remember that the session storage feature relies on names set to the components), we register a PropertyChangeListener to the DialogDescriptor to get notified, when the dialog is closed, so we can call there again the save method of the SessionStorage of the ApplicationContext, which is referenced by our Installer, to make the mentioned properties persistent. Note that we use a different file name for the dialog than for the TopComponent, so that one does not overwrite the other. After defining the PropertyChangeListener we call the restore method of the SessionStorage and set the visible property of the dialog to true to show it.


private static final String PARTICIPANTS_DIALOG_XML =
"participantsDialog.xml"; // NOI18N

private void participantsDialogButtonActionPerformed(java.awt.event.ActionEvent evt) {
ParticipantsPane pp = new ParticipantsPane();

pp.setParticipants(participants); // NOI18N
pp.setName("participantsPane"); // NOI18N

DialogDescriptor dialogDescriptor = new DialogDescriptor(pp, NbBundle.
getMessage(JXTableSampleTopComponent.class,
"JXTableSampleTopComponent.ParticipantsDialog.title")); // NOI18N
final JButton closeButton =
new JButton(NbBundle.getMessage(JXTableSampleTopComponent.class,
"JXTableSampleTopComponent.ParticipantsDialog.button.text")); // NOI18N
dialogDescriptor.setOptions(
new Object[]{closeButton});
final Dialog participantsDialog =
DialogDisplayer.getDefault().createDialog(dialogDescriptor);
participantsDialog.setName("participantsDialog"); // NOI18N
dialogDescriptor.addPropertyChangeListener(new PropertyChangeListener() {

public void propertyChange(PropertyChangeEvent evt) {

if (evt.getPropertyName().equals(DialogDescriptor.PROP_VALUE) &&
((evt.getNewValue() == DialogDescriptor.CLOSED_OPTION) ||
(evt.getNewValue() == closeButton))) {
try {
Installer.getApplicationContext().getSessionStorage().
save(participantsDialog, PARTICIPANTS_DIALOG_XML);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
}
});

try {
Installer.getApplicationContext().getSessionStorage().
restore(participantsDialog, PARTICIPANTS_DIALOG_XML);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
participantsDialog.setVisible(true);
}
Now the mentioned dialog properties and the properties of its JXTable are persistent!
Here are the complete classes:
When you run this sample application from inside the NetBeans IDE, you should see something like the following inside your suite-directory:



The XML files, which contain the session storage data, are inside the module specific directory "blogspot.puce.sessionstate.sample". This directory is inside the SessionStorage directory we created inside the NetBeans Platform application specific configuration directory.

You can have a look at this sample application using Java WebStart:


Try to:
  • reorder the columns
  • change the column visibility by clicking on the button on the top-right side of the JXTable
  • change the column widths
  • change the column sorting by clicking on the column headers
  • click the button below the table and do the same for the JXTable inside the dialog
  • change the size of the dialog
  • change the position of the dialog
  • close and reopen the dialog and see how all these properties are persistent
  • close and restart the application and see how all these properties of the TopComponent and dialog are persistent
Summary:
This post showed you how we can reuse parts of the Swing Application Framework inside a NetBeans Platform application to make properties of components persistent. This could become especially interesting if the Swing Application Framework makes it into JDK 7. No Library Wrapper Module would be needed anymore and we might see SessionStorage.Property implementations for many more components.

Possible next step:
Create
SessionStorage.Property implementations for the explorer views of the NetBeans Platform APIs.

What do you think? Was this post interesting? Do you think this proposed solution for persisting component properties is useful? Do you have any code improvements? Please let me know of your thoughts and comments!

Downloads:
Versions:
  • Java SE 6
  • NetBeans Platform/ IDE v6.7
  • Swing Application Framework v1.03
  • SwingX v0.9.5