Serializing UI Components

Are you looking for a Java developer?
Do you need custom Java applications or components?
Are you building your own Java product and you want to accelerate its development?
 

Java XML Solutions

Custom Development
FREE Open Source
Java XML Projects

Web Development

with JSP, JSTL, XJTL, JDBC,
XML, SAX, DOM, XSLT &
Custom JSP Tag Libraries

J2EE & Web Services

Making Enterprise Resources
Available as Web Services
with SOAP, WSDL & UDDI

 

In the previous article, I showed you how to convert your components to AWT 1.1, to benefit by the advantages of the delegation event model. Next I will show you how object serialization can be used to create persistent user interfaces.

 

Transforming the applet into an application

Not to have trouble with the security politics of the browsers, the Test applet should be transformed into an application named TestApp. First the java.applet.Applet superclass must be replaced with java.awt.Frame, so that the application can run into a window. The init() method can be kept. The only one modification is in the line that set the font. Its name can't be obtained with getFont().getName() anymore, because when init() is called, getFont() returns null. A void string is used for the font's name, letting AWT to choose a valid name for the application's font. After these modifications, the TestApp class needs only a main() method.

 

Initializing the application

The main() method tries first to "read" a TestApp object from the TestApp.ser file. The FileNotFoundException exception will be ignored because at the first run of the application this file won't exist. If the deserialization succeeds then the TestApp object is shown on the screen with the show() method inherited from java.awt.Frame. Otherwise the main() method creates a TestApp window, it sets the title and the size, it calls the init() method to create the buttons that form the user interface, then it registers a WindowListener and in the final it shows the window. After it returns from main(), the application continue to run. It is finished when the user closes the window.

 

Closing the application

The instance of the TestWindowAdapter class, which was created in main() method, receive the event that is fired when the user tries to close the window of the application. Before it calls System.exit(), it serialize the TestApp object in the TestApp.ser file, together with the components referenced by this object. The listeners are also serialized because they are referenced by the components. When the next run of the application occurs the TestApp window is deserialized and shown on the screen at the same position with the same dimension, and the checkbox and radio buttons have the same state as they had at the last close of the application.

The application isn't multi-user. If you launch more than one instance when they are closed, they override same TestApp.ser file.

 

Serializing Listeners

All Listeners must implement the java.io.Serializable interface to make serialization possible. This interface has no methods and acts like a mark.

