JDK-4950972 : LTP: Long-term persistence bugs (swing, Encoder, XMLEncoder)
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.beans
  • Affected Version: 1.4.2
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2003-11-07
  • Updated: 2008-11-19
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
Name: gm110360			Date: 11/07/2003


FULL PRODUCT VERSION :
java -version
java version "1.4.1_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_01-b01)
Java HotSpot(TM) Client VM (build 1.4.1_01-b01, mixed mode)


FULL OPERATING SYSTEM VERSION :
Microsoft Windows 2000 [Version 5.00.2195]

A DESCRIPTION OF THE PROBLEM :
I like the idea of the new long-term persistence, but I've
found the implementation to have some bugs.For example:

1. JInternalFrame does not persist properly (the bounds and
visibility are not set).
2. BorderLayout doesn't persist properly without a center
component (last component is always placed in the center).
3. JPopupMenu.add(Action) doesn't persist properly
(exception raised) (workaround: popup.add(new JMenuItem
(Action)) does work.)
4. Inner classes can't persist (no public constructor).
(Yet inner classes are Sun's argument against c# delegates.
The new EventHandler is a poor approximation to delegates
with no compile-time checks.)

For the BorderLayout, the problem is that this:

  c.add(comp, BorderLayout.EAST)

is not quite the same as:

  c.add(comp);
  c.getLayout().addLayoutComponent(BorderLayout.EAST, be);

The latter is what the XMLEncoder creates for the former.
The latter is equivalent to:

  c.add(comp, BorderLayout.CENTER);
  c.getLayout().addLayoutComponent(BorderLayout.EAST, be);

The problem is that the same component is both the CENTER
and the EAST component in the BorderLayout! BorderLayout
assigns bounds to the EAST first and then to the CENTER;
our poor component gets its bounds set twice and lands in
the center, with space taken in the east for a phantom
version.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Use the XMLEncoder to write a JFrame with a JDesktopPane
and a JInternalFrame.  Use XMLDecoder to reload it.  The
JInternal will not be visible because its bounds are not
set and setVisible(true) is not called.
2. Use the XMLEncoder to write a component with
BorderLayout and one child in the NORTH (any of N,S,E,W
will do).  No child should be in the center.  Use the
XMLDecoder to reload it.  The component will be placed in
the center (and a phantom will be in the North).

EXPECTED VERSUS ACTUAL BEHAVIOR :
The XMLEncoder is not writing the bounds and visibility of
a JInternalFrame.  The frame should appear in the same
place it was before saving.

The XMLEncoder does not write save BorderLayouts properly.
The component should live in the proper location.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
c:\Documents and Settings\kbeyer\My Documents\src\dax\test>java BadEncoder
java BadEncoder

writing

java.lang.InstantiationException: javax.swing.JPopupMenu$2
Continuing ...
java.lang.Exception: discarding statement JPopupMenu0.add(JPopupMenu$20);
Continuing ...



REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
BadEncoder.java:
----------------

import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;
import dax.awt.*;

// MY OWN PersistenceDelegate for a JInternalFrame

class javax_swing_JInternalFrame_PersistenceDelegate extends
DefaultPersistenceDelegate {
    protected void initialize(Class type, Object oldInstance, Object
newInstance, Encoder out)
    {
        super.initialize(type, oldInstance, newInstance, out);
        JInternalFrame oldC = (JInternalFrame)oldInstance;
        JInternalFrame newC = (JInternalFrame)newInstance;

        // bounds
        Rectangle oldB = oldC.getBounds();
        Rectangle newB = newC.getBounds();
        if( ! oldB.equals(newB) ) {
            out.writeStatement(new Statement(oldInstance, "setBounds", new
Object[]{oldB}));
        }

        // visible
        boolean oldV = oldC.isVisible();
        boolean newV = newC.isVisible();
        if (newV != oldV) {
            out.writeStatement(new Statement(oldInstance, "setVisible", new
Object[]{Boolean.valueOf(oldV)}));
        }

    }
}

public class BadEncoder
{
    private static boolean fixPopup = false;
    private static boolean fixJInternalFrame = false;
    private static boolean fixBorderLayout = false;

    public static void decode() throws IOException
    {
        XMLDecoder in = new XMLDecoder(
            new BufferedInputStream(new FileInputStream("test.xml")));
        in.readObject();
        in.close();
    }

