1 /*
   2  * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control;
  27 
  28 import javafx.css.PseudoClass;
  29 import javafx.beans.InvalidationListener;
  30 import javafx.beans.WeakInvalidationListener;
  31 import javafx.beans.value.ObservableValue;
  32 import javafx.collections.ListChangeListener;
  33 import javafx.event.Event;
  34 import javafx.scene.AccessibleAction;
  35 import javafx.scene.AccessibleAttribute;
  36 import javafx.scene.AccessibleRole;
  37 import javafx.scene.control.TableView.TableViewFocusModel;
  38 
  39 import com.sun.javafx.scene.control.skin.TableCellSkin;
  40 import javafx.collections.WeakListChangeListener;
  41 import java.lang.ref.WeakReference;
  42 import java.util.List;
  43 import javafx.beans.property.ReadOnlyObjectProperty;
  44 import javafx.beans.property.ReadOnlyObjectWrapper;
  45 import javafx.collections.FXCollections;
  46 
  47 import javafx.scene.control.TableColumn.CellEditEvent;
  48 
  49 
  50 /**
  51  * Represents a single row/column intersection in a {@link TableView}. To 
  52  * represent this intersection, a TableCell contains an 
  53  * {@link #indexProperty() index} property, as well as a 
  54  * {@link #tableColumnProperty() tableColumn} property. In addition, a TableCell
  55  * instance knows what {@link TableRow} it exists in.
  56  *
  57  * <p><strong>A note about selection:</strong> A TableCell visually shows it is
  58  * selected when two conditions are met:
  59  * <ol>
  60  *   <li>The {@link TableSelectionModel#isSelected(int, TableColumnBase)} method
  61  *   returns true for the row / column that this cell represents, and</li>
  62  *   <li>The {@link javafx.scene.control.TableSelectionModel#cellSelectionEnabledProperty() cell selection mode}
  63  *   property is set to true (to represent that it is allowable to select
  64  *   individual cells (and not just rows of cells)).</li>
  65  * </ol>
  66  * </p>
  67  * 
  68  * @see TableView
  69  * @see TableColumn
  70  * @see Cell
  71  * @see IndexedCell
  72  * @see TableRow
  73  * @param <S> The type of the TableView generic type (i.e. S == TableView&lt;S&gt;).
  74  *           This should also match with the first generic type in TableColumn.
  75  * @param <T> The type of the item contained within the Cell.
  76  * @since JavaFX 2.0
  77  */
  78 public class TableCell<S,T> extends IndexedCell<T> {
  79     
  80     /***************************************************************************
  81      *                                                                         *
  82      * Constructors                                                            *
  83      *                                                                         *
  84      **************************************************************************/
  85 
  86     /**
  87      * Constructs a default TableCell instance with a style class of 'table-cell'
  88      */
  89     public TableCell() {
  90         getStyleClass().addAll(DEFAULT_STYLE_CLASS);
  91         setAccessibleRole(AccessibleRole.TABLE_CELL);
  92         
  93         updateColumnIndex();
  94     }
  95 
  96 
  97 
  98     /***************************************************************************
  99      *                                                                         *
 100      * Private fields                                                          *
 101      *                                                                         *
 102      **************************************************************************/
 103 
 104     // package for testing
 105     boolean lockItemOnEdit = false;
 106 
 107 
 108     /***************************************************************************
 109      *                                                                         *
 110      * Callbacks and Events                                                    *
 111      *                                                                         *
 112      **************************************************************************/
 113     
 114     private boolean itemDirty = false;
 115     
 116     /*
 117      * This is the list observer we use to keep an eye on the SelectedCells
 118      * ObservableList in the table view. Because it is possible that the table can
 119      * be mutated, we create this observer here, and add/remove it from the
 120      * storeTableView method.
 121      */
 122     private ListChangeListener<TablePosition> selectedListener = c -> {
 123         while (c.next()) {
 124             if (c.wasAdded() || c.wasRemoved()) {
 125                 updateSelection();
 126             }
 127         }
 128     };
 129 
 130     // same as above, but for focus
 131     private final InvalidationListener focusedListener = value -> {
 132         updateFocus();
 133     };
 134 
 135     // same as above, but for for changes to the properties on TableRow
 136     private final InvalidationListener tableRowUpdateObserver = value -> {
 137         itemDirty = true;
 138         requestLayout();
 139     };
 140     
 141     private final InvalidationListener editingListener = value -> {
 142         updateEditing();
 143     };
 144     
 145     private ListChangeListener<TableColumn<S,?>> visibleLeafColumnsListener = c -> {
 146         updateColumnIndex();
 147     };
 148     
 149     private ListChangeListener<String> columnStyleClassListener = c -> {
 150         while (c.next()) {
 151             if (c.wasRemoved()) {
 152                 getStyleClass().removeAll(c.getRemoved());
 153             }
 154 
 155             if (c.wasAdded()) {
 156                 getStyleClass().addAll(c.getAddedSubList());
 157             }
 158         }
 159     };
 160 
 161     private final InvalidationListener columnStyleListener = value -> {
 162         if (getTableColumn() != null) {
 163             possiblySetStyle(getTableColumn().getStyle());
 164         }
 165     };
 166 
 167     private final InvalidationListener columnIdListener = value -> {
 168         if (getTableColumn() != null) {
 169             possiblySetId(getTableColumn().getId());
 170         }
 171     };
 172     
 173     private final WeakListChangeListener<TablePosition> weakSelectedListener =
 174             new WeakListChangeListener<>(selectedListener);
 175     private final WeakInvalidationListener weakFocusedListener = 
 176             new WeakInvalidationListener(focusedListener);
 177     private final WeakInvalidationListener weaktableRowUpdateObserver = 
 178             new WeakInvalidationListener(tableRowUpdateObserver);
 179     private final WeakInvalidationListener weakEditingListener = 
 180             new WeakInvalidationListener(editingListener);
 181     private final WeakInvalidationListener weakColumnStyleListener =
 182             new WeakInvalidationListener(columnStyleListener);
 183     private final WeakInvalidationListener weakColumnIdListener =
 184             new WeakInvalidationListener(columnIdListener);
 185     private final WeakListChangeListener<TableColumn<S,?>> weakVisibleLeafColumnsListener =
 186             new WeakListChangeListener<>(visibleLeafColumnsListener);
 187     private final WeakListChangeListener<String> weakColumnStyleClassListener =
 188             new WeakListChangeListener<String>(columnStyleClassListener);
 189 
 190     
 191     /***************************************************************************
 192      *                                                                         *
 193      * Properties                                                              *
 194      *                                                                         *
 195      **************************************************************************/
 196     
 197     // --- TableColumn
 198     private ReadOnlyObjectWrapper<TableColumn<S,T>> tableColumn = new ReadOnlyObjectWrapper<TableColumn<S,T>>() {
 199         @Override protected void invalidated() {
 200             updateColumnIndex();
 201         }
 202 
 203         @Override public Object getBean() {
 204             return TableCell.this;
 205         }
 206 
 207         @Override public String getName() {
 208             return "tableColumn";
 209         }
 210     };
 211     /**
 212      * The TableColumn instance that backs this TableCell.
 213      */
 214     public final ReadOnlyObjectProperty<TableColumn<S,T>> tableColumnProperty() { return tableColumn.getReadOnlyProperty(); }
 215     private void setTableColumn(TableColumn<S,T> value) { tableColumn.set(value); }
 216     public final TableColumn<S,T> getTableColumn() { return tableColumn.get(); }
 217     
 218     
 219     // --- TableView
 220     private ReadOnlyObjectWrapper<TableView<S>> tableView;
 221     private void setTableView(TableView<S> value) {
 222         tableViewPropertyImpl().set(value);
 223     }
 224     public final TableView<S> getTableView() {
 225         return tableView == null ? null : tableView.get();
 226     }
 227 
 228     /**
 229      * The TableView associated with this TableCell.
 230      */
 231     public final ReadOnlyObjectProperty<TableView<S>> tableViewProperty() {
 232         return tableViewPropertyImpl().getReadOnlyProperty();
 233     }
 234 
 235     private ReadOnlyObjectWrapper<TableView<S>> tableViewPropertyImpl() {
 236         if (tableView == null) {
 237             tableView = new ReadOnlyObjectWrapper<TableView<S>>() {
 238                 private WeakReference<TableView<S>> weakTableViewRef;
 239                 @Override protected void invalidated() {
 240                     TableView.TableViewSelectionModel<S> sm;
 241                     TableViewFocusModel<S> fm;
 242                     
 243                     if (weakTableViewRef != null) {
 244                         cleanUpTableViewListeners(weakTableViewRef.get());
 245                     }
 246                     
 247                     if (get() != null) {
 248                         sm = get().getSelectionModel();
 249                         if (sm != null) {
 250                             sm.getSelectedCells().addListener(weakSelectedListener);
 251                         }
 252 
 253                         fm = get().getFocusModel();
 254                         if (fm != null) {
 255                             fm.focusedCellProperty().addListener(weakFocusedListener);
 256                         }
 257 
 258                         get().editingCellProperty().addListener(weakEditingListener);
 259                         get().getVisibleLeafColumns().addListener(weakVisibleLeafColumnsListener);
 260                         
 261                         weakTableViewRef = new WeakReference<TableView<S>>(get());
 262                     }
 263                     
 264                     updateColumnIndex();
 265                 }
 266 
 267                 @Override public Object getBean() {
 268                     return TableCell.this;
 269                 }
 270 
 271                 @Override public String getName() {
 272                     return "tableView";
 273                 }
 274             };
 275         }
 276         return tableView;
 277     }
 278 
 279 
 280 
 281     // --- TableRow
 282     /**
 283      * The TableRow that this TableCell currently finds itself placed within.
 284      * The TableRow may be null early in the TableCell lifecycle, in the period
 285      * between the TableCell being instantiated and being set into an owner
 286      * TableRow.
 287      */
 288     private ReadOnlyObjectWrapper<TableRow> tableRow = new ReadOnlyObjectWrapper<TableRow>(this, "tableRow");
 289     private void setTableRow(TableRow value) { tableRow.set(value); }
 290     public final TableRow getTableRow() { return tableRow.get(); }
 291     public final ReadOnlyObjectProperty<TableRow> tableRowProperty() { return tableRow;  }
 292 
 293 
 294 
 295     /***************************************************************************
 296      *                                                                         *
 297      * Editing API                                                             *
 298      *                                                                         *
 299      **************************************************************************/
 300 
 301     /** {@inheritDoc} */
 302     @Override public void startEdit() {
 303         final TableView<S> table = getTableView();
 304         final TableColumn<S,T> column = getTableColumn();
 305         if (! isEditable() ||
 306                 (table != null && ! table.isEditable()) ||
 307                 (column != null && ! getTableColumn().isEditable())) {
 308             return;
 309         }
 310 
 311         // We check the boolean lockItemOnEdit field here, as whilst we want to
 312         // updateItem normally, when it comes to unit tests we can't have the
 313         // item change in all circumstances.
 314         if (! lockItemOnEdit) {
 315             updateItem(-1);
 316         }
 317 
 318         // it makes sense to get the cell into its editing state before firing
 319         // the event to listeners below, so that's what we're doing here
 320         // by calling super.startEdit().
 321         super.startEdit();
 322         
 323         if (column != null) {
 324             CellEditEvent<S,?> editEvent = new CellEditEvent<>(
 325                 table,
 326                 table.getEditingCell(),
 327                 TableColumn.editStartEvent(),
 328                 null
 329             );
 330 
 331             Event.fireEvent(column, editEvent);
 332         }
 333     }
 334 
 335     /** {@inheritDoc} */
 336     @Override public void commitEdit(T newValue) {
 337         if (! isEditing()) return;
 338         
 339         final TableView<S> table = getTableView();
 340         if (table != null) {
 341             // Inform the TableView of the edit being ready to be committed.
 342             CellEditEvent editEvent = new CellEditEvent(
 343                 table,
 344                 table.getEditingCell(),
 345                 TableColumn.editCommitEvent(),
 346                 newValue
 347             );
 348 
 349             Event.fireEvent(getTableColumn(), editEvent);
 350         }
 351 
 352         // inform parent classes of the commit, so that they can switch us
 353         // out of the editing state.
 354         // This MUST come before the updateItem call below, otherwise it will
 355         // call cancelEdit(), resulting in both commit and cancel events being
 356         // fired (as identified in RT-29650)
 357         super.commitEdit(newValue);
 358 
 359         // update the item within this cell, so that it represents the new value
 360         updateItem(newValue, false);
 361 
 362         if (table != null) {
 363             // reset the editing cell on the TableView
 364             table.edit(-1, null);
 365 
 366             // request focus back onto the table, only if the current focus
 367             // owner has the table as a parent (otherwise the user might have
 368             // clicked out of the table entirely and given focus to something else.
 369             // It would be rude of us to request it back again.
 370             ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table);
 371         }
 372     }
 373 
 374     /** {@inheritDoc} */
 375     @Override public void cancelEdit() {
 376         if (! isEditing()) return;
 377 
 378         final TableView<S> table = getTableView();
 379 
 380         super.cancelEdit();
 381 
 382         // reset the editing index on the TableView
 383         if (table != null) {
 384             TablePosition<S,?> editingCell = table.getEditingCell();
 385             if (updateEditingIndex) table.edit(-1, null);
 386 
 387             // request focus back onto the table, only if the current focus
 388             // owner has the table as a parent (otherwise the user might have
 389             // clicked out of the table entirely and given focus to something else.
 390             // It would be rude of us to request it back again.
 391             ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table);
 392 
 393             CellEditEvent<S,?> editEvent = new CellEditEvent<>(
 394                 table,
 395                 editingCell,
 396                 TableColumn.editCancelEvent(),
 397                 null
 398             );
 399 
 400             Event.fireEvent(getTableColumn(), editEvent);
 401         }
 402     }
 403     
 404     
 405     
 406     /* *************************************************************************
 407      *                                                                         *
 408      * Overriding methods                                                      *
 409      *                                                                         *
 410      **************************************************************************/
 411 
 412     /** {@inheritDoc} */
 413     @Override public void updateSelected(boolean selected) {
 414         // copied from Cell, with the first conditional clause below commented 
 415         // out, as it is valid for an empty TableCell to be selected, as long 
 416         // as the parent TableRow is not empty (see RT-15529).
 417         /*if (selected && isEmpty()) return;*/
 418         if (getTableRow() == null || getTableRow().isEmpty()) return;
 419         setSelected(selected);
 420     }
 421 
 422     /** {@inheritDoc} */
 423     @Override protected Skin<?> createDefaultSkin() {
 424         return new TableCellSkin<S,T>(this);
 425     }
 426     
 427 //    @Override public void dispose() {
 428 //        cleanUpTableViewListeners(getTableView());
 429 //        
 430 //        if (currentObservableValue != null) {
 431 //            currentObservableValue.removeListener(weaktableRowUpdateObserver);
 432 //        }
 433 //        
 434 //        super.dispose();
 435 //    }
 436 
 437     /* *************************************************************************
 438     *                                                                         *
 439     * Private Implementation                                                  *
 440     *                                                                         *
 441     **************************************************************************/
 442     
 443     private void cleanUpTableViewListeners(TableView<S> tableView) {
 444         if (tableView != null) {
 445             TableView.TableViewSelectionModel<S> sm = tableView.getSelectionModel();
 446             if (sm != null) {
 447                 sm.getSelectedCells().removeListener(weakSelectedListener);
 448             }
 449 
 450             TableViewFocusModel<S> fm = tableView.getFocusModel();
 451             if (fm != null) {
 452                 fm.focusedCellProperty().removeListener(weakFocusedListener);
 453             }
 454 
 455             tableView.editingCellProperty().removeListener(weakEditingListener);
 456             tableView.getVisibleLeafColumns().removeListener(weakVisibleLeafColumnsListener);
 457         }        
 458     }
 459 
 460     @Override void indexChanged(int oldIndex, int newIndex) {
 461         super.indexChanged(oldIndex, newIndex);
 462 
 463         // Ideally we would just use the following two lines of code, rather
 464         // than the updateItem() call beneath, but if we do this we end up with
 465         // RT-22428 where all the columns are collapsed.
 466         // itemDirty = true;
 467         // requestLayout();
 468         updateItem(oldIndex);
 469         updateSelection();
 470         updateFocus();
 471     }
 472     
 473     private boolean isLastVisibleColumn = false;
 474     private int columnIndex = -1;
 475     
 476     private void updateColumnIndex() {
 477         TableView<S> tv = getTableView();
 478         TableColumn<S,T> tc = getTableColumn();
 479         columnIndex = tv == null || tc == null ? -1 : tv.getVisibleLeafIndex(tc);
 480         
 481         // update the pseudo class state regarding whether this is the last
 482         // visible cell (i.e. the right-most). 
 483         isLastVisibleColumn = getTableColumn() != null &&
 484                 columnIndex != -1 && 
 485                 columnIndex == getTableView().getVisibleLeafColumns().size() - 1;
 486         pseudoClassStateChanged(PSEUDO_CLASS_LAST_VISIBLE, isLastVisibleColumn);
 487     }
 488 
 489     private void updateSelection() {
 490         /*
 491          * This cell should be selected if the selection mode of the table
 492          * is cell-based, and if the row and column that this cell represents
 493          * is selected.
 494          *
 495          * If the selection mode is not cell-based, then the listener in the
 496          * TableRow class might pick up the need to set an entire row to be
 497          * selected.
 498          */
 499         if (isEmpty()) return;
 500 
 501         final boolean isSelected = isSelected();
 502         if (! isInCellSelectionMode()) {
 503             if (isSelected) {
 504                 updateSelected(false);
 505             }
 506             return;
 507         }
 508 
 509         final TableView<S> tableView = getTableView();
 510         if (getIndex() == -1 || tableView == null) return;
 511 
 512         TableSelectionModel<S> sm = tableView.getSelectionModel();
 513         if (sm == null) {
 514             updateSelected(false);
 515             return;
 516         }
 517 
 518         boolean isSelectedNow = sm.isSelected(getIndex(), getTableColumn());
 519         if (isSelected == isSelectedNow) return;
 520 
 521         updateSelected(isSelectedNow);
 522     }
 523 
 524     private void updateFocus() {
 525         final boolean isFocused = isFocused();
 526         if (! isInCellSelectionMode()) {
 527             if (isFocused) {
 528                 setFocused(false);
 529             }
 530             return;
 531         }
 532 
 533         final TableView<S> tableView = getTableView();
 534         final TableRow<S> tableRow = getTableRow();
 535         final int index = getIndex();
 536         if (index == -1 || tableView == null || tableRow == null) return;
 537 
 538         final TableViewFocusModel<S> fm = tableView.getFocusModel();
 539         if (fm == null) {
 540             setFocused(false);
 541             return;
 542         }
 543 
 544         boolean isFocusedNow = fm != null && fm.isFocused(index, getTableColumn());
 545 
 546         setFocused(isFocusedNow);
 547     }
 548 
 549     private void updateEditing() {
 550         if (getIndex() == -1 || getTableView() == null) return;
 551 
 552         TablePosition<S,?> editCell = getTableView().getEditingCell();
 553         boolean match = match(editCell);
 554         
 555         if (match && ! isEditing()) {
 556             startEdit();
 557         } else if (! match && isEditing()) {
 558             // If my index is not the one being edited then I need to cancel
 559             // the edit. The tricky thing here is that as part of this call
 560             // I cannot end up calling list.edit(-1) the way that the standard
 561             // cancelEdit method would do. Yet, I need to call cancelEdit
 562             // so that subclasses which override cancelEdit can execute. So,
 563             // I have to use a kind of hacky flag workaround.
 564             updateEditingIndex = false;
 565             cancelEdit();
 566             updateEditingIndex = true;
 567         }
 568     }
 569     private boolean updateEditingIndex = true;
 570 
 571     private boolean match(TablePosition<S,?> pos) {
 572         return pos != null && pos.getRow() == getIndex() && pos.getTableColumn() == getTableColumn();
 573     }
 574 
 575     private boolean isInCellSelectionMode() {
 576         TableView<S> tableView = getTableView();
 577         if (tableView == null) return false;
 578         TableSelectionModel<S> sm = tableView.getSelectionModel();
 579         return sm != null && sm.isCellSelectionEnabled();
 580     }
 581     
 582     /*
 583      * This was brought in to fix the issue in RT-22077, namely that the 
 584      * ObservableValue was being GC'd, meaning that changes to the value were
 585      * no longer being delivered. By extracting this value out of the method, 
 586      * it is now referred to from TableCell and will therefore no longer be
 587      * GC'd.
 588      */
 589     private ObservableValue<T> currentObservableValue = null;
 590 
 591     private boolean isFirstRun = true;
 592 
 593     private WeakReference<S> oldRowItemRef;
 594 
 595     /*
 596      * This is called when we think that the data within this TableCell may have
 597      * changed. You'll note that this is a private function - it is only called
 598      * when one of the triggers above call it.
 599      */
 600     private void updateItem(int oldIndex) {
 601         if (currentObservableValue != null) {
 602             currentObservableValue.removeListener(weaktableRowUpdateObserver);
 603         }
 604         
 605         // get the total number of items in the data model
 606         final TableView<S> tableView = getTableView();
 607         final List<S> items = tableView == null ? FXCollections.<S>emptyObservableList() : tableView.getItems();
 608         final TableColumn<S,T> tableColumn = getTableColumn();
 609         final int itemCount = items == null ? -1 : items.size();
 610         final int index = getIndex();
 611         final boolean isEmpty = isEmpty();
 612         final T oldValue = getItem();
 613 
 614         final TableRow<S> tableRow = getTableRow();
 615         final S rowItem = tableRow == null ? null : tableRow.getItem();
 616 
 617         final boolean indexExceedsItemCount = index >= itemCount;
 618         
 619         // there is a whole heap of reasons why we should just punt...
 620         outer: if (indexExceedsItemCount ||
 621                 index < 0 || 
 622                 columnIndex < 0 ||
 623                 !isVisible() ||
 624                 tableColumn == null || 
 625                 !tableColumn.isVisible()) {
 626 
 627             // RT-30484 We need to allow a first run to be special-cased to allow
 628             // for the updateItem method to be called at least once to allow for
 629             // the correct visual state to be set up. In particular, in RT-30484
 630             // refer to Ensemble8PopUpTree.png - in this case the arrows are being
 631             // shown as the new cells are instantiated with the arrows in the
 632             // children list, and are only hidden in updateItem.
 633             // RT-32621: There are circumstances where we need to updateItem,
 634             // even when the index is greater than the itemCount. For example,
 635             // RT-32621 identifies issues where a TreeTableView collapses a
 636             // TreeItem but the custom cells remain visible. This is now
 637             // resolved with the check for indexExceedsItemCount.
 638             if ((!isEmpty && oldValue != null) || isFirstRun || indexExceedsItemCount) {
 639                 updateItem(null, true);
 640                 isFirstRun = false;
 641             }
 642             return;
 643         } else {
 644             currentObservableValue = tableColumn.getCellObservableValue(index);
 645             final T newValue = currentObservableValue == null ? null : currentObservableValue.getValue();
 646 
 647             // RT-35864 - if the index didn't change, then avoid calling updateItem
 648             // unless the item has changed.
 649             if (oldIndex == index) {
 650                 if (!isItemChanged(oldValue, newValue)) {
 651                     // RT-36670: we need to check the row item here to prevent
 652                     // the issue where the cell value and index doesn't change,
 653                     // but the backing row object does.
 654                     S oldRowItem = oldRowItemRef != null ? oldRowItemRef.get() : null;
 655                     if (oldRowItem != null && oldRowItem.equals(rowItem)) {
 656                         // RT-37054:  we break out of the if/else code here and
 657                         // proceed with the code following this, so that we may
 658                         // still update references, listeners, etc as required//.
 659                         break outer;
 660                     }
 661                 }
 662             }
 663             updateItem(newValue, false);
 664         }
 665 
 666         oldRowItemRef = new WeakReference<>(rowItem);
 667         
 668         if (currentObservableValue == null) {
 669             return;
 670         }
 671         
 672         // add property change listeners to this item
 673         currentObservableValue.addListener(weaktableRowUpdateObserver);
 674     }
 675 
 676     @Override protected void layoutChildren() {
 677         if (itemDirty) {
 678             updateItem(-1);
 679             itemDirty = false;
 680         }
 681         super.layoutChildren();
 682     }
 683 
 684     
 685 
 686 
 687     /***************************************************************************
 688      *                                                                         *
 689      *                              Expert API                                 *
 690      *                                                                         *
 691      **************************************************************************/
 692 
 693     /**
 694      * Updates the TableView associated with this TableCell. This is typically
 695      * only done once when the TableCell is first added to the TableView.
 696      *
 697      * @expert This function is intended to be used by experts, primarily
 698      *         by those implementing new Skins. It is not common
 699      *         for developers or designers to access this function directly.
 700      */
 701     public final void updateTableView(TableView tv) {
 702         setTableView(tv);
 703     }
 704 
 705     /**
 706      * Updates the TableRow associated with this TableCell.
 707      *
 708      * @expert This function is intended to be used by experts, primarily
 709      *         by those implementing new Skins. It is not common
 710      *         for developers or designers to access this function directly.
 711      */
 712     public final void updateTableRow(TableRow tableRow) {
 713         this.setTableRow(tableRow);
 714     }
 715 
 716     /**
 717      * Updates the TableColumn associated with this TableCell.
 718      *
 719      * @expert This function is intended to be used by experts, primarily
 720      *         by those implementing new Skins. It is not common
 721      *         for developers or designers to access this function directly.
 722      */
 723     public final void updateTableColumn(TableColumn col) {
 724         // remove style class of existing table column, if it is non-null
 725         TableColumn<S,T> oldCol = getTableColumn();
 726         if (oldCol != null) {
 727             oldCol.getStyleClass().removeListener(weakColumnStyleClassListener);
 728             getStyleClass().removeAll(oldCol.getStyleClass());
 729 
 730             oldCol.idProperty().removeListener(weakColumnIdListener);
 731             oldCol.styleProperty().removeListener(weakColumnStyleListener);
 732 
 733             String id = getId();
 734             String style = getStyle();
 735             if (id != null && id.equals(oldCol.getId())) {
 736                 setId(null);
 737             }
 738             if (style != null && style.equals(oldCol.getStyle())) {
 739                 setStyle("");
 740             }
 741         }
 742         
 743         setTableColumn(col);
 744         
 745         if (col != null) {
 746             getStyleClass().addAll(col.getStyleClass());
 747             col.getStyleClass().addListener(weakColumnStyleClassListener);
 748 
 749             col.idProperty().addListener(weakColumnIdListener);
 750             col.styleProperty().addListener(weakColumnStyleListener);
 751 
 752             possiblySetId(col.getId());
 753             possiblySetStyle(col.getStyle());
 754         }
 755     }
 756 
 757 
 758 
 759     /***************************************************************************
 760      *                                                                         *
 761      * Stylesheet Handling                                                     *
 762      *                                                                         *
 763      **************************************************************************/
 764 
 765     private static final String DEFAULT_STYLE_CLASS = "table-cell";
 766     private static final PseudoClass PSEUDO_CLASS_LAST_VISIBLE = 
 767             PseudoClass.getPseudoClass("last-visible");
 768 
 769     private void possiblySetId(String idCandidate) {
 770         if (getId() == null || getId().isEmpty()) {
 771             setId(idCandidate);
 772         }
 773     }
 774 
 775     private void possiblySetStyle(String styleCandidate) {
 776         if (getStyle() == null || getStyle().isEmpty()) {
 777             setStyle(styleCandidate);
 778         }
 779     }
 780 
 781 
 782 
 783     /***************************************************************************
 784      *                                                                         *
 785      * Accessibility handling                                                  *
 786      *                                                                         *
 787      **************************************************************************/
 788 
 789     @Override
 790     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 791         switch (attribute) {
 792             case ROW_INDEX: return getIndex();
 793             case COLUMN_INDEX: return columnIndex;
 794             case SELECTED: return isInCellSelectionMode() ? isSelected() : getTableRow().isSelected();
 795             default: return super.queryAccessibleAttribute(attribute, parameters);
 796         }
 797     }
 798 
 799     @Override
 800     public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 801         switch (action) {
 802             case REQUEST_FOCUS: {
 803                 TableView<S> tableView = getTableView();
 804                 if (tableView != null) {
 805                     TableViewFocusModel<S> fm = tableView.getFocusModel();
 806                     if (fm != null) {
 807                         fm.focus(getIndex(), getTableColumn());
 808                     }
 809                 }
 810                 break;
 811             }
 812             default: super.executeAccessibleAction(action, parameters);
 813         }
 814     }
 815 }