2

I just added filtering to my TreeTableView and a bug started appearing that seems to be random (probably is not random). I can reproduce it with spamming the filter textField to update the TreeTableView until it jams. I get the following stack trace, the TreeTableView is not updating anymore but the program does not crash. The only part of the stack trace that is related to my code points to a cellFactory where I override the updateItem of a CheckBoxTreeTableCell in order to achieve a styling for top level rows :

colActShop.setCellFactory(column ->{
        return new CheckBoxTreeTableCell<Product, Boolean>(){
            @Override
            public void updateItem(Boolean item, boolean empty) {
                super.updateItem(item, empty);
                boolean isTopLevel = getTreeTableView().getRoot().getChildren()
                                   .contains(getTreeTableRow().getTreeItem());
                if(item == null || empty){
                    setText(null);
                    setGraphic(null);
                    //It crashes on the following line according to the stack trace
                    getTreeTableRow().getStyleClass().remove("topLevelRow");
                }else{
                    if(isTopLevel){
                        getTreeTableRow().getStyleClass().add("topLevelRow");
                    }else{
                        getTreeTableRow().getStyleClass().remove("topLevelRow");
                    }

                    setEditable(!isTopLevel);   
                }
            }
        };
    });

If I remove the getTreeTableRow().getStyleClass()... lines it seems to be removing the bug but then I lose the styling behaviour I am looking for. I desperately also tried putting some Platform.runlater at some places without success. I am unable to provide a minimal executable example for now as my filtering and TreeTableView updating are getting quite large. Here is a function that gets called to update the root of the TreeTableView when I enter text into the filter textField :

     /**
  * A method to update a TreeTableView every time backing data changes in the model,
  * without resetting the table by only adding and removing data when required.
  * Also applies filters
  * @param root : the root TreeItem of the table to update
  * @param inventory : the inventory backing this table
  * @param shop : the shop to calculate the price of products
  * @param filter : comma separated string filter
  * @param catFilter : category filter list
  */
private void updateTreeItemRoot(TreeItem<Product> root, Inventory inventory, Shop shop, String filter, ArrayList<String> catFilter){
    inventory.clean();
    shop.clean();
    inventory.calculateAverageData();
    //retrieve the product list of category items of the previous TreeTableView structure before updating
    ArrayList<Product> oldProdCategoryList= new ArrayList<Product>();
    ArrayList<TreeItem<Product>> oldTreeItemCategoryList= new ArrayList<>(root.getChildren());
    for(TreeItem<Product> treeItem : oldTreeItemCategoryList){
        oldProdCategoryList.add(treeItem.getValue());
    }
    for(Entry<String, ArrayList<Product>> entry : inventory.getData().entrySet()){
        Product product = inventory.getAverageData(entry.getKey());
        //Filtering the TreeTableView 
        boolean filterOK = false;
        if(catFilter!=null){
            if(catFilter.contains("All")||catFilter.contains("All seeds")){
                filterOK = true;
            }else{
                //else look for any matching categories
                for(String str:catFilter){
                    if(product.getCategories().stream().anyMatch(s -> s.equals(str))){
                        filterOK = true;
                        break;
                    }
                }
            }
            //turn filterOk to false if we want only mature and product is not
            if(catFilter.contains("Only mature")&&product.getMaturity()!=100) {
                filterOK = false;
            }
            //turn filterOK to false if we want only seeds and the product is not a seed
            if(catFilter.contains("Only seeds")){
                if(!(product.getCategories().stream().anyMatch(s -> s.equals("Seed")))){
                    filterOK = false;
                }
            }
        }else{
            filterOK = true;
        }
        String[] filterList = filter.split(",");
        if(filter.length()>0&&!Arrays.stream(filterList).anyMatch(s->product.getName().matches( "(?i:.*"+s+".*)"))){
            filterOK = false;
        }
        if(filterOK){
            product.updatePrice(shop);
            TreeItem<Product> averageProdDataItem; ;
            //if this product category is not yet present in the table add it.
            if(!oldProdCategoryList.contains(product)){
                averageProdDataItem = new TreeItem<Product>(product);
                averageProdDataItem.setExpanded(true);
                root.getChildren().add(averageProdDataItem);
            }else{
                //else if this product category is already in the table, retrieve the reference and remove it from the 
                //  "old" Lists to see if something needs to be removed from the table in the end
                averageProdDataItem = oldTreeItemCategoryList.get(oldProdCategoryList.indexOf(product));
                oldTreeItemCategoryList.remove(oldProdCategoryList.indexOf(product));
                oldProdCategoryList.remove(product);
            }
            //retrieve the product list for this category category of the previous TreeTableView structure before updating
            ArrayList<Product> oldProdList= new ArrayList<Product>();
            ArrayList<TreeItem<Product>> oldTreeItemList= new ArrayList<>(averageProdDataItem.getChildren());
            for(TreeItem<Product> treeItem : oldTreeItemList){
                oldProdList.add(treeItem.getValue());
            }
            for(Product prod : entry.getValue()){
                prod.updatePrice(shop);
                TreeItem<Product> treeItem;
                //if the table does not contain this prod, add it
                if(!oldProdList.contains(prod)){
                    treeItem = new TreeItem<Product>(prod);
                    averageProdDataItem.getChildren().add(treeItem);
                }else{

                    oldTreeItemList.remove(oldProdList.indexOf(prod));
                    oldProdList.remove(prod);
                }
            }
            averageProdDataItem.getChildren().removeAll(oldTreeItemList); //clean up empty products
        }// if filterOK
    }//for
    root.getChildren().removeAll(oldTreeItemCategoryList);// clean up empty categories
}

