RFR: 4938801: The popup does not go when the component is removed [v6]

Jeremy Wood duke at openjdk.org
Wed Jul 30 04:44:01 UTC 2025


On Wed, 30 Jul 2025 03:44:37 GMT, Prasanta Sadhukhan <psadhukhan at openjdk.org> wrote:

>> src/java.desktop/share/classes/javax/swing/JPopupMenu.java line 1427:
>> 
>>> 1425:             indexCounter++;
>>> 1426:         }
>>> 1427:     }
>> 
>> This may be a naive question: is the lambda we use for `propListener` Serializable?
>> 
>> (If so: great. If not: won't we get inconsistent results if we're working with a deserialized copy of a JPopupMenu? The original will have a non-null `propListener` field, and the copy will have a null `propListener`? Or am I missing something?)
>
> It is transient so  its value should not be included in the default serialization process in my opinion

I'm fine with `propListener` being transient ... as long as when we call readObject() we still make a point to initialize `propListener`.

Here's a unit test that demonstrates what I'm worried about:

import javax.swing.*;
import java.beans.PropertyChangeListener;
import java.io.*;

public class JPopupMenuSerializationTest {
    static class JPopupMenuSubclass extends JPopupMenu implements Serializable {
        private transient PropertyChangeListener propListener =
                (e) -> {
                    if (e.getOldValue() != null
                            && e.getNewValue() == null
                            && isVisible()) {
                        setVisible(false);
                    }
                };

        @Serial
        private void writeObject(ObjectOutputStream s) throws IOException {
            Object serializable = propListener instanceof Serializable ?
                    (Serializable) propListener : null;
            System.out.println("Serialized: " + serializable);
            s.writeObject(serializable);
        }

        @Serial
        private void readObject(ObjectInputStream s)
                throws IOException, ClassNotFoundException {
            PropertyChangeListener l = (PropertyChangeListener) s.readObject();
            System.out.println("Deserialized: " + l);
            if (l != null) {
                propListener = l;
            }
            System.out.println("propListener: " + propListener);
        }
    }

    public static void main(String[] args) throws Exception {
        JPopupMenuSerializationTest test = new JPopupMenuSerializationTest();
        test.testSerialization();
    }

    public void testSerialization() throws Exception {
        JPopupMenuSubclass popupMenu = new JPopupMenuSubclass();
        byte[] data = serialize(popupMenu);
        JPopupMenuSubclass copy = deserialize(data);

        assertTrue("original.propListener should exist", popupMenu.propListener != null);

        // currently this fails, and IMO it should pass:
        assertTrue("copy.propListener should exist",  copy.propListener != null);
    }

    private static void assertTrue(String errorMsg, boolean b) {
        if (!b)
            throw new Error(errorMsg);
    }

    private static byte[] serialize(JPopupMenuSubclass popupMenu) throws IOException {
        try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream()) {
            try (ObjectOutputStream objOut = new ObjectOutputStream(byteOut)) {
                objOut.writeObject(popupMenu);
            }
            return byteOut.toByteArray();
        }
    }

    private static JPopupMenuSubclass deserialize(byte[] data) throws IOException, ClassNotFoundException {
        try (ByteArrayInputStream byteIn = new ByteArrayInputStream(data)) {
            try (ObjectInputStream objIn = new ObjectInputStream(byteIn)) {
                return (JPopupMenuSubclass) objIn.readObject();
            }
        }
    }
}


Currently on my machine this outputs:

Serialized: null
Deserialized: null
propListener: null
Exception in thread "main" java.lang.Error: copy.propListener should exist
	at JPopupMenuSerializationTest.assertTrue(JPopupMenuSerializationTest.java:54)
	at JPopupMenuSerializationTest.testSerialization(JPopupMenuSerializationTest.java:49)
	at JPopupMenuSerializationTest.main(JPopupMenuSerializationTest.java:38)


This means (I think?) a deserialized copy is going to act different than the original, right? (I'm not an expert on serializing UI components, so maybe I'm missing something...?)

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/26407#discussion_r2241513779


More information about the client-libs-dev mailing list