Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
8187229: Tree/TableCell: cancel event must return correct editing loc…
…ation

8269136: Tree/TablePosition: must not throw NPE on instantiating with null table

Reviewed-by: mhanl, aghaisas
  • Loading branch information
Jeanette Winzenburg committed Jul 6, 2021
1 parent 0e7cf62 commit 47c2ec3
Show file tree
Hide file tree
Showing 7 changed files with 359 additions and 27 deletions.
Expand Up @@ -299,6 +299,9 @@ private ReadOnlyObjectWrapper<TableView<S>> tableViewPropertyImpl() {
* *
**************************************************************************/

// editing location at start of edit - fix for JDK-8187229
private TablePosition<S, T> editingCellAtStartEdit;

/** {@inheritDoc} */
@Override public void startEdit() {
final TableView<S> table = getTableView();
Expand Down Expand Up @@ -333,6 +336,7 @@ private ReadOnlyObjectWrapper<TableView<S>> tableViewPropertyImpl() {

Event.fireEvent(column, editEvent);
}
editingCellAtStartEdit = new TablePosition<>(table, getIndex(), column);
}

/** {@inheritDoc} */
Expand Down Expand Up @@ -384,7 +388,6 @@ private ReadOnlyObjectWrapper<TableView<S>> tableViewPropertyImpl() {

// reset the editing index on the TableView
if (table != null) {
TablePosition<S,?> editingCell = table.getEditingCell();
if (updateEditingIndex) table.edit(-1, null);

// request focus back onto the table, only if the current focus
Expand All @@ -395,7 +398,7 @@ private ReadOnlyObjectWrapper<TableView<S>> tableViewPropertyImpl() {

CellEditEvent<S,?> editEvent = new CellEditEvent<>(
table,
editingCell,
editingCellAtStartEdit,
TableColumn.editCancelEvent(),
null
);
Expand Down Expand Up @@ -704,9 +707,6 @@ private void updateItem(int oldIndex) {
super.layoutChildren();
}




/***************************************************************************
* *
* Expert API *
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -70,7 +70,7 @@ public TablePosition(@NamedArg("tableView") TableView<S> tableView, @NamedArg("r
super(row, tableColumn);
this.controlRef = new WeakReference<>(tableView);

List<S> items = tableView.getItems();
List<S> items = tableView != null ? tableView.getItems() : null;
this.itemRef = new WeakReference<>(
items != null && row >= 0 && row < items.size() ? items.get(row) : null);

Expand Down
Expand Up @@ -300,6 +300,9 @@ private ReadOnlyObjectWrapper<TreeTableView<S>> treeTableViewPropertyImpl() {
* *
**************************************************************************/

// editing location at start of edit - fix for JDK-8187229
private TreeTablePosition<S, T> editingCellAtStartEdit = null;

/** {@inheritDoc} */
@Override public void startEdit() {
if (isEditing()) return;
Expand Down Expand Up @@ -336,6 +339,7 @@ private ReadOnlyObjectWrapper<TreeTableView<S>> treeTableViewPropertyImpl() {

Event.fireEvent(column, editEvent);
}
editingCellAtStartEdit = new TreeTablePosition<>(table, getIndex(), column);
}

/** {@inheritDoc} */
Expand Down Expand Up @@ -390,9 +394,6 @@ private ReadOnlyObjectWrapper<TreeTableView<S>> treeTableViewPropertyImpl() {

// reset the editing index on the TableView
if (table != null) {
@SuppressWarnings("unchecked")
TreeTablePosition<S,T> editingCell = (TreeTablePosition<S,T>) table.getEditingCell();

if (updateEditingIndex) table.edit(-1, null);

// request focus back onto the table, only if the current focus
Expand All @@ -403,7 +404,7 @@ private ReadOnlyObjectWrapper<TreeTableView<S>> treeTableViewPropertyImpl() {

CellEditEvent<S,T> editEvent = new CellEditEvent<S,T>(
table,
editingCell,
editingCellAtStartEdit,
TreeTableColumn.<S,T>editCancelEvent(),
null
);
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -72,7 +72,8 @@ public TreeTablePosition(@NamedArg("treeTableView") TreeTableView<S> treeTableVi
TreeTablePosition(@NamedArg("treeTableView") TreeTableView<S> treeTableView, @NamedArg("row") int row, @NamedArg("tableColumn") TreeTableColumn<S,T> tableColumn, boolean doLookup) {
super(row, tableColumn);
this.controlRef = new WeakReference<>(treeTableView);
this.treeItemRef = new WeakReference<>(doLookup ? treeTableView.getTreeItem(row) : null);
this.treeItemRef = new WeakReference<>(doLookup ?
(treeTableView != null ? treeTableView.getTreeItem(row) : null) : null);

nonFixedColumnIndex = treeTableView == null || tableColumn == null ? -1 : treeTableView.getVisibleLeafIndex(tableColumn);
}
Expand Down
Expand Up @@ -25,33 +25,44 @@

package test.javafx.scene.control;

import javafx.scene.control.TableRow;
import javafx.scene.control.TreeTableRow;
import javafx.scene.control.skin.TableCellSkin;
import test.com.sun.javafx.scene.control.infrastructure.StageLoader;
import test.com.sun.javafx.scene.control.infrastructure.VirtualFlowTestUtils;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableCellShim;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import static test.com.sun.javafx.scene.control.infrastructure.ControlTestUtils.assertStyleClassContains;
import com.sun.javafx.tk.Toolkit;

import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static test.com.sun.javafx.scene.control.infrastructure.ControlTestUtils.*;
import static test.com.sun.javafx.scene.control.infrastructure.ControlSkinFactory.*;

import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableCellShim;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.skin.TableCellSkin;
import test.com.sun.javafx.scene.control.infrastructure.StageLoader;
import test.com.sun.javafx.scene.control.infrastructure.VirtualFlowTestUtils;

/**
*/
public class TableCellTest {
private TableCell<String,String> cell;
private TableView<String> table;
private TableColumn<String, String> editingColumn;
private TableRow<String> row;
private ObservableList<String> model;
private StageLoader stageLoader;

@Before public void setup() {
Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) -> {
Expand All @@ -65,12 +76,14 @@ public class TableCellTest {
cell = new TableCell<String,String>();
model = FXCollections.observableArrayList("Four", "Five", "Fear"); // "Flop", "Food", "Fizz"
table = new TableView<String>(model);
editingColumn = new TableColumn<>("TEST");

row = new TableRow<>();
}

@After
public void cleanup() {
if (stageLoader != null) stageLoader.dispose();
Thread.currentThread().setUncaughtExceptionHandler(null);
}

Expand Down Expand Up @@ -402,6 +415,120 @@ public void testCellInUneditableColumnIsNotEditable() {
assertFalse(cell.isEditing());
}

/**
* Basic config of table-/cell to allow testing of editEvents:
* table is editable, has editingColumn and cell is configured with table and column.
*/
private void setupForEditing() {
table.setEditable(true);
table.getColumns().add(editingColumn);
// FIXME: default cell (of tableColumn) needs not-null value for firing cancel
editingColumn.setCellValueFactory(cc -> new SimpleObjectProperty<>(""));

cell.updateTableView(table);
cell.updateTableColumn(editingColumn);
}

@Test
public void testEditCancelEventAfterCancelOnCell() {
setupForEditing();
int editingIndex = 1;
cell.updateIndex(editingIndex);
table.edit(editingIndex, editingColumn);
TablePosition<?, ?> editingPosition = table.getEditingCell();
List<CellEditEvent<?, ?>> events = new ArrayList<>();
editingColumn.setOnEditCancel(events::add);
cell.cancelEdit();
assertEquals("column must have received editCancel", 1, events.size());
assertEquals("editing location of cancel event", editingPosition, events.get(0).getTablePosition());
}

@Test
public void testEditCancelEventAfterCancelOnTable() {
setupForEditing();
int editingIndex = 1;
cell.updateIndex(editingIndex);
table.edit(editingIndex, editingColumn);
TablePosition<?, ?> editingPosition = table.getEditingCell();
List<CellEditEvent<?, ?>> events = new ArrayList<>();
editingColumn.setOnEditCancel(events::add);
table.edit(-1, null);
assertEquals("column must have received editCancel", 1, events.size());
assertEquals("editing location of cancel event", editingPosition, events.get(0).getTablePosition());
}

@Test
public void testEditCancelEventAfterCellReuse() {
setupForEditing();
int editingIndex = 1;
cell.updateIndex(editingIndex);
table.edit(editingIndex, editingColumn);
TablePosition<?, ?> editingPosition = table.getEditingCell();
List<CellEditEvent<?, ?>> events = new ArrayList<>();
editingColumn.setOnEditCancel(events::add);
cell.updateIndex(0);
assertEquals("column must have received editCancel", 1, events.size());
assertEquals("editing location of cancel event", editingPosition, events.get(0).getTablePosition());
}

@Test
public void testEditCancelEventAfterModifyItems() {
setupForEditing();
stageLoader = new StageLoader(table);
int editingIndex = 1;
table.edit(editingIndex, editingColumn);
TablePosition<?, ?> editingPosition = table.getEditingCell();
List<CellEditEvent<?, ?>> events = new ArrayList<>();
editingColumn.setOnEditCancel(events::add);
table.getItems().add(0, "added");
Toolkit.getToolkit().firePulse();
assertEquals("column must have received editCancel", 1, events.size());
assertEquals("editing location of cancel event", editingPosition, events.get(0).getTablePosition());
}

/**
* Test that removing the editing item implicitly cancels an ongoing
* edit and fires a correct cancel event.
*/
@Test
public void testEditCancelEventAfterRemoveEditingItem() {
setupForEditing();
stageLoader = new StageLoader(table);
int editingIndex = 1;
table.edit(editingIndex, editingColumn);
TablePosition<?, ?> editingPosition = table.getEditingCell();
List<CellEditEvent<?, ?>> events = new ArrayList<>();
editingColumn.setOnEditCancel(events::add);
table.getItems().remove(editingIndex);
Toolkit.getToolkit().firePulse();
assertNull("sanity: editing terminated on items modification", table.getEditingCell());
assertEquals("column must have received editCancel", 1, events.size());
assertEquals("editing location of cancel event", editingPosition, events.get(0).getTablePosition());
}

/**
* Test that removing the editing item does not cause a memory leak.
*/
@Test
public void testEditCancelMemoryLeakAfterRemoveEditingItem() {
TableView<MenuItem> table = new TableView<>(FXCollections.observableArrayList(
new MenuItem("some"), new MenuItem("other")));
TableColumn<MenuItem, String> editingColumn = new TableColumn<>("Text");
editingColumn.setCellValueFactory(cc -> new SimpleObjectProperty<>(""));
table.setEditable(true);
table.getColumns().add(editingColumn);
stageLoader = new StageLoader(table);
int editingIndex = 1;
MenuItem editingItem = table.getItems().get(editingIndex);
WeakReference<MenuItem> itemRef = new WeakReference<>(editingItem);
table.edit(editingIndex, editingColumn);
table.getItems().remove(editingIndex);
editingItem = null;
Toolkit.getToolkit().firePulse();
attemptGC(itemRef);
assertEquals("item must be gc'ed", null, itemRef.get());
}

/**
* Test that cell.cancelEdit can switch table editing off
* even if a subclass violates its contract.
Expand Down
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package test.javafx.scene.control;

import org.junit.Test;

import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTablePosition;

/**
* Test Tree/TablePosition.
*/
public class TablePositionBaseTest {

//---------- TableView

/**
* JDK-8269136: must not throw on null Table.
*/
@Test
public void testNullTable() {
new TablePosition<>(null, 2, new TableColumn<>());
}

//------------- TreeTableView

/**
* JDK-8269136: must not throw on null TreeTable.
*/
@Test
public void testNullTreeTable() {
new TreeTablePosition<>(null, 2, new TreeTableColumn<>());
}

}

1 comment on commit 47c2ec3

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.