If one of the EastListener or SouthListener classes doesn't implement java.io.Serializable interface then the application works, but exceptions are thrown, caught, and shown to console. The user interface won't be persistent. However, if TestWindowAdapter do not implement java.io.Serializable then the application manifest a strange behavior. At the first run everything seems to be OK. No exception is thrown. When the next runs occur the deserialization works, but the application can't be closed and the user interface isn't serialized anymore. This, because the TestWindowAdapter instance was simply not serialized at the first run. I deduced this conclusion on two ways. First I compared the TestApp.ser files generated in both situations with TestWindowAdapter serializable and not (and keeping same state for buttons, and same dimension and position for the window). I observed that if I cut a certain continuous part from the file that was generated in the case in which TestWindowAdapter implemented java.io.Serializable, then I obtain the file that was generated in the second case (in which it didn't implement it). Even more, the cut part contains the string "TestWindowAdapter". The second way was based on the analysis of the source code of the java.awt.AWTEventMulticaster class.

 

A bug in java.awt.AWTEventMulticaster class

The add/remove<EventType>Listener() methods of the components call add() and remove() methods of the java.awt.AWTEventMulticaster class. The values that are returned from these methods are stored in transient variables. The AWTEventMulticaster class is used to build chains of Listeners, but isn't serializable.

At serialization, the components call the save() method of AWTEventMulticaster, which call saveInternal(). These methods can serialize both AWTEventMulticaster objects and serializable Listeners, but do not consider the case in which non-serializable Listeners are passed to them. When they receive the TestWindowAdapter object, they should throw an exception, but they don't. You may correct this bug by modifying the final part of the AWTEventMulticaster.java file. This becomes:

................................................
import java.io.NotSerializableException;
................................................

protected void saveInternal(...) throws IOException
{
    if (a instanceof AWTEventMulticaster)
        ... ... ...
    else if (a instanceof Serializable)
        ... ... ...
    else
        throw (new NotSerializableException(a.getClass().getName()));

    if (b instanceof AWTEventMulticaster)
        ... ... ...
    else if (b instanceof Serializable)
        ... ... ...
    else
        throw (new NotSerializableException(b.getClass().getName()));
}

protected static void save(...) throws IOException
{
    ... ... ...
    else
        throw (new NotSerializableException(l.getClass().getName()));
}

Next, AWTEventMulticaster.java must be compiled, and the resulted .class file must be moved in classes.zip.

It should be highlighted that this bug creates problems only at debugging. If you take care that all Listener classes implement java.io.Serializable then this bug won't affect you. But imagine that you work on an application, which registers hundreds of Listeners to AWT components. If only one Listener doesn't implement java.io.Serializable, then your application won't serialize well, and won't signal this (no exception is thrown).

 

A bug in my own AltButton

In this moment it might look strange that my application works well, because the AltButton class uses java.awt.AWTEventMulticaster without calling save() methods (it isn't public, so it can't use it), and the actionListener member variable isn't transient like in java.awt.Button class. The explication can be found by analyzing the chaining mechanism of the Listeners. If a button has only one ActionListener then it won't create any instances of the AWTEventMulticaster class. This phenomenon allows TestApp to serialize / deserialize right.

But if you add, for example, two Listeners at one of the buttons from east, as bellow, then the application won't offer persistency anymore, and throws exceptions at serialization.

e2 = new AltButton("Button 2");
e2.addActionListener(new EastListener(w2));
e2.addActionListener(new EastListener(w2));
p.add(e2);

The application continues to work because the exceptions are caught. By clicking on the second button from east, you won't change the state of that from west because the first EastListener modifies it, and the second modifies it again, bringing the button in the initial state. This behavior is right. If java.awt.AWTEventMulticaster is modified again to implement java.io.Serializable, then the persistency will work.

To modify the Java classes isn't a good solution for at least two reasons. First, the programs become non-portable - they run only on the machines in which same modifications were made in the source code of the standard classes. Second, you can't know which effect the modifications have on other Java classes or other applications. These modifications are good only for experimenting.

The right solution consist in modifying the add/removeActionListener() methods of AltButton. Instead of AWTEventMulticaster you must use another class. Such a file:

// AltEventMulticaster.java
public class AltEventMulticaster extends java.awt.AWTEventMulticaster 
implements java.io.Serializable
{
}

won't compile. This solution would have worked if java.awt.AWTEventMulticaster had a public or protected no-arg constructor. In the variant listed below, the file compiles, but the persistency don't work. The error is signaled at runtime.

// AltEventMulticaster.java
import java.util.EventListener;
public class AltEventMulticaster extends java.awt.AWTEventMulticaster 
implements java.io.Serializable
{
    protected AltEventMulticaster(EventListener a, EventListener b) 
    {
        super(a, b);
    }
}

A solution would be to make AltButton member of java.awt package and implement the serialization like java.awt.Button does. But this is a bad idea. Another solution is to make a copy of the AWTEventMulticaster.java file, and rename it AltEventMulticaster.java. You must remove the declaration "package java.awt;" from the beginning of the new file, and the string "AWTEventMulticaster" must be replaced with "AltEventMulticaster". The new class must implement java.io.Serializable. But this cannot be distributed because of the License Agreement.

The only solution, I recommend you, is to write a less generally class - AltActionEventMulticaster that implements methods like those of java.awt.AWTEventMulticaster. The add/removeActionListener() methods of AltButton will call the add/remove() methods of AltActionEventMulticaster, instead of those of AWTEventMulticaster.

 

Conclusion

There are a few important frameworks that offer components written 100% in Java: JFC (Sun), IFC (Netscape) and AFC (Microsoft). Nevertheless, there is a place for small companies or individual programmers. They may offer specialized components or with a "nonstandard" look, like these presented in my articles. But their work won't be easy.

My application runs, and the persistency works in all right scenarios that I imagined and it fails when it's expected to. This, without to modify the sources of the AWT classes. But I would have a few questions for the programmers from JavaSoft:

Why do new methods call deprecated methods?
Why isn't eventEnebled() protected?
Why isn't AWTEventMulticaster serializable?

 

Resources

The JavaBeans HomePage
http://java.sun.com/beans/index.html

Object Serialization
http://java.sun.com/products/jdk/1.1/docs/guide/serialization/index.html

JFC vs. AFC: Which GUI toolkit should you use?
http://www.javaworld.com/javaworld/jw-02-1998/jw-02-afc.v.jfc.html

 

TestApp.java

// TestApp.java

import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;
import java.io.*;

public class TestApp extends java.awt.Frame
{
    AltCheckboxGroup n, s;
    AltCheckbox n1, n2, n3;
    AltCheckbox s1, s2, s3;
    AltCheckbox w1, w2, w3;
    AltButton   e1, e2, e3;

    public void init()
    {
        setBackground(Color.lightGray);
        setForeground(Color.black);
        setFont(new Font("", Font.PLAIN, 14));
        setLayout(new GridLayout(5,1));
        Panel p;

        p = new Panel();
        p.setLayout(new GridLayout(1, 3));
        n = new AltCheckboxGroup();
        n1 = new AltCheckbox("Radio 1", false, n);
        p.add(n1);
        n2 = new AltCheckbox("Radio 2", false, n);
        p.add(n2);
        n3 = new AltCheckbox("Radio 3", false, n);
        p.add(n3);
        n.setSelectedCheckbox(n2);
        add(p);

        p = new Panel();
        p.setLayout(new GridLayout(1, 2));
        w1 = new AltCheckbox("Check 1", true);
        p.add(w1);
        e1 = new AltButton("Button 1");
        e1.addActionListener(new EastListener(w1));
        p.add(e1);
        add(p);

        p = new Panel();
        p.setLayout(new GridLayout(1, 2));
        w2 = new AltCheckbox("Check 2", false);
        p.add(w2);
        e2 = new AltButton("Button 2");
        e2.addActionListener(new EastListener(w2));
        p.add(e2);
        add(p);

        p = new Panel();
        p.setLayout(new GridLayout(1, 2));
        w3 = new AltCheckbox("Check 3", true);
        p.add(w3);
        e3 = new AltButton("Button 3");
        e3.addActionListener(new EastListener(w3));
        p.add(e3);
        add(p);

        p = new Panel();
        p.setLayout(new GridLayout(1, 3));
        s = new AltCheckboxGroup();
        s1 = new AltCheckbox("First", false, s);
        s1.addActionListener(new SouthListener(n, n1));
        p.add(s1);
        s2 = new AltCheckbox("Second", false, s);
        s2.addActionListener(new SouthListener(n, n2));
        p.add(s2);
        s3 = new AltCheckbox("Third", false, s);
        s3.addActionListener(new SouthListener(n, n3));
        p.add(s3);
        s.setSelectedCheckbox(s2);
        add(p);
    }

    public static void main(String args[]) 
    {
        TestApp t;
        try
        {
            FileInputStream in = new FileInputStream("TestApp.ser");
            ObjectInputStream s = new ObjectInputStream(in);
            t = (TestApp) s.readObject();
            t.show();
            return;
        }
        catch (FileNotFoundException e)
        {
        }
        catch (IOException e)
        {
            System.out.println(e);
        }
        catch (ClassNotFoundException e)
        {
            System.out.println(e);
        }
        catch (SecurityException e)
        {
            System.out.println(e);
        }

        t = new TestApp();
        t.setTitle("TestApp");
        t.setSize(200,200);
        t.init();
        t.addWindowListener(new TestWindowAdapter(t));
        t.show();
    }

}

class TestWindowAdapter extends WindowAdapter implements Serializable
{
    TestApp t;

    TestWindowAdapter(TestApp t)
    {
        this.t = t;
    }
    
    public void windowClosing(WindowEvent we)
    {
        try
        {
            FileOutputStream out = new FileOutputStream("TestApp.ser");
            ObjectOutputStream s = new ObjectOutputStream(out);
            s.writeObject(t);
            s.flush();
        }
        catch (IOException e)
        {
            System.out.println(e);
        }
        catch (SecurityException e)
        {
            System.out.println(e);
        }

        System.exit(0);
    }

}

class EastListener implements ActionListener, Serializable
{
    AltCheckbox w;

    EastListener(AltCheckbox w)
    {
        this.w = w;
    }

    public void actionPerformed(ActionEvent e)
    {
        w.setState(!w.getState());
    }

}

class SouthListener implements ActionListener, Serializable
{
    AltCheckboxGroup g;
    AltCheckbox n;

    SouthListener(AltCheckboxGroup g, AltCheckbox n)
    {
        this.g = g;
        this.n = n;
    }

    public void actionPerformed(ActionEvent e)
    {
        g.setSelectedCheckbox(n);
    }

}

Back to story

 

AltActionEventMulticaster.java

// AltActionEventMulticaster.java

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.Serializable;


public class AltActionEventMulticaster implements Serializable, ActionListener
{
    protected final ActionListener a, b;

    protected AltActionEventMulticaster(ActionListener a, ActionListener b) 
    {
        this.a = a; 
        this.b = b;
    }

    public void actionPerformed(ActionEvent e) 
    {
        a.actionPerformed(e);
        b.actionPerformed(e);
    }

    public static ActionListener add(ActionListener a, ActionListener b) 
    {
        if (a == null) return b;
        if (b == null) return a;
        return new AltActionEventMulticaster(a, b);
    }

    public static ActionListener remove(ActionListener l, ActionListener oldl) 
    {
        if (l == oldl || l == null) 
            return null;
        else if (l instanceof AltActionEventMulticaster) 
            return ((AltActionEventMulticaster)l).remove(oldl);
        else
            return l;
    }

    protected ActionListener remove(ActionListener oldl) 
    {
        if (oldl == a) return b;
        if (oldl == b) return a;
        ActionListener a2 = remove(a, oldl);
        ActionListener b2 = remove(b, oldl);
        if (a2 == a && b2 == b)
            return this;
        return add(a2, b2);
    }

}

Back to story


Back to Inside AWT
Back to Java Developer's Page



This page hosted by Get your own Free Homepage

1