HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

I'm starting my computer. I'm waiting for the operating system to be loaded. Now I can see the icons of my favorite applications. They're aligned on my desktop and in the same positions I left them.

I'm launching a development tool. It's creating its own windows and loading the toolbars I need. I select a project I want to work on from the list of those I used most recently. Everything is the same as it was when I closed this project.

What is hiding behind this mechanism? Lots of code that saves the coordinates of the windows, the position of the cursor in each window, the position of the visible text, the position of the selected text and many other parameters that determine the current state of the application's user interface. Each project has several files in which it keeps all this data. The application has its own config files. The programmers who made the tool worked very hard, and the persistence of the interface between two consecutive sessions is timesaving for users.

Now I'm launching one of my applications written in Java. Its window is placed somewhere on-screen at random coordinates or in the upper-left corner. This phenomenon doesn't appear at the Java applications that use property files, in which they keep the coordinates and dimensions of the windows. If the user interface is complex, however, then the property files become inadequate. You might be willing to spend money and time to write the code for reading and writing files in an application-specific format, but maintaining that code will be a real pain. Each change of the file format will require modifications in the source code in at least two places (the read and write routine). Is there a simpler solution? Yes. You can use object serialization.

Why Choose Java?
Suppose you need an application that allows each user to connect to a network, depending on his/her position(s) in the company: manager, designer or developer (Figure 1). The users won't want to redefine their profile each time they run the application, so the profile should be serialized. So as not to grow the line count of the source code more than necessary, I won't introduce options for colors or fonts. For a real-world application, these options might be compulsory. A user who has a few complex profiles might want to identify them rapidly according to the background color of the application (which becomes the parameter of the profile). Using the simple application described in this article, the user will be able to differentiate the profiles, depending on the on-screen position of the application's window.

Figure 1
Figure 1:

For a heterogeneous network you will choose Java. But the cross-platform support isn't the only advantage Java provides. Even if all users have the same operating system, they will still have displays with different resolutions, so fonts and controls will have different sizes. Few frameworks offer the flexibility of the Layout Managers from AWT. Sometimes you'll hear criticism that the AWT-based interfaces should be tested on all target platforms. It's impossible to write applications whose interfaces fit, on the first try, in all user preferences. However, an AWT dialog box can be resized, and all the UI components will be automatically reshaped. Users can resize the windows so the fonts and controls of their platform are correctly shown, instead of using font sizes that are established when the application is written. It remains only to serialize the dimensions of the windows so they don't have to start all over again each time they launch the application. It's unfair to criticize AWT, which has, in addition, a multithreading architecture. Since most native applications use dialog boxes that are modals with fixed dimensions, the code of these applications either isn't reentrant or is running in a single thread.

If you're still undecided about using Java, I'll give you one more reason. The Object Serialization API provided by Java is easy to use, flexible and scalable. You won't find something like this in other popular programming languages, such as C++, which produces native code, because the Serialization API is based on Reflection API. The latter provides information at runtime, which in the case of C++ native applications is available only at compile-time. I must say that MFC of Visual C++ provides some support for serialization, but the developers have to replace lots of TODOs with their own code. Unlike MFC, Java handles many issues automatically with the help of the reflection API. Sometimes, being interpreted means being superior.

The SmartLogin Application
The main class of the application, SmartLogin, extends java.awt.Frame. The member variables are a constant -- okay, a string -- profile, which keeps the name of a file and several variables that reference the UI components of the application. The application's user interface will be serialized in the profile file.

public static final String OK = "OK";
private transient String profile;
private TextField tName, tPassw;
private Checkbox cMan, cDes, cDev;
private Button bOk, bCancel;

The constructor of the SmartLogin class (Listing 1) receives as parameter a string taken from the command line, which represents the name of the user's profile. A reference to this string will be stored in the profile member variable. The constructor sets the title, size and the LayoutManager of the window. The last one is GridLayout(6,1). In the first two lines it inserts a label -- "Name:"- and a TextField -- tName - in which the users will type their name. In the third line are the three Checkboxes that define the user's profile, cMan, cDes and cDev. The next two lines contain a label -"Password:"- and a TextField -- tPassw -- in which users will type the password. The last line groups two buttons -- bOk, and bCancel. The events which are fired when the buttons are pushed or the window is closed when intercepted by a SmartAdapter instance.

