Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mapOnUpdate option to mapBacked method #4

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}