Reputation: 132
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
Upvotes: 0
Views: 501
Reputation: 209330
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)));
}
}
};
}
Upvotes: 2
Reputation: 3945
In the same loop if you are trying to iterate over the same collection and trying to modify the same collection, Java throws this concurrency exception.
If you want to modify the collection, please mantain a different collection for addition or modification. Once it comes out of the loop, call Collection.addAll() or Collection.removeAll() interface.
Upvotes: 1