It seems to be the same as this issue that has been opened 3 times and closed without any comment.

Stack Trace:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
    at com.sun.javafx.scene.control.skin.CellSkinBase.access$200(CellSkinBase.java:54)
    at com.sun.javafx.scene.control.skin.CellSkinBase$StyleableProperties$1.getStyleableProperty(CellSkinBase.java:149)
    at com.sun.javafx.scene.control.skin.CellSkinBase$StyleableProperties$1.getStyleableProperty(CellSkinBase.java:138)
    at javafx.scene.CssStyleHelper.resetToInitialValues(CssStyleHelper.java:447)
    at javafx.scene.CssStyleHelper.createStyleHelper(CssStyleHelper.java:180)
    at javafx.scene.Node.reapplyCss(Node.java:8983)
    at javafx.scene.Node.reapplyCss(Node.java:9012)
    at javafx.scene.Node.impl_reapplyCSS(Node.java:8946)
    at javafx.scene.Node$3.onChanged(Node.java:1023)
    at com.sun.javafx.collections.TrackableObservableList.lambda$new$29(TrackableObservableList.java:45)
    at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
    at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
    at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
    at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
    at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
    at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
    at javafx.collections.ModifiableObservableListBase.remove(ModifiableObservableListBase.java:183)
    at javafx.collections.ModifiableObservableListBase.remove(ModifiableObservableListBase.java:171)
    at homier.farmGame.controller.Engine$2.updateItem(Engine.java:768)
    at homier.farmGame.controller.Engine$2.updateItem(Engine.java:1)
    at javafx.scene.control.TreeTableCell.updateItem(TreeTableCell.java:630)
    at javafx.scene.control.TreeTableCell.indexChanged(TreeTableCell.java:457)
    at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:116)
    at com.sun.javafx.scene.control.skin.TableRowSkinBase.recreateCells(TableRowSkinBase.java:671)
    at com.sun.javafx.scene.control.skin.TableRowSkinBase.updateCells(TableRowSkinBase.java:499)
    at com.sun.javafx.scene.control.skin.TreeTableRowSkin.updateCells(TreeTableRowSkin.java:220)
    at com.sun.javafx.scene.control.skin.TableRowSkinBase.checkState(TableRowSkinBase.java:631)
    at com.sun.javafx.scene.control.skin.TableRowSkinBase.computePrefHeight(TableRowSkinBase.java:571)
    at javafx.scene.control.Control.computePrefHeight(Control.java:547)
    at javafx.scene.Parent.prefHeight(Parent.java:935)
    at javafx.scene.layout.Region.prefHeight(Region.java:1435)
    at com.sun.javafx.scene.control.skin.VirtualFlow.resizeCellSize(VirtualFlow.java:1947)
    at com.sun.javafx.scene.control.skin.VirtualFlow.addLeadingCells(VirtualFlow.java:1247)
    at com.sun.javafx.scene.control.skin.VirtualFlow.layoutChildren(VirtualFlow.java:1194)
    at javafx.scene.Parent.layout(Parent.java:1087)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Scene.doLayoutPass(Scene.java:552)
    at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2397)
    at com.sun.javafx.tk.Toolkit.lambda$runPulse$29(Toolkit.java:398)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:397)
    at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:424)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:518)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:498)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:491)
    at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$403(QuantumToolkit.java:319)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
    at java.lang.Thread.run(Unknown Source)
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
    at com.sun.javafx.scene.control.skin.CellSkinBase.access$200(CellSkinBase.java:54)
    at com.sun.javafx.scene.control.skin.CellSkinBase$StyleableProperties$1.getStyleableProperty(CellSkinBase.java:149)
    at com.sun.javafx.scene.control.skin.CellSkinBase$StyleableProperties$1.getStyleableProperty(CellSkinBase.java:138)
    at javafx.scene.CssStyleHelper.resetToInitialValues(CssStyleHelper.java:447)
    at javafx.scene.CssStyleHelper.createStyleHelper(CssStyleHelper.java:180)
    at javafx.scene.Node.reapplyCss(Node.java:8983)
    at javafx.scene.Node.impl_reapplyCSS(Node.java:8946)
    at javafx.scene.control.Control$1.invalidated(Control.java:300)
    at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:111)
    at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
    at javafx.css.StyleableObjectProperty.set(StyleableObjectProperty.java:82)
    at javafx.scene.control.Control$1.set(Control.java:237)
    at javafx.scene.control.Control$1.set(Control.java:220)
    at javafx.scene.control.Control.setSkin(Control.java:217)
    at com.sun.javafx.scene.control.skin.TableRowSkinBase.recreateCells(TableRowSkinBase.java:673)
    at com.sun.javafx.scene.control.skin.TableRowSkinBase.updateCells(TableRowSkinBase.java:499)
    at com.sun.javafx.scene.control.skin.TreeTableRowSkin.updateCells(TreeTableRowSkin.java:220)
    at com.sun.javafx.scene.control.skin.TableRowSkinBase.checkState(TableRowSkinBase.java:631)
    at com.sun.javafx.scene.control.skin.TableRowSkinBase.computePrefHeight(TableRowSkinBase.java:571)
    at javafx.scene.control.Control.computePrefHeight(Control.java:547)
    at javafx.scene.Parent.prefHeight(Parent.java:935)
    at javafx.scene.layout.Region.prefHeight(Region.java:1435)
    at com.sun.javafx.scene.control.skin.VirtualFlow.resizeCellSize(VirtualFlow.java:1947)
    at com.sun.javafx.scene.control.skin.VirtualFlow.addLeadingCells(VirtualFlow.java:1247)
    at com.sun.javafx.scene.control.skin.VirtualFlow.layoutChildren(VirtualFlow.java:1194)
    at javafx.scene.Parent.layout(Parent.java:1087)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Parent.layout(Parent.java:1093)
    at javafx.scene.Scene.doLayoutPass(Scene.java:552)
    at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2397)
    at com.sun.javafx.tk.Toolkit.lambda$runPulse$29(Toolkit.java:398)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:397)
    at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:424)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:518)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:498)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:491)
    at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$403(QuantumToolkit.java:319)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
    at java.lang.Thread.run(Unknown Source)

