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

TreeTableView.TreeTableViewSelectionModel nulls and unseen multi-select

    Details

    • Subcomponent:
    • CPU:
      x86_64
    • OS:
      generic

      Description

      FULL PRODUCT VERSION :
      Java SE 1.8.0_162-b12

      ADDITIONAL OS VERSION INFORMATION :
      Duplicated on both with same jdk:

      Windows 10.0.1.14393
      RHEL 3.10.0-693.17.1.el7.x86_64

      A DESCRIPTION OF THE PROBLEM :
      TreeTableView.TreeTableViewSelectionModel gets out of date while deleting objects.

      Using the given test case, the SelectionModel can be caused to contain null selected items, throw IndexOutOfBoundsExceptions, grow the selection during deletes, and have selections that are not highlighted / cannot be removed without reselecting the miscellaneous additions.

      There are multiple test cases/unexpected behaviors included in this single report because it is likely they are all related.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Create the three files below in the default package (SelectionModelTestCase.java, SelectionModelTestCase.fxml, and SelectionModelTestCaseController.java)

      Using the SelectionModelTestCase, each test is written as though the test was just started but can be run back-to-back.
      If more tree items are needed for the test, use the add button to populate more

      1 - Select "child 6" and press "Delete".
          There will be two items selected after the removal.
          Pressing "Delete" again will result in a third selection.

      2 - Select the last item in the list and press "Delete".
          There will now be a null object in the selection list.

      3 - Select the last item in the list and press "Delete" to put a null object in the selection list.
          Sort the "name" column to produce an IndexOutOfBoundsException

      4 - Select the last item in the list and press "Delete" two or more times.
          After the first deletion, each delete event no longer sends a selection event.
          This can be verified by adding a print() to the delete() function: duplicate prints should be made for each delete,
              but is reduced to one until a new selection is made.

      5 - Select the last item in the list and press "Delete" two times, and press on an item not listed in the print-out.
          More than one item will be selected, but the second will not be highlighted. This extra selection will stay until
          it is directly selected (null items occupying the extra selection spot will not disappear)

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Using the same code with TableView instead of TreeTableView produces the expected results
      ACTUAL -
      Actual results are recorded in the "Steps to Reproduce"

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      Crashes do not occur, and only one of the above cases results in an IndexOutOfBoundsException while there are nulls / -1 indices in the selection

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      SelectionModelTestCase.java:

      import javafx.application.Application;
      import javafx.fxml.FXMLLoader;
      import javafx.scene.Scene;
      import javafx.scene.layout.BorderPane;
      import javafx.stage.Stage;

      import java.io.IOException;

      public class SelectionModelTestCase extends Application
      {

        public static void main(String[] args)
        {
          launch(args);
        }

        @Override
        public void start(Stage primaryStage) throws IOException
        {
          FXMLLoader loader = new FXMLLoader(SelectionModelTestCase.class.getResource("SelectionModelTestCase.fxml"));
          BorderPane borderPane = loader.load();
          Scene scene = new Scene(borderPane);
          primaryStage.setScene(scene);
          primaryStage.show();
          primaryStage.setTitle("Selection Model");
        }
      }





      SelectionModelTestCase.fxml:

      <?xml version="1.0" encoding="UTF-8"?>

      <?import javafx.scene.control.*?>
      <?import javafx.scene.layout.*?>

      <BorderPane xmlns="http://javafx.com/javafx"
                  xmlns:fx="http://javafx.com/fxml"
                  fx:controller="SelectionModelTestCaseController"
                  prefHeight="400.0" prefWidth="600.0">
          <top>
              <HBox spacing="5">
                  <Button text="Add" onAction="#add"/>
                  <Button text="Delete" onAction="#delete"/>
              </HBox>
          </top>
          <center>
              <TreeTableView fx:id="treeTable">
                  <columns>
                      <TreeTableColumn fx:id="nameColumn" text="Object Name" prefWidth="300"/>
                  </columns>
              </TreeTableView>
          </center>
      </BorderPane>






      SelectionModelTestCaseController.java:

      import javafx.beans.InvalidationListener;
      import javafx.beans.WeakInvalidationListener;
      import javafx.fxml.FXML;
      import javafx.scene.control.SelectionMode;
      import javafx.scene.control.TreeItem;
      import javafx.scene.control.TreeTableCell;
      import javafx.scene.control.TreeTableColumn;
      import javafx.scene.control.TreeTableView;

      import java.util.ArrayList;
      import java.util.stream.Collectors;

      public class SelectionModelTestCaseController
      {
        private static final String NULL_ITEM = "null item";
        private static final String NULL_DATA = "null data";

        @FXML
        private TreeTableView<TestData> treeTable;

        @FXML
        private TreeTableColumn<TestData, String> nameColumn;

        private int childNumber = 1; // the number of the added child
        private TreeItem<TestData> root; // the tree root: for adding and removing items
        private InvalidationListener printSelection = observable -> print(); // prints the current selection

        public void initialize()
        {
          root = new TreeItem<>(new TestData("root"));

          // add the root node and save for later
          treeTable.setRoot(root);
          treeTable.setShowRoot(false);
          treeTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

          // add printing for the current selection
          treeTable.getSelectionModel().getSelectedItems().addListener(new WeakInvalidationListener(printSelection));

          // set cell factory, otherwise won't render
          nameColumn.setCellFactory(column -> new CustomTreeCell());

          // add some starting children
          for (int i = 0; i < 10; i++)
          {
            add();
          }
        }

        @FXML
        private void delete()
        {
          // remove objects from the tree and redraw. New ArrayList does not change behavior, but seems safer
          root.getChildren().removeAll(new ArrayList<>(treeTable.getSelectionModel().getSelectedItems()));
        }

        @FXML
        private void add()
        {
          // just adds children with incrementing numbers
          root.getChildren().add(new TreeItem<>(new TestData("child " + childNumber++)));
        }

        private void print()
        {
          System.out.println(treeTable.getSelectionModel().getSelectedItems().stream()
                                      .map(this::getStringForItem) // map to string
                                      .collect(Collectors.joining(", "))); // to be joined
        }

        // Gets a string to represent each tree item. Allows for null items and data.
        private String getStringForItem(TreeItem<TestData> treeItem)
        {
          if(treeItem == null)
          {
            return NULL_ITEM;
          }

          TestData data = treeItem.getValue();
          return data == null ? NULL_DATA : data.getName();
        }

        // for use in this test case
        private static class CustomTreeCell extends TreeTableCell<TestData, String>
        {
          @Override
          protected void updateItem(String item, boolean empty)
          {
            super.updateItem(item, empty);

            TestData data = getTreeTableRow().getItem();
            if (empty || data == null)
            {
              setText("");
            }
            else
            {
              setText(data.getName());
            }
          }
        }

        // for use in this test case
        private static class TestData
        {
          private String name;

          private TestData(String name)
          {
            this.name = name;
          }

          private String getName()
          {
            return name;
          }
        }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Filter nulls

        Attachments

          Activity

            People

            • Assignee:
              aghaisas Ajit Ghaisas
              Reporter:
              pmangal Priyanka Mangal (Inactive)
            • Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

              • Created:
                Updated: