NPE for TableCell's skin

62 Views Asked by At

I have implemented a TableView in my application. I have created custom skin for the TableCell and initiated the custom skin in the createDefaultSkin() method of the TableCell. Everything works as expected.

But after my application ran for continous 14 hours, it got crashed showing the following error.

java.lang.NullPointerException: Cannot invoke "javafx.scene.control.Skin.dispose()" because the return value of "javafx.scene.control.IndexedCell.getSkin()" is null
    at javafx.scene.control.skin.TableRowSkinBase.recreateCells(TableRowSkinBase.java:713)
    at javafx.scene.control.skin.TableRowSkinBase.updateCells(TableRowSkinBase.java:496)
    at javafx.scene.control.skin.TableRowSkinBase.checkState(TableRowSkinBase.java:640)
    at javafx.scene.control.skin.TableRowSkinBase.layoutChildren(TableRowSkinBase.java:247)
    at javafx.scene.control.Control.layoutChildren(Control.java:601)
    at javafx.scene.control.Cell.layoutChildren(Cell.java:636)
    at javafx.scene.Parent.layout(Parent.java:1207)
    at javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.scene.Scene.doLayoutPass(Scene.java:579)
    at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2499)
    at com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:405)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:404)
    at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:434)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:575)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:555)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:548)
    at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:352)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
    at com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$11(GtkApplication.java:316)
    at java.base/java.lang.Thread.run(Thread.java:833)

And the code in TableRowSkinBase -> recreateCells() is as below:

/*
     * A map that maps from TableColumn to TableCell (i.e. model to view).
     * This is recreated whenever the leaf columns change, however to increase
     * efficiency we create cells for all columns, even if they aren't visible,
     * and we only create new cells if we don't already have it cached in this
     * map.
     *
     * Note that this means that it is possible for this map to therefore be
     * a memory leak if an application uses TableView and is creating and removing
     * a large number of tableColumns. This is mitigated in the recreateCells()
     * function below - refer to that to learn more.
     */
    WeakHashMap<TableColumnBase, Reference<R>> cellsMap;


    private void recreateCells() {
            if (cellsMap != null) {
                Collection<Reference<R>> cells = cellsMap.values();
                Iterator<Reference<R>> cellsIter = cells.iterator();
                while (cellsIter.hasNext()) {
                    Reference<R> cellRef = cellsIter.next();
                    R cell = cellRef.get();
                    if (cell != null) {
                        cell.updateIndex(-1);
                        cell.getSkin().dispose(); // THIS IS WHERE THE ISSUE OCCURS
                        cell.setSkin(null);
                    }
                }
                cellsMap.clear();
            }
    
            ObservableList<? extends TableColumnBase/*<T,?>*/> columns = getVisibleLeafColumns();
    
            cellsMap = new WeakHashMap<>(columns.size());
            fullRefreshCounter = DEFAULT_FULL_REFRESH_COUNTER;
            getChildren().clear();
    
            for (TableColumnBase col : columns) {
                if (cellsMap.containsKey(col)) {
                    continue;
                }
    
                // create a TableCell for this column and store it in the cellsMap
                // for future use
                createCellAndCache(col);
            }
        }

I investigated the internal code of TableRowSkinBase and could'nt figure out under what circumstances it can have cells without the skins. I believe if it is something that I did wrong, it should show up in the start of my application or in the early stages. And the custom skin I have is not so complex. All it has is some custom behaviour plugged to it.

import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumnBase;
import javafx.scene.control.skin.TableCellSkinBase;

/**
 * Custom table cell skin.
 *
 * @param <S> The type of the TableView generic type (i.e. S == TableView&lt;S&gt;)
 * @param <T> The type of the content in all cells in this TableColumn
 * @param <C> the cell type
 */
public final class CustomTableCellSkin<S, T, C extends IndexedCell<T>> extends TableCellSkinBase<S, T, C> {

    /** Cell behaviour. */
    private final CustomTableCellBehavior<S, T> behaviour;

    /**
     * Constructor.
     *
     * @param cell skinnable cell
     */
    @SuppressWarnings("unchecked")
    public CustomTableCellSkin(final TableCell<S, T> cell) {
        super((C) cell);
        behaviour = new CustomTableCellBehavior<>(cell);
    }

    /**
     * Return the cell behaviour.
     *
     * @return behaviour
     */
    public final CustomTableCellBehavior<>T> getBehaviour() {
        return behaviour;
    }

    @Override
    public final ReadOnlyObjectProperty<? extends TableColumnBase<S, T>> tableColumnProperty() {
        @SuppressWarnings("unchecked")
        final TableCell<S, T> cell = (TableCell<S, T>) getSkinnable();
        return cell.tableColumnProperty();
    }
}

I know that without a proper reproducible code, it is very hard to expect a solution. But as the issue is raising from a behavior which I don't have control, It would be very grateful if any of you can give any leads or hints regarding the possible cause of this issue.

The key things I am investigating/trying to understand are:

  • Under which conditions a cell skin is cleared
  • If clearing of skin from a cell is possible, then why a null check is not implemented before disposing the cell skin in the recreateCells() method.
0

There are 0 best solutions below