Search code examples
javalistviewjavafxtableviewchangelistener

JavaFX ConcurrentModificationException withouth (knowingly) implemtented threads


First of all my objects:

public class Group {
     private final ObservableList<IDevice> sourceList;
     private final ObservableList<IDevice> destinationList;
     private final ObservableList<Mapping> mappingList;
...}

public class Mapping {
     private final IDevice source;
     private final IDevice destination;
     private final MappingMode mode;
     public final StringProperty sourceName = new SimpleStringProperty();
     public final StringProperty destinationName = new SimpleStringProperty();
     public final StringProperty modeName = new SimpleStringProperty();
...}

Basically a group contains two lists of IDevices which can either be source or destination and a mapping list that contains one of them and one of two modes (enum).

The IDevice lists are displayed in an own listview with a table between them, representing the mapping (containing one column from the first, one from the second list and the mode column).

I have added them via setItems, this is the CellFactory for the ListViews

private Callback<ListView<IDevice>, ListCell<IDevice>> getFullNameDisplay() {
    return new Callback<ListView<IDevice>, ListCell<IDevice>>() {
        @Override
        public ListCell<IDevice> call(ListView<IDevice> p) {
            ListCell<IDevice> cell = new ListCell<IDevice>() {
                @Override
                protected void updateItem(IDevice t, boolean bln) {
                    super.updateItem(t, bln);
                    if (t != null) {
                        setText(t.getFullName());
                    }
                    else
                        setText("");
                }
            };
            return cell;
        }
    };
}

The columns are set like this:

sourceColumn.setCellValueFactory(cellData -> cellData.getValue().sourceName);
destinationColumn.setCellValueFactory(cellData -> cellData.getValue().destinationName);
modeColumn.setCellValueFactory(cellData -> cellData.getValue().modeName);

I added two buttons for each listview to add and remove new items.

Of course if I remove a source or destination device, I want all of its mappings removed, so I added a ListChangeListener to the two lists:

 private ListChangeListener<IDevice> getDeviceChangeListener() {
    return (javafx.collections.ListChangeListener.Change<? extends IDevice> c) -> {
        while (c.next()) {
            if (c.wasRemoved()) {
                c.getRemoved().stream().forEach((d) -> {
                    mappingList.stream().filter((map) -> (map.getSource().equals(d) || map.getDestination().equals(d))).forEach((map) -> {
                        mappingList.remove(map);
                    });
                });
            }
        }
    };
}

This also does what I intended it to do (and also all refactorings I tried did), but i cant get why this invokes (most of the time) a ConcurrentModificationException as I have not yet used any threading in my application. It seems as it doesnt trigger each time, which I understand can be lucky scheduling if I would be using threads.. The result is correct though

Someone any clue?

Thanks in advance


Solution

  • You cannot modify a collection while iterating through it, unless the modification is done via the iterator. In Java 8, the Collection class introduced a removeIf(...) method which helps in this use case:

    private ListChangeListener<IDevice> getDeviceChangeListener() {
        return (javafx.collections.ListChangeListener.Change<? extends IDevice> c) -> {
            while (c.next()) {
                if (c.wasRemoved()) {
                    c.getRemoved().forEach(d -> 
                        mappingList.removeIf(map -> map.getDestination().equals(d) 
                                                 || map.getSource().equals(d)));
                }
            }
        };
    }