    public static void encode() throws Exception
    {
        JFrame topFrame = new JFrame();
        topFrame.setBounds(0,0,500,500);
        JDesktopPane desktop = new JDesktopPane();
        topFrame.getContentPane().add(desktop);
        JInternalFrame frame = new JInternalFrame("my
frame",true,true,true,true);
        frame.setVisible(true);
        frame.setBounds(10,10,100,100);
        JComponent c = (JComponent) frame.getContentPane();
        c.add(new JButton("button"), BorderLayout.NORTH);
        if( fixBorderLayout ) {
            c.add(new JPanel(), BorderLayout.CENTER);
        }
        desktop.add(frame);

        JPopupMenu popup = new JPopupMenu();
        //this does not persist properly (exception due to JPopupMenu inner
class):
        if( !fixPopup ) {
            popup.add(new ExitAction());
        } else {
            popup.add(new JMenuItem(new ExitAction()));
        }

        // try{ popup.add(new SimpleAction("Exit",
SimpleAction.class, "doExit")); } catch(Exception ex) {}

        //Add listener to components that can bring up popup menus.
        MouseListener popupListener = new PopupListener(popup);
        desktop.addMouseListener(popupListener);

        topFrame.setVisible(true);

        Thread.sleep(2000);
        System.err.println("\nwriting\n");
        XMLEncoder e = new XMLEncoder(
            new BufferedOutputStream(new FileOutputStream("test.xml")));
        if( fixJInternalFrame ) {
            e.setPersistenceDelegate(JInternalFrame.class,
                                     new
javax_swing_JInternalFrame_PersistenceDelegate());
        }
        e.writeObject(topFrame);
        e.close();

    }

    public static void main(String args[]) throws Exception
    {
        boolean doDecode = false;

        for(int i = 0 ; i < args.length ; i++) {
            if( "-decode".equals(args[i]) ) {
                doDecode = true;
            }
            else if( "-fixPopup".equals(args[i]) ) {
                fixPopup = true;
            }
            else if( "-fixJInternalFrame".equals(args[i]) ) {
                fixJInternalFrame = true;
            }
            else if( "-fixBorderLayout".equals(args[i]) ) {
                fixBorderLayout = true;
            }
            else {
                System.err.println("unknown argument: "+args[i]);
            }
        }

        if( doDecode ) {
            decode();
        } else {
            encode();
            System.exit(0);
        }
    }
}


PopupListener.java
------------------

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

// NOTE THE EXTRA FILES FOR THE MOUSEADAPTER AND THE ACTION!
// THESE ARE NEEDED FOR PERSISTENCE BEFORE THEY WERE INNER CLASSES.
// (EventHandler might be a usable substitute.)
public class PopupListener extends MouseAdapter
{
    JPopupMenu popup;

    public PopupListener()
    {
    }
    public PopupListener(JPopupMenu popup)
    {
        this.popup = popup;
    }

    public JPopupMenu getPopup() { return popup; }
    public void setPopup(JPopupMenu popup) { this.popup = popup; }

    public void mousePressed(MouseEvent e)
    {
        maybeShowPopup(e);
    }

    public void mouseReleased(MouseEvent e)
    {
        maybeShowPopup(e);
    }

    private void maybeShowPopup(MouseEvent e)
    {
        if (e.isPopupTrigger()) {
            popup.show(e.getComponent(),
                       e.getX(), e.getY());
        }
    }
}


ExitAction.java
---------------

import javax.swing.AbstractAction;
import java.awt.event.ActionEvent;

public class ExitAction extends AbstractAction
{
    public ExitAction()
    {
        super("Exit");
    }
    
    public void actionPerformed(ActionEvent e)
    {
        System.exit(0);
    }
}


  To run:

javac BadEncoder.java PopupListener.java ExitAction.java

java BadEncoder
java BadEncoder -decode

java BadEncoder -fixPopup
java BadEncoder -decode

java BadEncoder -fixPopup -fixJInternalFrame
java BadEncoder -decode

java BadEncoder -fixPopup -fixJInternalFrame
java BadEncoder -decode

---------- END SOURCE ----------

CUSTOMER WORKAROUND :
workarounds shown in source code
(Incident Review ID: 179580) 
======================================================================

Comments
EVALUATION The bug with JPopup persistance moved into the 6353636 bug.
21-11-2005

EVALUATION JPopupMenu.add(Action) doesn't persist properly because this method uses anonymous class instead JMenuItem. Unfortunately XMLEncoder doesn't have access rights to create instance of private anonymous class.
17-11-2005

EVALUATION To fix bounds persistence we should fix bug 4994649 first.
15-11-2005

EVALUATION The bug with undefined behavior of BorderLayout moved into the 6349028 bug.
11-11-2005

CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: dragon
14-06-2004

EVALUATION Many of these issues have been addressed in 1.5. ###@###.### 2003-11-11 After running the example, none of these issues are addressed in 1.5. Fix for dragonfly. ###@###.### 2003-11-19 Some of these issues have already been addressed, but we need to fix the remaining ###@###.### 2005-06-06 15:49:22 GMT
19-11-2003