Skip to content

Commit 9320783

Browse files
authored
Merge pull request #5 from LoayGhreeb/FixMappedBackedList
Fix `MappedBackedList` when the change is `wasUpdated`
2 parents 4e7ab3e + 9e5bdd5 commit 9320783

File tree

3 files changed

+123
-8
lines changed

3 files changed

+123
-8
lines changed

src/main/java/com/tobiasdiez/easybind/EasyBind.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -295,25 +295,40 @@ public static <T, U> EasyObservableList<U> map(ObservableList<? extends T> sourc
295295
return new MappedList<>(sourceList, f);
296296
}
297297

298-
public static <T> EasyObservableList<T> flatten(ObservableList<ObservableList<? extends T>> sources) {
298+
public static <T> EasyObservableList<T> flatten(ObservableList<ObservableList<? extends T>> sources) {
299299
return new FlattenedList<>(sources);
300300
}
301301

302302
@SafeVarargs
303-
public static <T> EasyObservableList<T> concat(ObservableList<? extends T>... sources) {
303+
public static <T> EasyObservableList<T> concat(ObservableList<? extends T>... sources) {
304304
return new FlattenedList<>(FXCollections.observableArrayList(sources));
305305
}
306306

307307
/**
308308
* Creates a new list in which each element is converted using the provided mapping.
309309
* All changes to the underlying list are propagated to the converted list.
310310
* <p>
311+
* If the change event indicates that an item was updated, as determined by {@link ListChangeListener.Change#wasUpdated()},
312+
* the mapping function is called to create a new object reflecting the updated value.
313+
* <p>
311314
* In contrast to {@link #map(ObservableList, Function)},
312-
* the items are converted when the are inserted instead of when they are accessed.
313-
* Thus the initial CPU overhead and memory consumption is higher but the access to list items is quicker.
315+
* the items are converted when they are inserted instead of when they are accessed.
316+
* Thus, the initial CPU overhead and memory consumption is higher but the access to list items is quicker.
314317
*/
315318
public static <A, B> EasyObservableList<B> mapBacked(ObservableList<A> source, Function<A, B> mapper) {
316-
return new MappedBackedList<>(source, mapper);
319+
return new MappedBackedList<>(source, mapper, true);
320+
}
321+
322+
/**
323+
* Similar to {@link #mapBacked(ObservableList, Function)}, but allows specifying if new objects should be created on update.
324+
* <p>
325+
* If {@code mapOnUpdate} is {@code true}, new objects are created when items in the source list are updated.
326+
* <p>
327+
* If {@code mapOnUpdate} is {@code false}, updates do not create new objects. This can be useful in scenarios where
328+
* the mapped objects already have bindings or listeners that reflect changes from the source objects.
329+
*/
330+
public static <A, B> EasyObservableList<B> mapBacked(ObservableList<A> source, Function<A, B> mapper, boolean mapOnUpdate) {
331+
return new MappedBackedList<>(source, mapper, mapOnUpdate);
317332
}
318333

319334
public static <A, B, R> EasyBinding<R> combine(ObservableValue<A> src1, ObservableValue<B> src2, BiFunction<A, B, R> f) {

src/main/java/com/tobiasdiez/easybind/MappedBackedList.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ class MappedBackedList<E, F> extends TransformationList<E, F> implements EasyObs
1111

1212
private final Function<F, E> mapper;
1313
private final List<E> backingList;
14+
private final boolean mapOnUpdate;
1415

15-
public MappedBackedList(ObservableList<? extends F> sourceList, Function<F, E> mapper) {
16+
public MappedBackedList(ObservableList<? extends F> sourceList, Function<F, E> mapper, boolean mapOnUpdate) {
1617
super(sourceList);
1718
this.mapper = mapper;
19+
this.mapOnUpdate = mapOnUpdate;
1820
this.backingList = new ArrayList<>(sourceList.size());
1921
sourceList.stream().map(mapper).forEach(backingList::add);
2022
}
@@ -45,8 +47,16 @@ protected void sourceChanged(ListChangeListener.Change<? extends F> change) {
4547
}
4648
nextPermutation(from, to, permutation);
4749
} else if (change.wasUpdated()) {
48-
backingList.set(change.getFrom(), mapper.apply(getSource().get(change.getFrom())));
49-
nextUpdate(change.getFrom());
50+
if (mapOnUpdate) {
51+
for (int i = change.getFrom(); i < change.getTo(); i++) {
52+
E old = backingList.set(i, mapper.apply(getSource().get(i)));
53+
nextSet(i, old);
54+
}
55+
} else {
56+
for (int i = change.getFrom(); i < change.getTo(); i++) {
57+
nextUpdate(i);
58+
}
59+
}
5060
} else {
5161
if (change.wasRemoved()) {
5262
int removePosition = change.getFrom();
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package com.tobiasdiez.easybind;
2+
3+
import java.util.List;
4+
5+
import javafx.beans.Observable;
6+
import javafx.beans.property.IntegerProperty;
7+
import javafx.beans.property.SimpleIntegerProperty;
8+
import javafx.collections.FXCollections;
9+
import javafx.collections.ObservableList;
10+
import javafx.collections.transformation.SortedList;
11+
12+
import org.junit.jupiter.api.Test;
13+
14+
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
16+
public class MappedBackedListTest {
17+
18+
@Test
19+
public void testMappedBackedListWithMappingOnUpdate() {
20+
ObservableList<IntegerProperty> list = FXCollections.observableArrayList(number -> new Observable[]{number});
21+
ObservableList<Integer> mappedList = EasyBind.mapBacked(list, IntegerProperty::get, true);
22+
23+
IntegerProperty number = new SimpleIntegerProperty(1);
24+
list.add(number);
25+
26+
assertEquals(1, mappedList.get(0));
27+
28+
number.set(2);
29+
30+
assertEquals(2, mappedList.get(0));
31+
}
32+
33+
@Test
34+
public void testMappedBackedListWithoutMappingOnUpdate() {
35+
ObservableList<IntegerProperty> list = FXCollections.observableArrayList(number -> new Observable[]{number});
36+
ObservableList<Integer> mappedList = EasyBind.mapBacked(list, IntegerProperty::get, false);
37+
38+
IntegerProperty number = new SimpleIntegerProperty(1);
39+
list.add(number);
40+
41+
assertEquals(1, mappedList.get(0));
42+
43+
number.set(2);
44+
45+
assertEquals(1, mappedList.get(0));
46+
}
47+
48+
@Test
49+
public void testUnSortedListUpdatesWithMappedBackedList() {
50+
ObservableList<IntegerProperty> list = FXCollections.observableArrayList(number -> new Observable[]{number});
51+
ObservableList<Integer> mappedList = EasyBind.mapBacked(list, IntegerProperty::get);
52+
SortedList<Integer> sortedList = new SortedList<>(mappedList);
53+
54+
IntegerProperty num1 = new SimpleIntegerProperty(1);
55+
IntegerProperty num2 = new SimpleIntegerProperty(3);
56+
IntegerProperty num3 = new SimpleIntegerProperty(2);
57+
58+
list.addAll(num1, num2, num3);
59+
60+
// list= [1, 3, 2], sortedList= [1, 3, 2]
61+
assertEquals(List.of(1, 3, 2), sortedList);
62+
63+
num2.set(4);
64+
65+
// list= [1, 4, 2], sortedList= [1, 4, 2]
66+
assertEquals(List.of(1, 4, 2), sortedList);
67+
}
68+
69+
70+
@Test
71+
public void testSortedListUpdatesWithMappedBackedList() {
72+
ObservableList<IntegerProperty> list = FXCollections.observableArrayList(number -> new Observable[]{number});
73+
ObservableList<Integer> mappedList = EasyBind.mapBacked(list, IntegerProperty::get);
74+
SortedList<Integer> sortedList = new SortedList<>(mappedList, Integer::compareTo);
75+
76+
IntegerProperty num1 = new SimpleIntegerProperty(1);
77+
IntegerProperty num2 = new SimpleIntegerProperty(3);
78+
IntegerProperty num3 = new SimpleIntegerProperty(2);
79+
80+
list.addAll(num1, num2, num3);
81+
82+
// list= [1, 3, 2], sortedList= [1, 2, 3]
83+
assertEquals(List.of(1, 2, 3), sortedList);
84+
85+
num2.set(4);
86+
87+
// list= [1, 4, 2], sortedList= [1, 2, 4]
88+
assertEquals(List.of(1, 2, 4), sortedList);
89+
}
90+
}

0 commit comments

Comments
 (0)