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
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)));
}
}
};
}