JDK-8244056 : TableView Layout NullPointerException
  • Type: Bug
  • Component: javafx
  • Sub-Component: controls
  • Affected Version: openjfx13
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_10
  • CPU: x86_64
  • Submitted: 2020-04-28
  • Updated: 2020-05-07
  • Resolved: 2020-05-07
Related Reports
Duplicate :  
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
JavaFX 13

A DESCRIPTION OF THE PROBLEM :
When a TableView column is frequently invalidated, while it's contents are changing, it will result a layout that attempts to recreate a row's cells that will always fail with a null pointer exception.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Frequently update a table's contents while another binding invalidates the column. See attached test case.

ACTUAL -
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
    at javafx.controls/javafx.scene.control.skin.TableCellSkin.tableColumnProperty(TableCellSkin.java:97)
    at javafx.controls/javafx.scene.control.skin.TableCellSkinBase.getTableColumn(TableCellSkinBase.java:123)
    at javafx.controls/javafx.scene.control.skin.TableCellSkinBase.dispose(TableCellSkinBase.java:136)
    at javafx.controls/javafx.scene.control.skin.TableCellSkin.dispose(TableCellSkin.java:88)
    at javafx.controls/javafx.scene.control.Control$2.invalidated(Control.java:267)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:147)
    at javafx.graphics/javafx.css.StyleableObjectProperty.set(StyleableObjectProperty.java:82)
    at javafx.controls/javafx.scene.control.Control$2.set(Control.java:250)
    at javafx.controls/javafx.scene.control.Control$2.set(Control.java:233)
    at javafx.controls/javafx.scene.control.Control.setSkin(Control.java:230)
    at javafx.controls/javafx.scene.control.skin.TableRowSkinBase.recreateCells(TableRowSkinBase.java:715)
    at javafx.controls/javafx.scene.control.skin.TableRowSkinBase.updateCells(TableRowSkinBase.java:505)
    at javafx.controls/javafx.scene.control.skin.TableRowSkinBase.checkState(TableRowSkinBase.java:649)
    at javafx.controls/javafx.scene.control.skin.TableRowSkinBase.computePrefHeight(TableRowSkinBase.java:588)
    at javafx.controls/javafx.scene.control.Control.computePrefHeight(Control.java:570)
    at javafx.graphics/javafx.scene.Parent.prefHeight(Parent.java:1039)
    at javafx.graphics/javafx.scene.layout.Region.prefHeight(Region.java:1559)
    at javafx.controls/javafx.scene.control.skin.VirtualFlow.resizeCell(VirtualFlow.java:1923)
    at javafx.controls/javafx.scene.control.skin.VirtualFlow.addLeadingCells(VirtualFlow.java:2030)
    at javafx.controls/javafx.scene.control.skin.VirtualFlow.layoutChildren(VirtualFlow.java:1250)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1206)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213)
    at javafx.graphics/javafx.scene.Scene.doLayoutPass(Scene.java:576)
    at javafx.graphics/javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2482)
    at javafx.graphics/com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:412)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:389)
    at javafx.graphics/com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:411)
    at javafx.graphics/com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:438)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:563)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:543)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:536)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:342)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
    at java.base/java.lang.Thread.run(Thread.java:835)

---------- BEGIN SOURCE ----------
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
import org.junit.jupiter.api.Test;
import org.testfx.framework.junit5.ApplicationTest;
import org.testfx.util.WaitForAsyncUtils;

import java.util.List;

public class TableViewBug extends ApplicationTest {

    private ObjectProperty<List<Item>> data;

    private TableView<Item> table;

    @Override
    public void start(Stage stage) {
        initialize();
        stage.setScene(new Scene(table, 500, 500));
        stage.show();
    }

    private void initialize() {
        data = new SimpleObjectProperty<>();
        table = new TableView<>();

        TableColumn<Item, String> dataCol = new TableColumn<>("Data");
        dataCol.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().data.toString()));

        TableColumn<Item, String> labelCol = new TableColumn<>("Label");
        labelCol.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getLabel()));

        table.getColumns().add(dataCol);
        table.getColumns().add(labelCol);

        dataCol.prefWidthProperty().bind(table.widthProperty().multiply(.5));
        labelCol.prefWidthProperty().bind(table.widthProperty().multiply(.5));

        data.addListener((prop, oldVal, newVal) -> table.setItems(FXCollections.observableList(data.get())));

        // This binding is what causes the trouble, even if the value doesn't change whenever it's evaluated it invalidates the TableViewRows
        labelCol.visibleProperty().bind(Bindings.createBooleanBinding(() -> true, data));
    }

    @Test
    void bug() throws Throwable {
        WaitForAsyncUtils.asyncFx(() -> data.set(List.of(new Item(1, "one"))));
        // Number of iterations seems to be related to the delay between data updates (sleep amount below)
        for (int i = 0; i < 200; i++) {
            Thread.sleep(20); // Delay simulates work being done to produce data
            Platform.runLater(() ->
                    data.set(List.of(new Item(2, "two"))));
            WaitForAsyncUtils.checkException(); // THis is to end the test after the first exception shows up, if not it'll keep going and hang with more
        }
    }

    private static class Item {
        private final Integer data;
        private final String label;

        private Item(Integer data, String label) {
            this.data = data;
            this.label = label;
        }

        public Integer getData() {
            return data;
        }

        public String getLabel() {
            return label;
        }
    }
}

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

CUSTOMER SUBMITTED WORKAROUND :
Avoid invalidating the column.

FREQUENCY : always



Comments
looks very much like a duplicate ..
29-04-2020