August 2006 Archives
Swing made easy with genesis
Posted by mister__m on August 25, 2006 at 10:43 AM | Permalink
| Comments (13)
Swing was always known as a powerful, highly configurable UI toolkit. However, not much longer after it was born, it was also regarded as a slow, hard to learn, confusing, hard to program toolkit. Sun first started working on performance and Swing became faster and lighter - if you only knew how to code make a GUI with it. Designing some interfaces could take hours (or days) and since there are many ways of accomplishing (almost) the same thing in Swing, developers would usually get confused or pick the wrong road. Visual designers such as VEP and later Matisse came and made it simple to design the GUI. However, working with Swing still required understanding models, writing listeners and dealing with the many choices offered by the API.
An easier programming model was needed and then binding frameworks started to appear. genesis was born two years ago and it has been supporting GUI-toolkit independent binding for more than a year and a half now. At first, only Thinlet was supported and we were always asked about when it would support Swing. Well, since the beginning of the year a Swing binding has been implemented and now it has finally been released.
What makes the binding implemented by genesis unique is that it doesn't require you to use any "proprietary" components and it doesn't require you to code listeners (neither in the interface nor the JavaBean). So you can design your interface using Matisse, write your JavaBean class and just use a couple of annotations to bring it to life. Let's see how it works in practice. Let's say we would like to implement a login use case. We could code the UI handling JavaBean, called a form, like this :
@Form
public class LoginForm {
private String user;
private String password;
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Action
public void login() {
System.out.println(user);
System.out.println(password);
}
@Action
public void clear() {
setUser(null);
setPassword(null);
}
}
And bind it to a Swing UI like this:
@ViewHandler
public class LoginSwingView extends JDialog {
public LoginSwingView() {
super(new JFrame(), "Login");
initComponents();
SwingBinder binder = new SwingBinder(this, new LoginForm());
binder.bind();
}
private void initComponents() {
getContentPane().setLayout(new GridLayout(2, 1));
JPanel dataPanel = new JPanel();
dataPanel.setLayout(new GridLayout(2, 2, 5, 5));
JLabel labelUser = new JLabel();
labelUser.setText("User");
dataPanel.add(labelUser);
JTextField user = new JTextField();
user.setName("user");
dataPanel.add(user);
JLabel labelPassword = new JLabel();
labelPassword.setText("Password");
dataPanel.add(labelPassword);
JPasswordField password = new JPasswordField();
password.setName("password");
dataPanel.add(password);
getContentPane().add(dataPanel);
JPanel buttonPanel = new JPanel();
JButton login = new JButton();
login.setText("Login");
login.setName("login");
buttonPanel.add(login);
JButton clear = new JButton();
clear.setText("Clear");
clear.setName("clear");
buttonPanel.add(clear);
getContentPane().add(buttonPanel);
pack();
setLocationRelativeTo(null);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new LoginSwingView().setVisible(true);
}
});
}
}
So, as this example shows, genesis binds JavaBeans properties to widgets such as JLabels, JTextFields and others based on their names. This is just the default behaviour; it is possible to determine which component to bind to a property using any other technique, as well as to ignore a property. Methods annotated with @Action can be bound to JButtons and other widgets following the same logic used for properties. You can find out more about how the binding works by reading the docs.
Besides that basic binding features, genesis also makes it possible to enable/disable components based on conditions (using @EnabledWhen), making them visible/hide them (using @VisibleWhen), populate tables, combos, lists etc. with a java.util.List or an array (using @DataProvider) and much more. It is also fully compatible with Java 1.4 and has many other non-UI related features, such as transparent remoting (which I blogged almost two years ago).
To finish the big announcement day, a SWT binding is now in HEAD and should be released in the next few days. So if you are developing a desktop application that uses either Swing, SWT or Thinlet, take a look at genesis.
Bitten by the class literal change in Tiger
Posted by mister__m on August 16, 2006 at 10:02 PM | Permalink
| Comments (8)
If you are a returning reader, you're probably aware of the enum implementation I wrote for Java 1.4 almost three years ago. Running some Java 1.4 compatible code compiled with Java 5 has just called my attention to a supposedly low impact change that was implemented in Tiger.
Since Java 5, a class literal, i.e., an expression such as MyClass.class does not trigger class initialization anymore. This is a well known, documented issue, but solutions are not clean nor cover all previously supported scenarios.
Let me explain how my enum implementation is affected by this bug so you can understand if this bug might affect your Java 1.4 code. Enum's sole constructor registers the newly created instance's in a few internal Map cache structures. I mean, when something like this is executed:
public final class MyEnum extends Enum {
public static final MyEnum A = new MyEnum("A");
public static final MyEnum B = new MyEnum("B");
private MyEnum(final String name) {
super(name);
}
}
, cache is populated to tell what is the domain for MyEnum (A and B) and to be able to tell you that the instance that represents the String constant "A" in runtime for MyEnum is MyEnum.A
Ok, now that we have a real world example, let's see what worked with Java 1.4 and fails with Tiger and Mustang:
MyEnum m = (MyEnum)Enum.get(MyEnum.class, typedValue);
With Java 1.4, it works; with Java 5, you will get null. The reason is simple: A and B are never instantiated because the class is never initialized.
Now, to the shocking news: there is no way to tell whether a Class instance has been initialized nor a 100% reliable way to force it to initialize. Workarounds such as:
Class c = MyEnum.class;
Class.forName(c.getName(), true, c.getClassLoader());
will require you to handle an exception that should never been thrown in this situation (ClassNotFoundException) and will fail if c.getClassLoader() == null and your code haven't been granted the RuntimePermission("getClassLoader") permission.
Therefore, I've submitted a RFE to add Class.initialize() and Class.isInitialized() to Java SE. I really would like to see this implemented for Java SE 6, but I think it will have to wait for Java 7. This change involves messing with shared native C code and refactoring a lot of stuff on the way, so you are welcome to help me in this task in the JDK Collaboration project.
For now, beware of code that relies on class literal triggering class initialization and hope the ugly workaround above works for you if you cannot avoid it.
|