Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8244056

TableView Layout NullPointerException

XMLWordPrintable

    • x86_64
    • windows_10

      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


            aghaisas Ajit Ghaisas
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: