I have an interesting problem, and I am relatively new to JavaFX and I need to create a somewhat niche ObservableList
implementation.
Essentially, I need an ObservableList
that maintains a list of mapped derived values off another ObservableList
. I need to create an ObservableDistinctList<P,V>
that accepts another ObservableList<P>
and a Function<P,V>
lambda as its constructor arguments. The ObservableDistinctList<P,V>
maintains a list of distinct values off the applied Function<P,V>
for each element in ObservableList<P>
.
For example, say I have ObservableList<Flight> flights
with the following instances.
Flt # Carrier Orig Dest Dep Date
174 WN ABQ DAL 5/6/2015
4673 WN DAL HOU 5/6/2015
485 DL DAL PHX 5/7/2015
6758 UA JFK HOU 5/7/2015
If I created a new ObservableDistinctList off the carrier values of each Flight object, this is how I would do it on the client side.
ObservableDistinctList<Flight,String> distinctCarriers = new
ObservableDistinctList(flights, f -> f.getCarrier());
These would be the only values in that distinctCarriers
list.
WN
DL
UA
If a flight got added to flights
, it would first check if a new distinct value is actually present before adding it. So a new WN
flight would not cause an addition to the distinctCarriers
list, but an AA
flight will. Conversely, if a flight gets removed from flights
, it needs to check if other instances would persist the value before removing it. Removing a WN flight from flights
would not cause a removal of WN
from the distinctCarriers
list, but removing the DL
flight will cause its removal.
Here is my implementation. Did I implement the ListChangeListener
correctly? I get really uncomfortable with List mutability so I wanted to post this before I consider using it in my project. Also, do I need to worry about threadsafety using an ArrayList
to back this?
public final class ObservableDistinctList<P,V> extends ObservableListBase<V> {
private final ObservableList<P> parentList;
private final Function<P,V> valueExtractor;
private final List<V> values;
public ObservableDistinctList(ObservableList<P> parentList, Function<P,V> valueExtractor) {
this.parentList = parentList;
this.valueExtractor = valueExtractor;
this.values = parentList.stream().map(p -> valueExtractor.apply(p)).distinct().collect(Collectors.toList());
this.parentList.addListener((ListChangeListener.Change<? extends P> c) -> {
while (c.next()) {
if (c.wasRemoved()) {
final Stream<V> candidatesForRemoval = c.getRemoved().stream().map(p -> valueExtractor.apply(p));
final List<V> persistingValues = parentList.stream().map(p -> valueExtractor.apply(p)).distinct().collect(Collectors.toList());
final Stream<V> valuesToRemove = candidatesForRemoval.filter(v -> ! persistingValues.contains(v));
valuesToRemove.forEach(v -> values.remove(v));
}
if (c.wasAdded()) {
final Stream<V> candidatesForAdd = c.getAddedSubList().stream().map(p -> valueExtractor.apply(p));
final List<V> existingValues = parentList.stream().map(p -> valueExtractor.apply(p)).distinct().collect(Collectors.toList());
final Stream<V> valuesToAdd = candidatesForAdd.filter(v -> ! values.contains(v));
valuesToAdd.forEach(v -> values.add(v));
}
}
});
}
@Override
public V get(int index) {
return values.get(index);
}
@Override
public int size() {
return values.size();
}
}
Below is a simple example (plus driver - hint: that's what you should have provided in the question :-) custom ObservableList that keeps distinct values of a property of the elements in a source list. It keeps itself sync'ed to the source on adding/removing items. The sync is implemented by:
The notification is handled by messaging the utility methods nextRemove/nextAdd as appropriate.