Skip to content

Commit

Permalink
Merge pull request #5 from LoayGhreeb/FixMappedBackedList
Browse files Browse the repository at this point in the history
Fix `MappedBackedList` when the change is `wasUpdated`
  • Loading branch information
Siedlerchr authored Jul 29, 2024
2 parents 4e7ab3e + 9e5bdd5 commit 9320783
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 8 deletions.
25 changes: 20 additions & 5 deletions src/main/java/com/tobiasdiez/easybind/EasyBind.java
Original file line number Diff line number Diff line change
Expand Up @@ -295,25 +295,40 @@ public static <T, U> EasyObservableList<U> map(ObservableList<? extends T> sourc
return new MappedList<>(sourceList, f);
}

public static <T> EasyObservableList<T> flatten(ObservableList<ObservableList<? extends T>> sources) {
public static <T> EasyObservableList<T> flatten(ObservableList<ObservableList<? extends T>> sources) {
return new FlattenedList<>(sources);
}

@SafeVarargs
public static <T> EasyObservableList<T> concat(ObservableList<? extends T>... sources) {
public static <T> EasyObservableList<T> concat(ObservableList<? extends T>... sources) {
return new FlattenedList<>(FXCollections.observableArrayList(sources));
}

/**
* Creates a new list in which each element is converted using the provided mapping.
* All changes to the underlying list are propagated to the converted list.
* <p>
* If the change event indicates that an item was updated, as determined by {@link ListChangeListener.Change#wasUpdated()},
* the mapping function is called to create a new object reflecting the updated value.
* <p>
* In contrast to {@link #map(ObservableList, Function)},
* the items are converted when the are inserted instead of when they are accessed.
* Thus the initial CPU overhead and memory consumption is higher but the access to list items is quicker.
* the items are converted when they are inserted instead of when they are accessed.
* Thus, the initial CPU overhead and memory consumption is higher but the access to list items is quicker.
*/
public static <A, B> EasyObservableList<B> mapBacked(ObservableList<A> source, Function<A, B> mapper) {
return new MappedBackedList<>(source, mapper);
return new MappedBackedList<>(source, mapper, true);
}

/**
* Similar to {@link #mapBacked(ObservableList, Function)}, but allows specifying if new objects should be created on update.
* <p>
* If {@code mapOnUpdate} is {@code true}, new objects are created when items in the source list are updated.
* <p>
* If {@code mapOnUpdate} is {@code false}, updates do not create new objects. This can be useful in scenarios where
* the mapped objects already have bindings or listeners that reflect changes from the source objects.
*/
public static <A, B> EasyObservableList<B> mapBacked(ObservableList<A> source, Function<A, B> mapper, boolean mapOnUpdate) {
return new MappedBackedList<>(source, mapper, mapOnUpdate);
}

public static <A, B, R> EasyBinding<R> combine(ObservableValue<A> src1, ObservableValue<B> src2, BiFunction<A, B, R> f) {
Expand Down
16 changes: 13 additions & 3 deletions src/main/java/com/tobiasdiez/easybind/MappedBackedList.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ class MappedBackedList<E, F> extends TransformationList<E, F> implements EasyObs

private final Function<F, E> mapper;
private final List<E> backingList;
private final boolean mapOnUpdate;

public MappedBackedList(ObservableList<? extends F> sourceList, Function<F, E> mapper) {
public MappedBackedList(ObservableList<? extends F> sourceList, Function<F, E> mapper, boolean mapOnUpdate) {
super(sourceList);
this.mapper = mapper;
this.mapOnUpdate = mapOnUpdate;
this.backingList = new ArrayList<>(sourceList.size());
sourceList.stream().map(mapper).forEach(backingList::add);
}
Expand Down Expand Up @@ -45,8 +47,16 @@ protected void sourceChanged(ListChangeListener.Change<? extends F> change) {
}
nextPermutation(from, to, permutation);
} else if (change.wasUpdated()) {
backingList.set(change.getFrom(), mapper.apply(getSource().get(change.getFrom())));
nextUpdate(change.getFrom());
if (mapOnUpdate) {
for (int i = change.getFrom(); i < change.getTo(); i++) {
E old = backingList.set(i, mapper.apply(getSource().get(i)));
nextSet(i, old);
}
} else {
for (int i = change.getFrom(); i < change.getTo(); i++) {
nextUpdate(i);
}
}
} else {
if (change.wasRemoved()) {
int removePosition = change.getFrom();
Expand Down
90 changes: 90 additions & 0 deletions src/test/java/com/tobiasdiez/easybind/MappedBackedListTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.tobiasdiez.easybind;

import java.util.List;

import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class MappedBackedListTest {

@Test
public void testMappedBackedListWithMappingOnUpdate() {
ObservableList<IntegerProperty> list = FXCollections.observableArrayList(number -> new Observable[]{number});
ObservableList<Integer> mappedList = EasyBind.mapBacked(list, IntegerProperty::get, true);

IntegerProperty number = new SimpleIntegerProperty(1);
list.add(number);

assertEquals(1, mappedList.get(0));

number.set(2);

assertEquals(2, mappedList.get(0));
}

@Test
public void testMappedBackedListWithoutMappingOnUpdate() {
ObservableList<IntegerProperty> list = FXCollections.observableArrayList(number -> new Observable[]{number});
ObservableList<Integer> mappedList = EasyBind.mapBacked(list, IntegerProperty::get, false);

IntegerProperty number = new SimpleIntegerProperty(1);
list.add(number);

assertEquals(1, mappedList.get(0));

number.set(2);

assertEquals(1, mappedList.get(0));
}

@Test
public void testUnSortedListUpdatesWithMappedBackedList() {
ObservableList<IntegerProperty> list = FXCollections.observableArrayList(number -> new Observable[]{number});
ObservableList<Integer> mappedList = EasyBind.mapBacked(list, IntegerProperty::get);
SortedList<Integer> sortedList = new SortedList<>(mappedList);

IntegerProperty num1 = new SimpleIntegerProperty(1);
IntegerProperty num2 = new SimpleIntegerProperty(3);
IntegerProperty num3 = new SimpleIntegerProperty(2);

list.addAll(num1, num2, num3);

// list= [1, 3, 2], sortedList= [1, 3, 2]
assertEquals(List.of(1, 3, 2), sortedList);

num2.set(4);

// list= [1, 4, 2], sortedList= [1, 4, 2]
assertEquals(List.of(1, 4, 2), sortedList);
}


@Test
public void testSortedListUpdatesWithMappedBackedList() {
ObservableList<IntegerProperty> list = FXCollections.observableArrayList(number -> new Observable[]{number});
ObservableList<Integer> mappedList = EasyBind.mapBacked(list, IntegerProperty::get);
SortedList<Integer> sortedList = new SortedList<>(mappedList, Integer::compareTo);

IntegerProperty num1 = new SimpleIntegerProperty(1);
IntegerProperty num2 = new SimpleIntegerProperty(3);
IntegerProperty num3 = new SimpleIntegerProperty(2);

list.addAll(num1, num2, num3);

// list= [1, 3, 2], sortedList= [1, 2, 3]
assertEquals(List.of(1, 2, 3), sortedList);

num2.set(4);

// list= [1, 4, 2], sortedList= [1, 2, 4]
assertEquals(List.of(1, 2, 4), sortedList);
}
}

0 comments on commit 9320783

Please sign in to comment.