The SmartAdapter class (Listing 2) extends java.awt.event.WindowAdapter and implements java.awt.event.ActionListener, java.io.Serializable. The SmartLogin() constructor receives as parameter an instance, sd, of the SmartLogin class. The actionPerformed() method implements the method with the same name of the ActionListener interface. This method is called when one of the buttons is pushed. For the Ok button it calls the login() and serialize() methods of the SmartLogin instance. The windowClosing() method overrides the method with the same name of the WindowAdapter class. This method is called when the user closes the application's window. The Serializable interface has no methods. However, the SmartAdapter must "implement" it for it to be serializable. The SmartLogin class needn't implement Serializable, because one of its ancestors (java.awt.Component) implements it.

The login() method of the SmartLogin class simulates a login. It shows a message like the following one:

Hello Andrei
You have logged as designer and developer

The serialize() method first calls clearPassw() and then tries to serialize the user interface of the application. The clearPassw() method clears the password stored in a private variable of a TextField instance.

public void clearPassw() {
tPassw.setText("");
System.gc();
}

After it calls clearPassw(), the serialize() method uses writeObject() of the java.io.ObjectOutputStream class to serialize the SmartLogin object, which represents the application's window. The writeObject() method is applied recursively on the member variables of the object passed as parameter and takes care not to serialize the same object twice. The OK and profile variables aren't serialized because one of them is static and the other one is transient.

FileOutputStream out = new FileOutputStream(profile
+ ".ser");
ObjectOutputStream s = new ObjectOutputStream(out);
s.writeObject(this);
s.flush();
s.close();

The main() method of the SmartLogin class first tries to deserialize the user interface. For this purpose the readObject() method of the java.io.ObjectInputStream class is used. If the deserialization succeeds, then the setProfile() method of the SmartLogin class is called to initialize the profile member variable, which wasn't serialized because it's transient. The show() method, which SmartLogin inherits from Frame, will show the window of the application.

FileInputStream in = new FileInputStream(profile

+ ".ser");
ObjectInputStream s = new ObjectInputStream(in);
sl = (SmartLogin) s.readObject();
s.close();
sl.setProfile(profile);
sl.show();

If the deserialization fails, then an exception is thrown. This is caught and shown at console only if its type isn't FileNotFoundException (the profile files may not exist if they haven't been created or have been deleted). A new instance of the SmartLogin class is created and the application's window is shown.

sl = new SmartLogin(profile);
sl.show();

See Reference 2 for more information about object serialization.

Control What Is Serialized
While the serialization mechanism is simple, using it without a minimum analysis may generate inconsistency, bad performances or even security holes. This is actually true for most applications whether they use object serialization or not. Fortunately, these problems can be easily solved.

You probably noticed that I used the transient keyword when I declared the profile member variable. Hence, this variable is ignored by the serialization mechanism because the name of the profile can't be part of the file without generating worry about whether an inconsistency will be determined. Nevertheless, this operation shouldn't be made when the SmartLogin application is running. When the user interface is deserialized, the profile variable is initialized with a default value. It's the programmer's responsibility to give it a correct value. This is the reason I called the setProfile() method.

Storing constants in the serialization stream is futile. Therefore, I declared the OK variable with the static keyword. As usual, the constants of a class and other variables that need large amounts of memory (as precomputed tables) are declared static, to be shared between the instances of the class. Note that the static variables are ignored at serialization and mustn't be initialized in constructors because these aren't called at deserialization.

There is one more source of information, the password. For security reasons it must not be serialized. SmartLogin uses a TextField component to obtain the password. However, this component must be serialized because it's part of the user interface. The solution is to call the setText() method of the tPassw object to replace the password with an empty string. This operation is made by clearPassw(), which is called whether or not the user interface is serialized. The garbage collector is invoked as a supplementary cautious measure. This should be enough, but you can never be sure.

If you don't want a variable to be serialized, then you declare it static or transient. You might wonder how additional information could be stored in serialization streams. You can add write/readObject() methods to your serializable classes. These methods will be called by the write/readObject() methods of the ObjectOutput/InputStream classes, instead of the default serialization mechanism, which is still available via defaultWrite/ReadObject(). If this solution isn't sufficient, then you can implement the java.io.Externalizable interface instead of java.io.Serializable. The externalizable classes have total control of what is serialized. They also control the format of the serialized data. See "Object Serialization Specification" (Reference 2) for details.

Sometimes, you need to serialize information to which only authorized persons should have access. The easiest solution is to put filters between FileOutput/InputStream and ObjectOutput/InputStream. These filters should encrypt/decrypt the information, which you can store on disk or send over the network after encryption. However, the encryption algorithm should be chosen carefully because the format of the serialization stream is public. A safer solution is to combine encryption with the use of externalizable interface.