Does anyone have an idea to guide my debugging? Do I need to give up on styling certain rows?

EDIT 1 After some further testing it is less random than I thought. I enter one letter in the filter field and remove it 34 times before it jams, reproductible. If I use a sequence of letters (instead of using the same letter each time) it seems to jam earlier than 34, like 29 or 31. So it makes me think that something it not getting cleaned each iteration but I have no idea what.

Ram
  • 115
  • 10
  • 1
    Can you create a [mcve] that reproduces the error? That way we can debug on our own machines as well. Also, what version of JavaFX are you using? – Slaw Nov 19 '18 at 01:01
  • @Slaw I am trying myself to get one going but I don't even know where to start. If I figure out how to isolate the problem I will probably find the answer, but I will keep trying. I am using the latest version I think. In eclipse I have C:\Program Files\Java\jdk1.8.0_191\jre – Ram Nov 19 '18 at 01:13
  • it's conceptually wrong to set the row style from a cell - so at the end of the day you get the trouble you asked for ;) Anyway, don't understand why you're moving backwards: you got the advice to use a rowFactory already some weeks ago .. – kleopatra Nov 20 '18 at 09:33
  • I will most probably be moving backwards, sideways, downwards and sometimes forward. I think if I remember correctly I only had to change the editable state of one cell so I just used a cellfactory in that case. Then I decided to access the row to set a style since it was available to me. It was not obvious to me that it was conceptually wrong. Thanks for the help as always. I still think I found an interesting bug. – Ram Nov 20 '18 at 23:55

1 Answers1

0

The reason for the bug is still unknown to me. I think I found a way around by setting the style of the special rows by using setRowFactory on the TreeTableView instead of setCellFactory and getTreeTableRow() on the Column. It seems weird since the Row is the same object, I just access it in a different way but so far I have not had bugs with this way.

    tableShop.setRowFactory(table-> {
        return new TreeTableRow<Product>(){
            @Override
            public void updateItem(Product pers, boolean empty) {
                super.updateItem(pers, empty);      
                boolean isTopLevel = table.getRoot().getChildren().contains(treeItemProperty().get());
                if(pers==null||empty) {
                    setText(null);
                    setGraphic(null);
                    getStyleClass().remove("topLevelRow");
                }else {
                    if(isTopLevel) {
                        if(!getStyleClass().contains("topLevelRow")) {
                            getStyleClass().add("topLevelRow");
                        }
                    }else {
                        getStyleClass().remove("topLevelRow");
                    }
                }
            }
        };
    });
Ram
  • 115
  • 10