JFormDesigner / FlatLaf

FlatLaf - Swing Look and Feel (with Darcula/IntelliJ themes support)
https://www.formdev.com/flatlaf/
Apache License 2.0
3.34k stars 266 forks source link

NullPointerException when serializing TableModel #551

Closed N00ree closed 2 years ago

N00ree commented 2 years ago

Hi,

a NullPointerException is thrown when trying to serialize a TableModel object while the associated JTable object is visible in a JFrame. I created a minimal, reproducible Example and will also include the Stack-Trace.

I hope someone can help. I am using AdoptOpenJDK14 (14.0.2.12-hotspot)

Here is the minimal, reproducible Example:

Serialization.java (Entrypoint of the Program)

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

import com.formdev.flatlaf.FlatDarkLaf;

public class Serialization {

    public static void main(String[] args) throws Exception {
        FlatDarkLaf.setup(); // When using FlatLaf, it causes a NullPointer when serializing.

        MyAbstractModel model = new MyAbstractModel();
        JTable table = new JTable(model);

        // After creating the frame, putting in the table and setting it to visible the exception will be thrown when trying to serialize below
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame();
            frame.add(table);
            frame.setSize(300,300);
            frame.setLocationRelativeTo(null);
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.setVisible(true);
        });

        Thread.sleep(1000); // For GUI to finish building, fixes initial NullPointer 

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("some.dat"));
        oos.writeObject(model);
        oos.close();

        System.out.println("Done");
    }
}

MyAbstractModel.java (The Model to Serialize)

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.table.AbstractTableModel;

public class MyAbstractModel extends AbstractTableModel {

    private List<String> list = new ArrayList<>();

    public MyAbstractModel(){
        list = Collections.synchronizedList(new ArrayList<>());
        list.add("A");
        list.add("B");
        list.add("C");
    }

    @Override
    public int getRowCount() {
        return list.size();
    }

    @Override
    public int getColumnCount() {
        return 1;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return rowIndex + "-" + columnIndex;
    }

    @Override
    public String getColumnName(int column) {
        return "Column " + column;
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return String.class;
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return false;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        // TODO
    }
}

This is the Stacktrace I got.

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot invoke "javax.swing.CellRendererPane.paintComponent(java.awt.Graphics, java.awt.Component, java.awt.Container, int, int, int, int, boolean)" because "this.rendererPane" is null at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCell(BasicTableUI.java:2191) at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCells(BasicTableUI.java:2092) at java.desktop/javax.swing.plaf.basic.BasicTableUI.paint(BasicTableUI.java:1888) at com.formdev.flatlaf.ui.FlatTableUI.paint(FlatTableUI.java:397) at java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161) at java.desktop/javax.swing.JComponent.paintComponent(JComponent.java:797) at java.desktop/javax.swing.JComponent.paint(JComponent.java:1074) at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5255) at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedImpl(RepaintManager.java:1643) at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1618) at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1556) at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1323) at java.desktop/javax.swing.JComponent._paintImmediately(JComponent.java:5203) at java.desktop/javax.swing.JComponent.paintImmediately(JComponent.java:5013) at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:865) at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:848) at java.base/java.security.AccessController.doPrivileged(AccessController.java:391) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:848) at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:823) at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:772) at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1884) at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316) at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715) at java.base/java.security.AccessController.doPrivileged(AccessController.java:391) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740) at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90) Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot invoke "javax.swing.JTable.getColumnModel()" because "this.table" is null at java.desktop/javax.swing.plaf.basic.BasicTableUI.getPreferredSize(BasicTableUI.java:1768) at java.desktop/javax.swing.JComponent.getPreferredSize(JComponent.java:1680) at java.desktop/javax.swing.JTable.setWidthsFromPreferredWidths(JTable.java:3205) at java.desktop/javax.swing.JTable.doLayout(JTable.java:3117) at java.desktop/java.awt.Container.validateTree(Container.java:1722) at java.desktop/java.awt.Container.validateTree(Container.java:1731) at java.desktop/java.awt.Container.validateTree(Container.java:1731) at java.desktop/java.awt.Container.validateTree(Container.java:1731) at java.desktop/java.awt.Container.validate(Container.java:1657) at java.desktop/javax.swing.RepaintManager$3.run(RepaintManager.java:745) at java.desktop/javax.swing.RepaintManager$3.run(RepaintManager.java:743) at java.base/java.security.AccessController.doPrivileged(AccessController.java:391) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) at java.desktop/javax.swing.RepaintManager.validateInvalidComponents(RepaintManager.java:742) at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1883) at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316) at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715) at java.base/java.security.AccessController.doPrivileged(AccessController.java:391) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740) at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90) Done

N00ree commented 2 years ago

Not a FlatLaf Issue. The exception is still thrown even without using FlatLaf at all... Sorry about that, have a great weekend all.

N00ree commented 2 years ago

Ok, actually it is a FlatLaf Issue. I couldn't get the hang of it at first and couldn't provide a specific reproducible example but now I have it.

I updated the issue with the current reproducible example (Stack Trace also updated) I added a 1-second sleep before serialization, which helps to resolve the initial Nullpointer. I am also creating the UI on the EDT, which is better.

I don't know the technical background of it, but I think the GUI must be ready before serializing stuff, that's why I added the delay before serializing.

After all this, FlatLaf is causing an additional NullPointer. See the updated example/stacktrace above.

I hope someone can help. Any tips are appreciated.

DevCharly commented 2 years ago

This is a threading issue.

You should not serialize Swing objects on the main thread. Always do it on the AWT thread. Then it works.

But this also serializes the JTable (and maybe the complete component hierarchy) because the table adds a listener to the model. So there is a reference from the model to the table...

I would avoid serialization of table model. Better serialize a custom object.

N00ree commented 2 years ago

Hi there, thanks for the fast reply. Because I am generally an impatient person, I also asked a question on Stackoverflow and also got help from there.

I first thought that a TableModel is more like a model than a Swing Component, but I was wrong. There is (somehow) a connection to the JTable that causes it - as you described above.

Also the Javadoc about AbstractTableModel doesn't recommend serializing it. Maybe this is interesting too.

Even though I only know about the EDT (Event Dispatcher Thread), the AWT thread is probably another naming for it - right?

Simple conclusion for this issue - Putting the TableModel initialization and the serializing code inside the EDT fixes it.

Thank you again and have a great day.

DevCharly commented 2 years ago

Even though I only know about the EDT (Event Dispatcher Thread), the AWT thread is probably another naming for it - right?

yes