Be Sure Your Application Is Closing
The SmartAdapter class is responsible for the close of the application (Listing 2). This class is serializable because it implements java.io.Serializable. The constructor of the SmartLogin class will create a SmartAdapter instance that will be registered as the listener for some events (Listing 1). This object will be serialized together with the user interface because it is referenced by the components it was registered with. Still, if SmartAdapter doesn't implement Serializable, something strange will happen. When the application is run for the first time, everything will seem to be okay. But at the next run, with the same profile, the application won't close because the SmartAdapter instance couldn't be serialized at the first run because of a bug in java.awt.AWTEventMulticaster. (For more information, see Reference 3, "Serializing UI Components". I did report the bug to Sun, so it may already be corrected by the time you read this article.)

What Are the Advantages?
From the user's perspective, the UI persistence is timesaving. The application becomes friendlier because you don't have to repeat the same operations each time you start it. Resizing the application's window together with persistence might be an unusual solution for a well-known issue; the fonts and controls have different sizes on different computers even if the platform is the same.

From the programmer's point of view, object serialization is easy to use. You don't need to read "Object Serialization Specification" or have work experience with files in Java (this might be a common case because many Java programmers develop only applets, in which the use of streams is restricted for security reasons). But more importantly there is no supplementary cost for code maintenance.

Possible Inconveniences
Some people say that using serialization for the persistence of the coordinates of windows wastes space, but this isn't an issue in most cases. However, the transient keyword must be used to separate the application's user interface from the text or the tables edited in windows so that the application's data won't be serialized together with the interface. The data must be stored in separate files.

The SmartLogin application isn't perfect. A few users might try to log on with the same profile at the same time. If they modify the profile, the changes will be overridden on disk. This problem should be solved by the login() method, which should be able to detect the possible conflicts. The problem isn't relevant for this article.

Back to My Computer...
Before I stop it, I have to close all the applications so they can serialize their state. Only some of them can do that, and some of them provide "better" persistence than others. Wouldn't it be nice to have a button on the desktop that would serialize the user interface of all open applications? This way, the next time I start my computer, I won't have to restart them. This might sound utopian, because all native applications should cooperate with the operating system which doesn't provide a standard mechanism for serialization. Theoretically, you might think to save the image of the memory, the registers of the microprocessor and the state of the other hardware equipment. This won't work and wouldn't be practical. Imagine what would happen if the computer was connected to a network when it was closed. The applications must cooperate. If all the applications were written in Java and they knew how to serialize their state (another utopia), the listener of a simple button could save the state of all applications in a single bytestream that I could send to somebody, who could continue my work from the point I left it (this person could be me moving from one computer to another). The class files could then be downloaded from a Web server. This mechanism can work for a single Java application (an example is SmartLogin) and this might be enough. It's up to you to implement UI persistence for your applications.

References:

  1. The AWT Home Page http://java.sun.com/products/jdk/awt/index.html
  2. Object Serialization Specification http://java.sun.com/products/jdk/1.1/docs/guide/serialization/index.html
  3. Serializing UI Components http://www.geocities.com/SiliconValley/Horizon/6481/AltUI11s.html

About the Author
Andrei Cioroianu is an independent Java developer. He has a BS in mathematics-computer science and an MS in artificial intelligence. His focus is on 3D graphics (Java 3D), software components (JavaBeans) and user interface (AWT, JFC). You can reach Andrei for questions or comments at [email protected]

	

Listing 1: The SmartLogin Constructor.
 
private SmartLogin(String profile) { 
   this.profile = profile; 
   setTitle("Smart Login"); 
   setSize(400,200); 
   setLayout(new GridLayout(6,1)); 
   add(new Label("Name:")); 
   add(tName = new TextField()); 
   Panel p = new Panel(); 
   p.setLayout(new GridLayout(1, 3)); 
   p.add(cMan = new Checkbox("Manager")); 
   p.add(cDes = new Checkbox("Designer")); 
   p.add(cDev = new Checkbox("Developer")); 
   add(p); 
   add(new Label("Password:")); 
   add(tPassw = new TextField()); 
   SmartAdapter adapter = new SmartAdapter(this); 
   Panel q = new Panel(); 
   q.setLayout(new FlowLayout()); 
   q.add(bOk = new Button("    Ok    ")); 
   bOk.setActionCommand(OK); 
   bOk.addActionListener(adapter); 
   q.add(bCancel = new Button("  Cancel  ")); 
   bCancel.addActionListener(adapter); 
   add(q); 
   addWindowListener(adapter); 
} 

Listing 2: The SmartAdapter Class.
 
class SmartAdapter extends WindowAdapter 
      implements ActionListener, Serializable { 

SmartLogin sl; 

SmartAdapter(SmartLogin sl) { 
   this.sl = sl; 
} 

public void actionPerformed(ActionEvent e) { 
   if (SmartLogin.OK.equals(e.getActionCommand())) { 
      sl.login(); 
      sl.serialize(); 
   } else 
      sl.clearPassw(); 
   System.exit(0); 
} 

public void windowClosing(WindowEvent e) { 
   sl.clearPassw(); 
   System.exit(0); 
} 

} 
  

// SmartLogin.java 

import java.awt.*; 
import java.awt.event.*; 
import java.io.*; 

public class SmartLogin extends Frame 
{ 
    public  static final String OK = "OK"; 
    private transient String profile; 
    private TextField tName, tPassw; 
    private Checkbox  cMan, cDes, cDev; 
    private Button    bOk, bCancel; 

    // Creates the user interface 
    private SmartLogin(String profile) 
    { 
        this.profile = profile; 
        setTitle("Smart Login"); 
        setSize(400,200); 
        setLayout(new GridLayout(6,1)); 
        add(new Label("Name:")); 
        add(tName = new TextField()); 
        Panel p = new Panel(); 
        p.setLayout(new GridLayout(1, 3)); 
        p.add(cMan = new Checkbox("Manager")); 
        p.add(cDes = new Checkbox("Designer")); 
        p.add(cDev = new Checkbox("Developer")); 
        add(p); 
        add(new Label("Password:")); 
        add(tPassw = new TextField()); 
        SmartAdapter adapter = new SmartAdapter(this); 
        Panel q = new Panel(); 
        q.setLayout(new FlowLayout()); 
        q.add(bOk = new Button("    Ok    ")); 
        bOk.setActionCommand(OK); 
        bOk.addActionListener(adapter); 
        q.add(bCancel = new Button("  Cancel  ")); 
        bCancel.addActionListener(adapter); 
        add(q); 
        addWindowListener(adapter); 
    } 

    // Simulates a login 
    public void login() 
    { 
        System.out.println("Hello " + tName.getText()); 
        StringBuffer userType = new StringBuffer(); 
        if (cMan.getState()) 
            userType.append("manager "); 
        if (cDes.getState()) 
        { 
            if (userType.length() != 0) 
                userType.append("and "); 
            userType.append("designer "); 
        } 
        if (cDev.getState()) 
        { 
            if (userType.length() != 0) 
                userType.append("and "); 
            userType.append("developer "); 
        } 
        if (userType.length() == 0) 
            userType.append("simple user"); 
        System.out.println("You have logged as " + userType); 
    } 

    // Clear the password maintained in a private field of tPassw 
    public void clearPassw() 
    { 
        tPassw.setText(""); 
        System.gc(); 
    } 

    // Sets the profile member variable 
    public void setProfile(String profile) 
    { 
        this.profile = profile; 
    } 

    // Serialize the user interface of the application 
    public void serialize() 
    { 
        clearPassw(); 
        try 
        { 
            FileOutputStream out = new FileOutputStream(profile + 
".ser"); 
            ObjectOutputStream s = new ObjectOutputStream(out); 
            s.writeObject(this); 
            s.flush(); 
            s.close(); 
        } 
        catch (IOException e) 
            { System.out.println(e); } 
        catch (SecurityException e) 
            { System.out.println(e); } 
    } 

    // The main method 
    public static void main(String args[]) 
    { 
        SmartLogin sl; 
        String profile = args.length > 0 ? args[0] : "default"; 
        // Tries to deserialize the user interface 
        try 
        { 
            FileInputStream in = new FileInputStream(profile + ".ser"); 
            ObjectInputStream s = new ObjectInputStream(in); 
            sl = (SmartLogin) s.readObject(); 
            s.close(); 
            sl.setProfile(profile); 
            sl.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); } 
        // The deserialization failed 
        // A new SmartLogin instance will be created 
        sl = new SmartLogin(profile); 
        sl.show(); 
    } 

} 

class SmartAdapter extends WindowAdapter implements ActionListener, 
Serializable 
{ 
    SmartLogin sl; 

    SmartAdapter(SmartLogin sl) 
    { 
        this.sl = sl; 
    } 
  
    // This method is called when a button is pushed and released 
    public void actionPerformed(ActionEvent e) 
    { 
        if (SmartLogin.OK.equals(e.getActionCommand())) 
        { 
            sl.login(); 
            sl.serialize(); 
        } 
        else 
            sl.clearPassw(); 
        System.exit(0); 
    } 

    // This method is called when the window of the application is 
closed 
    public void windowClosing(WindowEvent e) 
    { 
        sl.clearPassw(); 
        System.exit(0); 
    } 

} 
  
      
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.